他的回复:
先给结论:主从架构下过期键的删除权完全在主库——主库把过期 key 的删除合成一条 DEL 命令放进复制流,从库跟着回放这条 DEL,不需要单独设计「过期同步协议」(3.2 以上从库读请求还会按逻辑时钟提前判过期,但物理删除仍等主库 DEL);「大量 key 同时过期」的优化重点不在 Redis 单机能力,而在过期时间打散和回源流量的控制。一、主从架构下过期键怎么同步Redis 的过期删除本来就是两套机制叠加(你提到的惰性+定期):惰性删除:访问 key 时判断是否已过期,过期就删。缺点是不访问就永远不删,内存会一直赖着;定期删除(active expire cycle):后台周期任务按内部 hz 设定的频率抽样扫描过期字典,删掉抽样命中的过期 key,每轮有时间预算,不会把 CPU 吃光。主从的规则只有一条:过期处理集中在主库。redis.io/commands/expire 的「Appendix: Redis expires」一节原文:"...when a key expires, a DEL operation is synthesized in both the AOF file and gains all the attached replicas nodes. This way the expiration process is centralized in the master instance, and there is no chance of consistency errors."也就是说主库无论走惰性还是定期删掉一个过期 key,都会往复制流里合成一条 DEL,从库照常回放。同一节还明确了从库行为:"...the replicas connected to a master will not expire keys independently (but will wait for the DEL coming from the master), they'll still take the full state of the expires existing in the dataset, so when a replica is elected to master it will be able to expire the keys independently, fully acting as a master."这段话有两层信息:从库不独立过期:只等主库的 DEL。推论是主库不可用期间,从库不会因为「key 到点了」自己去删,过期 key 的内存也不释放——内存回收这件事依赖主库活着;从库保留完整过期状态:全量同步时过期字典是随数据集一起传的(原文 "they'll still take the full state of the expires existing in the dataset"),所以从库晋升成主库后立刻能独立过期,故障切换后行为连续,不会出现「新主库不会过期」的问题。版本差异:Redis 3.2 之前,从库完全不给 key 判过期,主库 DEL 到达之前,从库可能把「自然过期但 DEL 还没到」的 key 当活 key 返回。3.2 的 release notes 记了这个改进:"Better consistency behavior between masters and slaves for expired keys. The slaves are only able to logically consider a key expired even before receiving the DEL command from the master." 3.2+ 的从库会提前按逻辑判断把这种 key 视为不存在,读请求拿不到过期数据,但物理删除仍然等主库 DEL。另外全量重同步时从库直接以主库数据集为准,主库早已删掉的 key 不会随快照回来。二、大量 Key 同时过期怎么优化先做个量级判断:Redis 自身的过期清理是抽样、分周期、限时的,扛得住「同时到点」这个输入。redis.conf 里 active-expire-effort 的官方注释写明默认策略是「内存中残留的过期键不超过 10%」,effort 默认 1、可调到 10,属于内存/CPU/延迟的权衡:CONFIG GET active-expire-effort CONFIG SET active-expire-effort 5 # 加大清理力度,CPU 会上去,按实际负载调按源码 expire.c 的常量,主动过期周期默认最多占用约 25% 的 CPU 时间(hz=10 时折算约 25ms/轮)。所以大量同时过期时,Redis 单机通常不会「挂」,真正会被打穿的是两点:内存回收没跟上时的内存膨胀,和同一时刻回源 DB 的流量洪峰(雪崩)。优化分两条线走。1. 过期时间加随机偏移——最基础的一招,把整点过期摊平。给 TTL 加几分钟内抖动:SET cache:user:10086 {userJson} EX 3600 # 代码里:ttl = 3600 + random(0, 300) # 0~300s 随机2. 逻辑过期——热点 key 干脆不设物理 TTL,value 里带一个逻辑过期时间戳,读时发现逻辑过期就异步回源重建,物理上永不过期,从根上消除「同一时刻到点」。代价是要自己控制重建并发:# value 结构示例 {"data": {...}, "expire_at": 1760000000}3. 互斥重建,防击穿——过期之后大量并发同时回源,只放一个线程去查 DB,其余短暂等待或先返回旧值。用 SET NX EX 抢重建锁:SET lock:cache:user:10086 1 NX EX 10 # 抢到的去查 DB 重建,没抢到的稍后重试或降级4. 大 key 异步释放——删除本身是同步的,value 很大时删一个 key 会阻塞主线程。redis.conf 的 LAZY FREEING 一节给了分场景的开关:CONFIG SET lazyfree-lazy-expire yes # 过期删除走异步释放 CONFIG SET lazyfree-lazy-user-del yes # 用户 DEL 表现得像 UNLINK不改配置的话,业务侧直接用 UNLINK 代替 DEL 即可,效果等同异步。注意别把 lazyfree-lazy-server-del 当 DEL 的开关——它管的是另一种场景:SET/RENAME 等命令覆盖旧值时产生的顺带删除。5. 监控与兜底:INFO stats # 看 expired_keys / evicted_keys / keyspace_misses 的斜率需要感知过期事件的场景(比如本地缓存联动失效)可以开 keyspace 通知:CONFIG SET notify-keyspace-events KEA同时给下游 DB 限流、熔断、加本地缓存兜底——雪崩的伤害最终落在 DB 上,Redis 侧只是触发器。三、定位命令速查看过期规模与速率:INFO stats 里 expired_keys 的累计增量,除以时间就是每秒过期量;扫 key(生产别用 KEYS,阻塞单线程):SCAN 0 MATCH cache:* COUNT 100;查单 key 剩余寿命:TTL cache:user:10086(-1 永不过期,-2 已不存在);看主从角色与延迟:INFO replication;大 key 普查:redis-cli --bigkeys,配合 MEMORY USAGE key 看单个 value 体积。收个尾:同步机制上信任主库的 DEL 广播即可,3.2 以上连从库的读窗口都补齐了;同时过期的治理在业务侧的 TTL 打散和重建并发控制,Redis 侧提供的是 active-expire-effort、UNLINK 这类旋钮。