Redis 大 key 的一次线上事故
上个月某天下午,监控突然报警:从库延迟飙到 40 秒。业务侧表现是「读到的数据偶尔是旧的」,持续了大概二十分钟。
现象
- 主库 CPU 正常,QPS 正常
- 从库
master_link_status:up,但master_repl_offset落后越来越多 - 网络没有明显抖动
看起来像是主库在往从库刷大量数据,但对外的 QPS 又没涨。
找到大 key
redis-cli --bigkeys
扫出来一个 hash,user:profile:cache:all,单个 key 里有 47 万个 field,占内存快 2GB。
代码里是这么写的:启动时把全量用户资料加载进一个 hash,方便「一次查询」。随着用户增长,这个 key 越来越大,而某次发布触发了全量重建——一次写入 2GB 的 key,主库得慢慢同步给从库,直接堵住了复制流。
为什么平时没事
平时只做增量更新,每次写几十个 field,复制没问题。只有重启/重建时才全量写一次,所以问题藏了很久,直到那天滚动发布把它暴露出来。
怎么处理的
- 先把重建逻辑停掉,改成从旧 key 逐批迁移
- 按用户 id 前缀拆成 64 个 hash:
user:profile:cache:{shard} - 加了写入保护:单次批量写超过 1000 个 field 就分批 + sleep
教训
- 大 key 的危害主要在复制和删除,不在读写本身。平时完全无感
- 凡是「全量加载进一个 key」的设计,都要考虑数据量增长后会怎样
--bigkeys应该定期跑,成本很低UNLINK比DEL好,大 key 删除会阻塞主线程
顺带说一句,--bigkeys 是 SCAN 实现的,对线上影响可控,但别在高峰期跑。