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