缓存问题(四) 缓存穿透、缓存雪崩、缓存并发 解决案例

2022-10-06 10:56:56

视频地址:https://www.bilibili.com/video/BV1Ha411c7hB

代码地址:https://gitee.com/crazyliyang/video-teaching.git

1. 缓存穿透 解决案例

使用布隆过滤器

aaaaa.png

核心代码, 使用 Redisson 库的布隆过滤器:org.redisson.api.RBloomFilter

<!-- Redis 客户端工具 redisson 实现对 Redisson 的自动化配置-->
<dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson-spring-boot-starter</artifactId>
     <version>3.13.4</version>
</dependency>

配置类, 实例化布隆过滤器RBloomFilter

@Configuration
public class RedisConfiguration {
    /**
     * 预计要插入多少数据
     */
    private static int size = 100000; // 十万

    /**
     * 期望的误判率
     */
    private static double fpp = 0.001;

    @Value("${spring.redis.host}")
    private String redistHost;

    @Value("${spring.redis.port}")
    private Integer redistPort;


    @Autowired
    private ProductService productService;  // 商品服务


    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        // 创建 RedisTemplate 对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();

        // 设置开启事务支持
        template.setEnableTransactionSupport(true);

        // 设置 RedisConnection 工厂。 它就是实现多种 Java Redis 客户端接入的秘密工厂
        template.setConnectionFactory(factory);

        // 使用 String 序列化方式,序列化 KEY 。
        template.setKeySerializer(RedisSerializer.string());

        // 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
        template.setValueSerializer(RedisSerializer.json());

        return template;
    }

    /**
     * 实例化布隆过滤器
     *
     */
    @Bean
    public RBloomFilter redisBloomFilter(){

        String redisAddress = "redis://"+redistHost+ ":"+redistPort;

        Config config = new Config();
        config.useSingleServer().setAddress(redisAddress);
        RedissonClient redisson = Redisson.create(config);

        // 创建RedisBloomFilter
        RBloomFilter<String> redisBloomFilter = redisson.getBloomFilter("productBloomFilter03");

        //初始化布隆过滤器:预计元素为10000L ( 十万), 误差率为 0.001
        redisBloomFilter.tryInit(size, fpp);

        return redisBloomFilter;
    }

}

使用隆过滤器RBloomFilter

@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, ProductEntity> implements ProductService {

    //redisKey前缀
    private static final String KEY_PREFIX = "product:";

    //  格式化一下
    private static String buildKey(String id) {
        return KEY_PREFIX + id;  // product:商品ID
    }

    @Resource(name = "redisTemplate")
    private ValueOperations<Serializable, String> redisOperations;

    @Autowired
    private RBloomFilter<String> redisBloomFilter;

    /**
     * 重载 getById(id)  做第一点增强
     *
     */
    @Override
    public ProductEntity getById(Serializable id) {
        String redisKey = buildKey(String.valueOf(id)); // 构建rediskey
        
        if(!redisBloomFilter.contains(redisKey)){  // 布隆过滤器判断 redisKey 不存在
            return null;
        }
        String redisValue = redisOperations.get(redisKey);  // 从缓存中去查

        if (redisValue != null) {  // 查到了
            return JSONUtil.parseObject(redisValue, ProductEntity.class); //直接返回;

        } else { // 没查到
            ProductEntity product = getBaseMapper().selectById(id); // 从数据库中查, 然后放到缓存中;
            if(product !=null){
                String jsonString = JSONUtil.toJSONString(product);
                redisOperations.set(redisKey, jsonString);    // 放到缓存中
                return product;
            }
            return null;
        }
    }
}

2. 缓存雪崩 解决案例

bbbbb.png

核心代码: 使用一个过期时间的随机数,  让 key 的过期时间分散化,    防止 key  大批集中过期;

@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, ProductEntity> implements ProductService {
    private static final String KEY_PREFIX = "product:";
    private static String buildKey(String id) {
        return KEY_PREFIX + id;  // product:商品ID
    }
    @Resource(name = "redisTemplate")
    private ValueOperations<Serializable, String> redisOperations;
   
    @Autowired
    private RBloomFilter<String> redisBloomFilter;

    /**
     * 重载 getById(id)  做第一点增强
     */
    @Override
    public ProductEntity getById(Serializable id) {
        String redisKey = buildKey(String.valueOf(id)); // 构建rediskey
        if(!redisBloomFilter.contains(redisKey)){  // 布隆过滤器判断 redisKey 不存在
            return null;
        }
        String redisValue = redisOperations.get(redisKey);  // 从缓存中去查
        if (redisValue != null) {  // 查到了
            return JSONUtil.parseObject(redisValue, ProductEntity.class); //直接返回;

        } else { // 没查到
            ProductEntity product = getBaseMapper().selectById(id); // 从数据库中查, 然后放到缓存中;
            if(product !=null){
                String jsonString = JSONUtil.toJSONString(product);
                long radomLong = radomLong();  // 随机产生一个数 ( 24 ~ 72 )
                redisOperations.set(redisKey, jsonString, radomLong, TimeUnit.HOURS);    // 放到缓存中, 时间取随机, 时间单位小时
                return product;
            }
            return null;
        }
    }
    public static long radomLong(){
        long min = 24;
        long max = 72;
        long rangeLong = min + (((long) (new Random().nextDouble() * (max - min))));
        return rangeLong;
    }

}

3. 缓存并发 解决案例

cccc.png

使用Redis 分布式锁, 核心代码:

封装 RedisLock

@Component
public class RedisLock {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 加锁
     * @param lockKey
     * @return
     */
    public  RLock lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        return lock;
    }

    /**
     * 释放锁
     * @param lockKey
     */
    public  void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }

    /**
     * 释放锁
     * @param lock
     */
    public  void unlock(RLock lock) {
        lock.unlock();
    }

    /**
     * 带超时的锁
     * @param lockKey
     * @param timeout 超时时间   单位:秒
     */
    public  RLock lock(String lockKey, int timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, TimeUnit.SECONDS);
        return lock;
    }

    /**
     * 带超时的锁
     * @param lockKey
     * @param unit 时间单位
     * @param timeout 超时时间
     */
    public  RLock lock(String lockKey, TimeUnit unit , int timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
        return lock;
    }

    /**
     * 尝试获取锁
     * @param lockKey
     * @param waitTime 最多等待时间
     * @param leaseTime 上锁后自动释放锁时间
     * @return
     */
    public  boolean tryLock(String lockKey, int waitTime, int leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            return false;
        }
    }

    /**
     * 尝试获取锁
     * @param lockKey
     * @param unit 时间单位
     * @param waitTime 最多等待时间
     * @param leaseTime 上锁后自动释放锁时间
     * @return
     */
    public  boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            return false;
        }
    }

}

配置类 实例化 注册RedissonClient

@Configuration
public class RedisConfiguration {
    
    @Value("${spring.redis.host}")
    private String redistHost;

    @Value("${spring.redis.port}")
    private Integer redistPort;


    @Autowired
    private ProductService productService;  // 商品服务

    /**
     * RedissonClient 注册
     */
    @Bean
    public RedissonClient redissonClient(){
        String redisAddress = "redis://"+redistHost+ ":"+redistPort;

        Config config = new Config();
        config.useSingleServer().setAddress(redisAddress);
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
    
}

使用Redis 分布式锁:

@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, ProductEntity> implements ProductService {

    //前缀
    private static final String KEY_PREFIX = "product:";

    private static String buildKey(String id) {
        return KEY_PREFIX + id;  // product:商品ID
    }

    @Resource(name = "redisTemplate")
    private ValueOperations<Serializable, String> redisOperations;

    @Autowired
    private RBloomFilter<String> redisBloomFilter;

    @Autowired
    private RedisLock redisLock;   // 自己封装的 redisLock

    /**
     * 重载 getById(id)  做第一点增强
     */
    @Override
    public ProductEntity getById(Serializable id) {
        String redisKey = buildKey(String.valueOf(id)); // 构建rediskey

        if (!redisBloomFilter.contains(redisKey)) {  // 布隆过滤器判断 redisKey 不存在
            return null;
        }
        String redisValue = redisOperations.get(redisKey);  // 从缓存中去查

        if (redisValue != null) {  // 查到了
            return JSONUtil.parseObject(redisValue, ProductEntity.class); //直接返回;
        }

        // 在查询 DB 之前加 分布式锁
        redisLock.lock(String.valueOf(id));
        try{
            // 再一次查询缓存, 是为了分布式并发情况下, 其他并发的请求, 能在锁解开之后, 再次查缓存
            String redisValue2 = redisOperations.get(redisKey);
            if (redisValue2 != null) {
                return JSONUtil.parseObject(redisValue, ProductEntity.class);
            }
            ProductEntity product = getBaseMapper().selectById(id); // 从数据库中查
            if (product != null) {
                String jsonString = JSONUtil.toJSONString(product);
                redisOperations.set(redisKey, jsonString);    // 放到缓存中
                return product;
            }
            return null;
        }finally {
            redisLock.unlock(String.valueOf(id));   // 释放锁
        }
    }
}
  • 作者:Crzayliyang-架构Young
  • 原文链接:https://blog.csdn.net/Crazy_liyang/article/details/109609876
    更新时间:2022-10-06 10:56:56