关于Redis缓存雪崩引起的思考

关于Redis缓存雪崩引起的思考

通常我们为了保证缓存中的数据与数据库中的数据一致性,会给 Redis 里的数据设置过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成缓存,因此就会访问数据库,并将数据更新到 Redis 里,这样后续请求都可以直接命中缓存。

图片

那么,当大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。


缓存雪崩两个原因:

  • 大量数据同时过期
  • Redis宕机

在这里我们只讨论大量数据同时过期的问题,常见的应对方式有设置随机过期时间、互斥锁、后台定时更新缓存等策略

关于互斥锁我们可以使用synchronized关键字来实现,详细代码🔽

/**
 * @author 海杰
 * @date 2021/8/9 10:04
 */
@RestController
public class TestController {
    @Autowired
    private RedisUtil redisUtil;

    @GetMapping("/test1/{key}")
    public String test1(@PathVariable String key) throws Exception {

        if (redisUtil.get(key)==null){
            synchronized (this){
                if (redisUtil.get(key)==null){
                    System.out.println("模拟从数据库取数据......");
                    Thread.sleep(10000);
                    redisUtil.set(key,key);
                }
            }
        }
        return redisUtil.get(key) + Thread.currentThread().getName();
    }
}

当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。但是这个模型只适用于单机服务,因为在集群模式下,synchronized根本锁不住这个代码块,这时候就必须要用到分布式锁

/**
 * @author 海杰
 * @date 2021/8/9 10:04
 */
@RestController
public class Test1Controller {
    @Autowired
    private RedisUtil redisUtil;

    private final String lock_key="wrangler_001";
    @GetMapping("/test2/{key}")
    public String test1(@PathVariable String key) throws Exception {
        System.out.println(Thread.currentThread().getName()+"key1");
        while (redisUtil.get(key)==null){
            System.out.println(Thread.currentThread().getName() + "key2");
            if (redisUtil.setnx(lock_key+key,key)){
                //设置锁的失效时间
                System.out.println(Thread.currentThread().getName() + "key3");
                redisUtil.expire(lock_key+key,5);
                System.out.println("模拟从数据库取数据......");
                Thread.sleep(2000);
                redisUtil.set(key,key);
                redisUtil.del(lock_key+key);
            }
        }
        return redisUtil.get(key) + Thread.currentThread().getName();
    }
}

image-20210809172944672

image-20210809172900224

可以看到,当两个请求“同时”打过来,并且缓存没有数据,线程2先拿到锁,线程7自旋等待,2秒后拿到数据存入缓存并释放redis锁,线程7由于满足有缓存的条件,直接从redis拿到结束进程

posted @ 2021-08-10 09:33  海杰wrangle  阅读(111)  评论(0编辑  收藏  举报