缓存雪崩

概念:

如果缓存集中在一段时间内失效,或者宕机,发生大量的缓存穿透,所有的请求都落在数据库上,造成了缓存雪崩。

解决方案

1、做基础的请求参数过滤,过滤掉非法的参数

2、热点数据永不过期或者设置key过期时间随机范围,避免同一过期时间

3、 可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀

4、大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。

缓存穿透

概念:

一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

解决方案

1、返回一个空值给缓存,并设置一个有效期,在有效期内避免恶意攻击数据库

2、布隆过滤器

缓存击穿

概念:

缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

解决方案:

1、可以将热点数据设置为永远不过期;

2、使用互斥锁(mutex key),简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;

锁必须具备的特点

1、互斥: 任意时刻, 只能有一个客户端获得锁
2、不会死锁: 客户端持有锁期间崩溃, 没有主动解除锁, 能保证后续的其他客户端获得锁(加一个短暂的过期时间)
3、锁归属标识: 加锁和解锁的必须是同一个客户端, 客户端不能解掉非自己持有的锁(锁应具备标识)

SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。

$redis = new Redis();
 $redis->pconnect("127.0.0.1", 6379);
 $redis->auth("password");   // 密码验证
 $redis->select(1);  // 选择所使用的数据库, 默认有16个
 $lock_key = 'LOCK_KEY;
 $is_lock = $redis->exists($lock_key);
  if(!$is_lock) { // 如果没有锁
             $redis->set($lock_key, 1, 'nx', 'ex', 4); // 加锁,持续4秒
             try{
                      //业务逻辑
             }catch(Exception $e){
                    $redis->del($lock_key);//发生异常也解锁
             }
             $redis->del($lock_key);
             return ['code' => '0000', 'msg' => '领取成功'];
 }else{
             return ['code'=>'1005','msg'=>'请稍后重试'];
 }