redis-cli --bigkeys 可快速定位大Key但存在采样偏差,仅扫描默认DB0、每类型只记最大值,易漏中型大Key;需逐库执行并调低采样间隔(-i 0.01),发现超12MB String即属高危;删大Key须用UNLINK(Redis 4.0+),并关注内存碎片率>1.5时启用主动碎片整理;读取大结构必须分页(如HSCAN/LRANGE带范围);写入前应在应用层按大小或时间切片,单value≤10KB、单结构≤1万元素且≤2MB;拆分后删除需SCAN匹配前缀避免残留。

用 redis-cli --bigkeys 快速定位大Key,但别信它全量扫描的结果
Redis 本身不主动暴露哪些 Key 占内存最多,redis-cli --bigkeys 是最轻量、最常用的发现手段,但它只采样部分数据库(默认 DB 0),且对每个类型只记录“最大那个”,容易漏掉分布均匀的中型大Key(比如几百个 5MB 的 Hash)。更关键的是:它不反映网络压力——一个 1MB 的 GET 不一定卡 Redis 主线程,但会吃光网卡带宽。
- 运行时加
-i 0.01降低采样间隔,减少对线上影响(默认是 0.001 秒,太激进) - 务必在所有 DB 上分别执行:
redis-cli -n 1 --bigkeys、redis-cli -n 2 --bigkeys……别只扫 DB 0 - 结果里看到
Biggest string found 'xxx' has 12000000 bytes,这不是警告,是事故倒计时——12MB String 在千兆网卡上一次读取就要占满 100ms 带宽
删大Key必须用 UNLINK,但得确认 Redis 版本和内存碎片状态
DEL 是同步阻塞删除,遇到含 50 万成员的 Set,主线程可能卡几百毫秒;UNLINK 把释放内存的动作扔给后台线程,主线程几乎无感。但它不是银弹:Redis 4.0+ 才支持,且后台回收依赖内存碎片整理能力。
- 先检查版本:
redis-cli info server | grep redis_version,低于 4.0 必须升级或改用分批删除脚本 - 执行
UNLINK后观察INFO memory | grep mem_fragmentation_ratio,如果 > 1.5 且持续上升,说明后台线程在碎片内存里找不着连续块来释放,反而堆积延迟 - 此时要配合
CONFIG SET activedefrag yes,否则UNLINK只是把阻塞从“前台”挪到“后台积压”
读大结构必须分页或流式处理,禁止 HGETALL/LRANGE 0 -1
一个包含 80 万字段的 Hash,HGETALL 会把全部数据一次性序列化、拼包、发给客户端,不仅主线程卡住,还极易触发客户端 OOM 或 TCP 缓冲区溢出。这不是“慢”,是设计层面的拒绝服务。
- 替代方案:用
HSCAN分批拉取,每次COUNT 500,自己控制节奏;服务端压力小,客户端也容易做断点续传 - 列表/有序集合同理:
LRANGE和ZRANGE永远带明确起止索引,别用-1;真要全量导出,走SCAN+ 类型判断 + 分批提取 - 注意:Lua 脚本里也不能写
redis.call('HGETALL', KEYS[1]),脚本执行期间仍是单线程阻塞,照样卡死
写入前就该拆分,别等报警才想起“大Key治理”
把一份用户行为日志 JSON 直接塞进 SET user:123:log,看着省事,实则埋雷。真正稳定的写法是在应用层就按时间片或大小切片,比如按天分 Key:user:123:log:20260310、user:123:log:20260311,单个 Value 控制在 100KB 内。
- String 类型单 value 建议 ≤ 10KB;Hash/List/Set/ZSet 单结构元素数建议 ≤ 1 万,总内存 ≤ 2MB
- 拆分逻辑不要藏在业务代码深处,封装成工具方法,比如
safeSetLargeString(key, value, maxChunk=8192),自动分片+加后缀+设置统一 TTL - 最容易被忽略的一点:拆分后,原
DEL变成批量DEL,要用SCAN匹配前缀再删,否则残留 Key 会悄悄吃掉内存










