Redis 缓存击穿、穿透、雪崩
1.缓存击穿
什么是缓存击穿
缓存击穿和缓存穿透从名词上可能很难区分开来,它们的区别是:穿透表示底层数据库没有数据且缓存内也没有数据,击穿表示底层数据库有数据而缓存内没有数据。
当热点数据key从缓存内失效时,大量访问同时请求这个数据,就会将查询下沉到数据库层,此时数据库层的负载压力会骤增,我们称这种现象为"缓存击穿"。
解决方案:
- 延长热点key的过期时间或者设置永不过期,如排行榜,首页等一定会有高并发的接口;
- 利用互斥锁保证同一时刻只有一个客户端可以查询底层数据库的这个数据,一旦查到数据就缓存至Redis内,避免其他大量请求同时穿过Redis访问底层数据库;
import redis
from time import sleep
conn = redis.Redis('127.0.0.1')
def get(key, key_time, mutex_time):
value = conn.get(key)
print(value)
if value == None:
key_mutex = key + "_nx"
# 设置超时,防止del操作失败的时候,下次缓存过期一直不能load db
# 待优化 使用lua保证原子性 避免死锁 也就是说下列代码有死锁风险
if conn.setnx(key_mutex, 1) == 1:
conn.expire(key_mutex, mutex_time)
sleep(5)
value = "我是查询数据"
conn.set(key, value, key_time)
conn.delete(key_mutex)
return value
else:
# 这个时候代表同时候的其他线程已经loaddb并回设到缓存了,这时候重试获取缓存值即可
# TODO 这里需要补充超时时间 时间超过某个限制 raise 异常
sleep(0.1)
get(key, key_time, mutex_time) # 重试
else:
return value
2.缓存穿透
什么是穿透
当查询Redis中没有的数据时,该查询会下沉到数据库层,同时数据库层也没有该数据,当这种情况大量出现或被恶意攻击时,接口的访问全部透过Redis访问数据库,而数据库中也没有这些数据,我们称这种现象为"缓存穿透"。
缓存穿透会穿透Redis的保护,提升底层数据库的负载压力,同时这类穿透查询没有数据返回也造成了网络和计算资源的浪费。
解决方案
- 在接口访问层对用户做校验,如接口传参、登陆状态、n秒内访问接口的次数;
- 利用布隆过滤器,将数据库层有的数据key存储在位数组中,以判断访问的key在底层数据库中是否存在;
PS:布隆过滤器有误判率,虽然不能完全避免数据穿透的现象,但已经可以将99.99%的穿透查询给屏蔽在Redis层了,极大的降低了底层数据库的压力,减少了资源浪费。
3.缓存雪崩
什么是缓存雪崩
缓存雪崩是缓存击穿的"大面积"版,缓存击穿是数据库缓存到Redis内的热点数据失效导致大量并发查询穿过redis直接击打到底层数据库,而缓存雪崩是指Redis中大量的key几乎同时过期,
然后大量并发查询穿过redis击打到底层数据库上,此时数据库层的负载压力会骤增,我们称这种现象为"缓存雪崩"。事实上缓存雪崩相比于缓存击穿更容易发生,对于大多数公司来讲,
同时超大并发量访问同一个过时key的场景的确太少见了,而大量key同时过期,大量用户访问这些key的几率相比缓存击穿来说明显更大。
解决方案:
- 在可接受的时间范围内随机设置key的过期时间,分散key的过期时间,以防止大量的key在同一时刻过期;(PS:给key加日期,使用定时任务将对应数据刷进redis缓存)
- 对于一定要在固定时间让key失效的场景(例如每日12点准时更新所有最新排名),可以在固定的失效时间时在接口服务端设置随机延时,将请求的时间打散,让一部分查询先将数据缓存起来;
- 延长热点key的过期时间或者设置永不过期,这一点和缓存击穿中的方案一样;