1.前言
Redis的所有数据结构都是可以设置过期时间的,时间一到就可以自动删除。那么,redis内部是如何知道哪些key会删除呢?
又如果同一时间有太多的key过期,redis是单线程的,会不会来不及删除这些key?
2.带有过期时间的key保存在哪里?
redis会将每个设置了过期时间的key放入到一个独立的字典中,之后会定时遍历这个字典来删除到期的key。
除了定时遍历外,还会使用惰性策略来删除过期的key,所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。定时删除是集中处理,惰性删除是零散处理。
这个字典如何进行删除呢?
每次请求都会先触发检查key是否过期的函数,expireIfNeeded(db,key),判断过期会将过期字典中的key删除
3.定时扫描策略
Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。
- 从过期字典中随机20个key
- 删除这20个key中已经过期的key
- 如果过期的key比率超过1/4,那就重复步骤1
同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。
如果,同一时间大部分的key都过期了,结果是怎样呢?
Redis 会持续扫描过期字典 (循环多次),直到过期字典中过期的 key 变得稀疏,才会停止 (循环次数明显下降)。这就会导致线上读写请求出现明显的卡顿现象。导致这种卡顿的另外一种原因是内存管理器需要频繁回收内存页,这也会产生一定的 CPU 消耗。
当客户端请求到来时,服务器如果正好进入过期扫描状态,客户端的请求将会等待至少 25ms 后才会进行处理,如果客户端将redis连接超时时间设置的比较短,比如 10ms,那么就会出现大量的链接因为超时而关闭,业务端就会出现很多异常。
而且这时你还无法从 Redis 的 slowlog 中看到慢查询记录,因为慢查询指的是逻辑处理过程慢,不包含等待时间。
解决方案:如果有大批量的 key 过期,要给过期时间设置一个随机范围,而不宜全部在同一时间过期,分散过期处理的压力。
# 在目标过期时间上增加一天的随机时间
redis.expire_at(key, random.randint(86400) + expire_ts)
4. 从库的过期策略
从库不会进行过期扫描,因此从库对过期的处理是被动的。主库在key到期时,会在AOF(Append Only File)文件中增加一条del指令,同步到所有的从库,从库通过执行这条del指令来删除过期的key。
因为指令同步是异步进行的,所以主库过期的key的del指令没有及时同步到从库的话,会出现主从数据的不一致,主库没有的数据在从库里还存在,比如上一节的集群环境分布式锁的算法漏洞就是因为这个同步延迟产生的。
网友评论