redis 缓存封装工具类

缓存穿透和击穿

1.缓存穿透问题的解决思路

缓存穿透 :缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。

常见的解决方案有两种:

  • 缓存空对象
    • 优点:实现简单,维护方便
    • 缺点:
      • 额外的内存消耗
      • 可能造成短期的不一致
  • 布隆过滤
    • 优点:内存占用较少,没有多余key
    • 缺点:
      • 实现复杂
      • 存在误判可能

本章只介缓存空对象
在这里插入图片描述

缓存击穿问题及解决思路
缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

常见的解决方案有两种:

  • 互斥锁
  • 逻辑过期

逻辑分析:假设线程1在查询缓存之后,本来应该去查询数据库,然后把这个数据重新加载到缓存的,此时只要线程1走完这个逻辑,其他线程就都能从缓存中加载这些数据了,但是假设在线程1没有走完的时候,后续的线程2,线程3,线程4同时过来访问当前这个方法, 那么这些线程都不能从缓存中查询到数据,那么他们就会同一时刻来访问查询缓存,都没查到,接着同一时间去访问数据库,同时的去执行数据库代码,对数据库访问压力过大
在这里插入图片描述
在这里插入图片描述

package com.hmdp.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.entity.Shop;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Component
public class CacheClient {

    private final StringRedisTemplate stringRedisTemplate;

    //    创建线程池
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    //添加互斥锁
    public boolean trylock(String key) {
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
//        直接返回有可能会导致拆箱使值为空
        return BooleanUtil.isTrue(aBoolean);
    }

    //释放锁
    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }


    //    用于缓存穿透
    public void set(String key, Object value, Long time, TimeUnit timeUnit) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, timeUnit);
    }

    //缓存重建 用于缓存击穿
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit timeUnit) {
//        设置过期时间
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusMinutes(timeUnit.toSeconds(time)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData), time, timeUnit);
    }

    //    缓存穿透
    public <R, ID> R querywithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit timeUnit) {
//              从redis获取数据
        String key = keyPrefix + id.toString();
        String Json = stringRedisTemplate.opsForValue().get(key);

        //        判断是否存在

        if (StrUtil.isBlank(Json)) {
//        存在返回
            return JSONUtil.toBean(Json, type);
        }

//        判断命中的是否为空值
        if (Json != null) {
//            返回错误信息
            return null;
        }


//        不存在 查询数据库
        R r = dbFallback.apply(id);

//        判断是数据库是否存在
        if (r == null) {
//        不存在也存储空对象
            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id.toString(), "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }

//        存在则保存到redis中 返回数据

        this.set(key, r, time, timeUnit);

        return r;
    }

    //    逻辑过期
    public <ID, R> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback,Long time, TimeUnit timeUnit) {
        String key = keyPrefix + id;
//        从redis获取缓存
        String Json = stringRedisTemplate.opsForValue().get(key);
//    判断缓存是否存在
        if (StrUtil.isBlank(Json)) {
//           不存在直接返回
            return null;
        }
//存在,先把JSON反序列化对象
        RedisData redisData = JSONUtil.toBean(Json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();

//        判断缓存是否过期
        if (expireTime.isAfter(LocalDateTime.now())) {//过期时间是不是在当前时间之后,没有则没过期
//        未过期直接返回
            return r;
        }

//        过期需要进行缓存重建

//        获取互斥锁
        boolean lock = trylock(key);
//        判断是否获取锁
        if (lock) {
            //        获取开启独立线程,重建线程池
            CACHE_REBUILD_EXECUTOR.submit(() -> {
//                重建缓存
                try {
//                    查询数据库id
                    R r1 = dbFallback.apply(id);
//                    缓存重建
                    this.setWithLogicalExpire(key,r1,time,timeUnit);
                    } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //释放锁
                    unlock(key);
                }

            });

        }

//返回过期商品信息
        return r;

    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值