26-拓展 4:朝生暮死 —— 过期策略
课程
1
开篇:授人以鱼不若授人以渔 —— Redis 可以用来做什么?
学习时长: 5分21秒
2
基础:万丈高楼平地起 —— Redis 基础数据结构
上次学到
学习时长: 16分14秒
3
应用 1:千帆竞发 —— 分布式锁
学习时长: 7分47秒
4
应用 2:缓兵之计 —— 延时队列
学习时长: 8分9秒
5
应用 3:节衣缩食 —— 位图
学习时长: 8分52秒
6
应用 4:四两拨千斤 —— HyperLogLog
学习时长: 14分17秒
7
应用 5:层峦叠嶂 —— 布隆过滤器
学习时长: 17分54秒
8
应用 6:断尾求生 —— 简单限流
学习时长: 4分37秒
9
应用 7:一毛不拔 —— 漏斗限流
学习时长: 7分22秒
10
应用 8:近水楼台 —— GeoHash
学习时长: 7分52秒
11
应用 9:大海捞针 —— Scan
学习时长: 8分42秒
12
原理 1:鞭辟入里 —— 线程 IO 模型
学习时长: 4分1秒
13
原理 2:交头接耳 —— 通信协议
学习时长: 3分34秒
14
原理 3:未雨绸缪 —— 持久化
学习时长: 5分27秒
15
原理 4:雷厉风行 —— 管道
学习时长: 3分51秒
16
原理 5:同舟共济 —— 事务
学习时长: 6分36秒
17
原理 6:小道消息 —— PubSub
学习时长: 7分7秒
18
原理 7:开源节流 —— 小对象压缩
学习时长: 7分14秒
19
原理 8:有备无患 —— 主从同步
学习时长: 4分9秒
20
集群 1:李代桃僵 —— Sentinel
学习时长: 3分52秒
21
集群 2:分而治之 —— Codis
学习时长: 7分28秒
22
集群 3:众志成城 —— Cluster
学习时长: 8分38秒
23
拓展 1:耳听八方 —— Stream
学习时长: 13分40秒
24
拓展 2:无所不知 —— Info 指令
学习时长: 4分4秒
25
拓展 3:拾遗补漏 —— 再谈分布式锁
学习时长: 2分18秒
26
拓展 4:朝生暮死 —— 过期策略
学习时长: 2分21秒
27
拓展 5:优胜劣汰 —— LRU
学习时长: 4分34秒
28
拓展 6:平波缓进 —— 懒惰删除
学习时长: 2分13秒
29
拓展 7:妙手仁心 —— 优雅地使用 Jedis
学习时长: 6分35秒
30
拓展 8:居安思危 —— 保护 Redis
学习时长: 2分19秒
31
拓展 9:隔墙有耳 —— Redis 安全通信
学习时长: 6分34秒
32
拓展 10:法力无边 —— Redis Lua 脚本执行原理
学习时长: 9分24秒
33
拓展 11:短小精悍 —— 命令行工具的妙用
学习时长: 9分21秒
34
源码 1:丝分缕析 —— 探索「字符串」内部
学习时长: 5分20秒
35
源码 2:循序渐进 —— 探索「字典」内部
学习时长: 7分24秒
36
源码 3:挨肩迭背 —— 探索「压缩列表」内部
学习时长: 10分42秒
37
源码 4:风驰电掣 —— 探索「快速列表」内部
学习时长: 3分49秒
38
源码 5:凌波微步 —— 探索「跳跃列表」内部
学习时长: 9分57秒
39
源码 6:破旧立新 —— 探索「紧凑列表」内部
学习时长: 2分42秒
40
源码 7:金枝玉叶 —— 探索「基数树」内部
学习时长: 5分36秒
41
源码 8:精益求精 —— LFU vs LRU
学习时长: 8分4秒
42
源码 9:如履薄冰 —— 懒惰删除的巨大牺牲
学习时长: 9分53秒
43
源码 10:跋山涉水 —— 深入字典遍历
学习时长: 9分24秒
44
源码 11:见缝插针 —— 探索 HyperLogLog 内部
学习时长: 13分3秒
45
尾声:百尺竿头 —— 继续深造指南
学习时长: 2分32秒
juejin_logo copyCreated with Sketch.

拓展 4:朝生暮死 —— 过期策略

Redis 所有的数据结构都可以设置过期时间,时间一到,就会自动删除。你可以想象 Redis 内部有一个死神,时刻盯着所有设置了过期时间的 key,寿命一到就会立即收割。

你还可以进一步站在死神的角度思考,会不会因为同一时间太多的 key 过期,以至于忙不过来。同时因为 Redis 是单线程的,收割的时间也会占用线程的处理时间,如果收割的太过于繁忙,会不会导致线上读写指令出现卡顿。

这些问题 Antirez 早就想到了,所有在过期这件事上,Redis 非常小心。

过期的 key 集合

redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。除了定时遍历之外,它还会使用惰性策略来删除过期的 key,所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。定时删除是集中处理,惰性删除是零散处理。

定时扫描策略

Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。

  1. 从过期字典中随机 20 个 key;
  2. 删除这 20 个 key 中已经过期的 key;
  3. 如果过期的 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 指令没有及时同步到从库的话,会出现主从数据的不一致,主库没有的数据在从库里还存在,比如上一节的集群环境分布式锁的算法漏洞就是因为这个同步延迟产生的。

留言
Ctrl + Enter
全部评论(110)
BinK_1783的头像
删除
一旦选择了远方,别回头就大踏步的前进
点赞
回复
山治不是香吉的头像
删除
过期时间随机化应该也需要看场景,比如大量用户token同时过期,token是不能加随机时间的,或者至少不能以一天或一小时作为随机范围,这样还是会存在大量token同时过期的情况吧。如果有硬性的过期时间要求时,需要用其它方案。
点赞
回复
陆伯言的头像
删除
java后端
从库删除的是通过aof删除吗?我看redis设计与实现中说的是发送一条del命令。
点赞
回复
柳双六的头像
删除
@作者,这里有个勘误:"当客户端请求到来时,服务器如果正好进入过期扫描状态,客户端的请求将会等待至少 25ms 后才会进行处理"。这里应该是至多 25 ms
13
1
删除
我觉得也是
点赞
回复
yefan813的头像
删除
最后一段,从库不会过期扫描,如果主库的 del 指令没有同步到从库,那从库这个 key 就一直存在了,这样如果读是从从库读那这个数据一直读得到。
1
2
删除
主从架构的话,操作都在主库吧,从库应该只是一个备份用,客户端不直接连接。
点赞
回复
删除
读取的时候也会做惰性删除,就是读取发现key过期了,也会删掉这个key,返回nil
点赞
回复
amzexin的头像
删除
删除策略两种:
定期删除,不是文中说的定时删除,定时删除时指某个指定时间来删除,定期删除是周期性的删除,比如每隔1s执行一次serverCron方法时检查一次redisServer中每个db的字典
惰性删除:每次请求的时候判断key是否在过期字典中,在判断时间是否到了,到了则删除(只有主库会删除,从库不会删除)
4
回复
zyfsuzy的头像
删除
研发工程师 @ 百度
总结:
1. redis 为了保证不影响使用性能,定时任务遍历设置了过期时间的 key 字典, 每次从字典中取出一定的key判断是否过期,过期进行删除。
2. 并且为了不影响主库性能,设置最大扫描时间。
3. 如果很多key 在一个时间段内的话,会增加 cpu 使用率,因此,redis 规范是尽可保持 key 的稀疏。
4. 从库的删除依靠主从同步去完成(AOF 指令)
展开
1
回复
复兴的头像
删除
后端程序员
10次扫描,与超过25%才会继续扫描,两者之间是什么关系呢。
点赞
1
删除
我说一下我的看法,有错误请指正:
十次扫描意思是:清除过期键的函数activeExpireCycle由serverCron间接调用,而serverCron默认每秒执行10次,所以Redis 默认会每秒进行十次过期扫描。
超过25%才回继续扫描意思是:如果遍历一个数据库时,清除的过期键的数量超过当前总数据库带过期时间的键数量的 25 %,就再对本数据库中的键进行扫描和清除
2
回复
春天百花开的头像
删除
为什么不介绍 6种淘汰策略?
3
1
删除
后边会介绍
点赞
回复
battle_field的头像
删除
最后一段关于分布式锁问题与这个过期的联想,据我了解分布式锁需要的是独立的redis节点,就算使用哨兵在master挂掉后将slave提升为master。那这个新master也有自动过期该过期的key的能力。不知道作者是不是论证过源码或者实验过。否则就是错误引导初学者
点赞
回复
溪风爱学习的头像
删除
本书是居于redis哪个版本开始研究的?
6
回复
锦簇的头像
删除
Java开发 @ 哆啦宝
"redis 会将每个设置了过期时间的 key 放入到一个独立的字典中";这个字典是有增无减么?有减的话是什么时候减?
点赞
3
删除
会减的(删除)。每次读或者写操作的时候,如果随机取到的key已经过了过期时间,那么就会去字典中删除key对应的过期时间记录
点赞
回复
删除
每次请求都会先触发检查key是否过期的函数 expireIfNeeded(db,key),判断过期会将过期字典中的key删除
点赞
回复
查看更多回复
大蒙的头像
删除
后端开发
redis默认情况下会有16个分区,那这些分区的过期策略的细节是什么呢?
点赞
3
删除
我想问的就是什么情况下会进入下一个db去处理定时扫描
点赞
回复
删除
每次定时清理过期key时,会在全局保存一个db的索引,记录扫描处理到哪个db了,如果这次定时清理结束了,下次定时清理时,会直接去读取记录的全局的db索引,去继续执行定时清理
点赞
回复
查看更多回复
迹_Jason的头像
删除
永远学习者 @ Zeros Tech
为什么不只使用惰性策略解决删除过期的问题,而是采用双策略的方式?我觉得惰性策略已经很好了呀,难道这方面存在很大的性能消耗吗?
点赞
3
删除
1.有一些key如果一直没有读到,会导致Redis内存占比特别高
2.Redis请求处理线程是单线程的,惰性删除其实也相当于附加执行了一条删除命令,两者配合使用能让大多数请求更快返回.
3.集群模式下从节点一般不提供服务,只做高可用,由于不处理读请求不会触发惰性删除,没有主动删除策略内存使用会一直升高.
3
回复
删除
第一,第二点很有道理,第三点不对,原文里说了从节点什么过期策略都不执行,只重放aof日志或者同步快照
点赞
回复
查看更多回复
扫地僧冻冻七的头像
删除
技术架构师
每秒10次,这个10次是怎么触发的?是固定时间间隔还是事件触发的?
点赞
1
删除
约等于10次,Redis主要是两类事件控制调用的,时间事件和文件事件,通过注册时间事件触发
点赞
回复
雪飞鸿的头像
删除
.NET Core/Python/JavaScript
如果过期的 key 比率超过 1/4,那就重复步骤。这里的1/4是指所有key的1/4,还是抽出20个key的1/4?
点赞
1
删除
20个key的1/4
点赞
回复
金龟的头像
删除
惰性扫描应该也是从从库查询的时候发现的吧?这时候会去主库删除对应的key哇?
2
2
删除
同样不理解,请老钱大大辛苦分析下吧
点赞
回复
删除
这个惰性扫描不是“我们扫描(查询)”,我们查询才是用的从库。是指主库自身的机制,主库扫描自身,然后删除,然后有AOF,从库用AOF做同样的操作。
点赞
回复
司但.stan的头像
删除
遇到过期扫描时,至多等待25ms还是至少等待25ms?
1
1
删除
(作者)
至多
点赞
回复
钱同志的头像
删除
延迟阅读的好处,有些障碍都已经扫除!
过期Key的清除,好比死神夺命
1:定期清除-按时按点,一扫一片
2:惰性清理-当活动时检查该不该死
3:同一时期,该死的太多死神会忙不过来,耽误其他神的活动,Redis世界中只允许同时出现一个神,所以,不要凑到一起死,给死亡时间加个随机数是个好方法。
7
回复
 在掘金60137的头像
删除
雪崩
1
回复

查看全部 110 条回复