缓存击穿
- 热点key在某一时间过期,此时大量并发请求进来,直达DB,造成DB压力骤增。
- 解决方案:
- 使用互斥锁,同时只能有1个线程能得到锁并访问DB,其他线程等待缓存构建完,再从缓存获取数据。
- 设置热点数据永不过期,由定时任务异步加载数据,更新缓存。
缓存雪崩
- 缓存同一时间大面积失效,请求直接落到DB上,造成DB压力骤增。
- 解决方案:
- 给缓存过期时间加上一个随机值,防止key在同一时刻失效。
- 设置热点数据永不过期。
- 缓存限流(互斥锁)、降级(启用备用缓存)。
缓存穿透
- 大量请求访问DB中不存在的key,请求直接落到DB上,且无法写入缓存,造成缓存起不到作用。
- 解决方案:
- 接口校验,直接对无效key过滤。
- 将这些无效key写入缓存,设置其值为null,设置很短的过期时间。
- 布隆过滤器。布隆过滤器由一个二进制数组bitMap和若干哈希函数组成,向容器中添加数据时,将key通过哈希函数映射到bitMap,将对应的值置为1。判断一个key是否存在,只需要判断这个key通过哈希函数映射到的各个值是否全部为1,只要存在某个值不为1,则它一定不在容器的键空间中。布隆过滤器只能判断一个key一定不存在,但无法保证一定存在。
双写不一致
- 在同一时刻,Redis和DB中的数据不一致。
- 解决方案:
- 如果不要求强一致性,只要保证最终一致性,可以给key设置较短的过期时间。
- 先删缓存,再更新数据库。这样可能产生的问题:线程1删除缓存,线程2读取数据库并写入缓存,线程1更新数据库,此时缓存中缓存了脏数据。因此,需要延时双删等策略防止脏数据。
- 先更新数据库,再删除缓存。这里可能由于删缓存失败导致产生脏数据。解决方法:使用消息队列不断重试确保删除缓存成功;订阅DB的binlog,尝试删除缓存,如果失败放入消息队列不断重试。
缓存无底洞
- 分布式缓存数据量特别大时,批量获取多个key由于分布在多个不同实例,需要多次网络IO,性能下降。
- 解决方案:
- 串行
MGET
,将N个key拆解为N次GET
操作。实现简单,性能较低。
- 串行IO,计算每个key对应的节点,分别访问对应的节点。实现简单,性能较低。
- 并行IO,将串行IO的网络请求改为多线程执行,实现复杂,性能较好。
- hash-tag,强制将多个key分配到一个节点上,性能最高,但维护成本高,容易出现数据倾斜。