拓展 4:朝生暮死 —— 过期策略
Redis 所有的数据结构都可以设置过期时间,时间一到,就会自动删除。你可以想象 Redis 内部有一个死神,时刻盯着所有设置了过期时间的 key,寿命一到就会立即收割。
你还可以进一步站在死神的角度思考,会不会因为同一时间太多的 key 过期,以至于忙不过来。同时因为 Redis 是单线程的,收割的时间也会占用线程的处理时间,如果收割的太过于繁忙,会不会导致线上读写指令出现卡顿。
这些问题 Antirez 早就想到了,所有在过期这件事上,Redis 非常小心。
过期的 key 集合
redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。除了定时遍历之外,它还会使用惰性策略来删除过期的 key,所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。定时删除是集中处理,惰性删除是零散处理。
定时扫描策略
Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。
- 从过期字典中随机 20 个 key;
- 删除这 20 个 key 中已经过期的 key;
- 如果过期的 key 比率超过 1/4,那就重复步骤 1;
同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。
设想一个大型的 Redis 实例中所有的 key 在同一时间过期了,会出现怎样的结果?
毫无疑问,Redis 会持续扫描过期字典 (循环多次),直到过期字典中过期的 key 变得稀疏,才会停止 (循环次数明显下降)。这就会导致线上读写请求出现明显的卡顿现象。导致这种卡顿的另外一种原因是内存管理器需要频繁回收内存页,这也会产生一定的 CPU 消耗。
当客户端请求到来时,服务器如果正好进入过期扫描状态,客户端的请求将会等待至少 25ms 后才会进行处理,如果客户端将超时时间设置的比较短,比如 10ms,那么就会出现大量的链接因为超时而关闭,业务端就会出现很多异常。而且这时你还无法从 Redis 的 slowlog 中看到慢查询记录,因为慢查询指的是逻辑处理过程慢,不包含等待时间。
所以业务开发人员一定要注意过期时间,如果有大批量的 key 过期,要给过期时间设置一个随机范围,而不宜全部在同一时间过期,分散过期处理的压力。
# 在目标过期时间上增加一天的随机时间
redis.expire_at(key, random.randint(86400) + expire_ts)
在一些活动系统中,因为活动是一期一会,下一期活动举办时,前面几期的很多数据都可以丢弃了,所以需要给相关的活动数据设置一个过期时间,以减少不必要的 Redis 内存占用。如果不加注意,你可能会将过期时间设置为活动结束时间再增加一个常量的冗余时间,如果参与活动的人数太多,就会导致大量的 key 同时过期。
掌阅服务端在开发过程中就曾出现过多次因为大量 key 同时过期导致的卡顿报警现象,通过将过期时间随机化总是能很好地解决了这个问题,希望读者们今后能少犯这样的错误。
从库的过期策略
从库不会进行过期扫描,从库对过期的处理是被动的。主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key。
因为指令同步是异步进行的,所以主库过期的 key 的 del 指令没有及时同步到从库的话,会出现主从数据的不一致,主库没有的数据在从库里还存在,比如上一节的集群环境分布式锁的算法漏洞就是因为这个同步延迟产生的。

定期删除,不是文中说的定时删除,定时删除时指某个指定时间来删除,定期删除是周期性的删除,比如每隔1s执行一次serverCron方法时检查一次redisServer中每个db的字典
惰性删除:每次请求的时候判断key是否在过期字典中,在判断时间是否到了,到了则删除(只有主库会删除,从库不会删除)
1. redis 为了保证不影响使用性能,定时任务遍历设置了过期时间的 key 字典, 每次从字典中取出一定的key判断是否过期,过期进行删除。
2. 并且为了不影响主库性能,设置最大扫描时间。
3. 如果很多key 在一个时间段内的话,会增加 cpu 使用率,因此,redis 规范是尽可保持 key 的稀疏。
4. 从库的删除依靠主从同步去完成(AOF 指令)
十次扫描意思是:清除过期键的函数activeExpireCycle由serverCron间接调用,而serverCron默认每秒执行10次,所以Redis 默认会每秒进行十次过期扫描。
超过25%才回继续扫描意思是:如果遍历一个数据库时,清除的过期键的数量超过当前总数据库带过期时间的键数量的 25 %,就再对本数据库中的键进行扫描和清除
2.Redis请求处理线程是单线程的,惰性删除其实也相当于附加执行了一条删除命令,两者配合使用能让大多数请求更快返回.
3.集群模式下从节点一般不提供服务,只做高可用,由于不处理读请求不会触发惰性删除,没有主动删除策略内存使用会一直升高.
过期Key的清除,好比死神夺命
1:定期清除-按时按点,一扫一片
2:惰性清理-当活动时检查该不该死
3:同一时期,该死的太多死神会忙不过来,耽误其他神的活动,Redis世界中只允许同时出现一个神,所以,不要凑到一起死,给死亡时间加个随机数是个好方法。
查看全部 110 条回复