TTL 到了,谁来删
设了 30 分钟 TTL 的 key,到期那一刻真的被删了吗?Redis 的答案是:不一定,而且不立即。给每个 key 配一个精确的定时器到点即删(定时删除)听起来最负责,但成千上万个到期事件会霸占单线程,得不偿失;完全不管、读到时再删(惰性删除)又会让再也不被访问的过期 key 永久霸占内存。Redis 的方案是两班倒:
惰性删除值班处理「被读到的」:每次访问 key 先查过期时间,过期就删掉并返回空——这次访问还顺手贡献了一次穿透风格的回源。定期删除值班处理「没人读的」:后台任务每 100ms 醒一次,从设置了 TTL 的 key 里随机抽样检查,过期比例超过四分之一就继续抽,并守住 25ms 的时间预算,单线程不能被清理工作拖太久。两班倒保证了内存不会无限膨胀,也保证了清理不会拖垮服务——至于从库,还有个特殊设定:从库不主动删过期 key,读到过期 key 返回空但自己不删,删除由主库的 DEL 同步过来。这保证了主从删除动作的一致性,代价是主从延迟窗口内从库可能多返回几次空。
主从复制:两段式同步
主从架构(读写分离、哨兵、Cluster 副本)的地基是复制。从库第一次连接主库时,数据得先「抄一遍家底」——全量同步:主库执行 bgsave 生成 RDB 快照发给从库,从库清空自己、载入 RDB;快照生成到传输完成期间主库新增的写命令,会先积压在复制缓冲区,RDB 载入完成后补发给从库。此后进入常态:命令传播,主库把每条写命令实时推给从库,从库记账记到一个 offset 上。
首次连接: bgsave 生成RDB ──传输──> 从库载入 ──> 补发缓冲区命令
断线重连: 从库上报 offset ──在 backlog 内──> 只补发缺失段(增量)
└─被覆盖─> 退化成全量同步(代价大)网络抖动断线重连是常态。主库在内存里维护一个 repl_backlog 环形缓冲区(默认 1MB,太小),从库带着 offset 回来,缺失的命令还在 backlog 里就只补发缺失段——增量同步,毫秒级恢复;断线太久 offset 被环形覆盖了,对不起,退化成全量同步,又一次 bgsave 加 RDB 传输。这就是为什么写流量大的实例要把 repl-backlog-size 调大(写 QPS × 预期最长断线秒数),几十 MB 的内存换掉一次全量同步风暴,很划算。
复制与缓存业务的相遇
这两套机制和前面的缓存话题处处呼应:主从延迟让「读从库」的回源可能读到旧值(一致性篇的延迟双删就是冲它去的);从库读过期 key 返回空,让「主库还在、从库已过期」的瞬间多出一次空值路径;而全量同步的 RDB 传输,正是大 Key 最拖速度的环节(大 Key 篇的引信之一)。机制背后的代价,要在业务层提前安排。
复制解决了「数据多副本」,但主库挂了谁来切从库上位?靠人半夜爬起来切?下一讲的主角是哨兵——主从架构的自动值班员。
评论 (0)