Redis 大 key 怎么排查和删除?内存碎片率 1.8、一个 hash 占 2.41GB 的处理记录

🔑 关键词:Redis大key,Redis内存碎片,UNLINK,MEMORY USAGE,activedefrag

📖 摘要:从一次 16GB Redis 6.2 主从实例告警切入,区分大 key 和内存碎片,给出 --bigkeys、--memkeys、MEMORY USAGE、UNLINK、分批 HDEL、lazyfree 与 activedefrag 的具体参数和踩坑。

先别急着开 activedefrag,我当时也差点这么干

上周二凌晨 1:47,监控群连响三次:Redis 主实例 used_memory 12.3GB,maxmemory 12GB,mem_fragmentation_ratio 1.79,客户端 P99 从 8ms 飙到 340ms。 我们那套是 Redis 6.2.7,三主三从,主实例 16GB,业务是商品详情缓存,QPS 峰值 2.8 万。 第一反应是内存碎片,差点直接 CONFIG SET activedefrag yes 然后睡觉。 后来发现 INFO memory 里 used_memory 本身就 12.1GB,allocator_frag_ratio 只有 1.08。 这说明大头不是 allocator 碎片,而是数据真的胖;mem_fragmentation_ratio 1.79 很吓人,但它等于 used_memory_rss/used_memory,RSS 里还混着进程共享库、COW 页和 jemalloc 保留的 arena。 只看这一个指标就开 defrag,属于把体温计当药吃。

图片

大 key 的判定,别背一个绝对值

网上很多文章写 String 超过 10KB 就是大 key,这话只能算经验线。 阿里云 Redis 开发规范里提到 String 大于 10KB、集合元素大于 5000 或总大小大于 1MB 要关注,腾讯云也有类似口径,但 Redis 官方并没有一条超过多少字节必须删的红线。 我的独立看法是:大 key 的风险 = 删除耗时 × 复制放大 × 访问频率。 一个 800KB 的 String 如果每秒 GET 3 万次,比一个 20MB 但 10 分钟才碰一次的 hash 更危险;一个 5 万元素的 zset 如果每次 ZRANGE 全量拉,也会把从库复制缓冲区顶爆。 那次我们抓到的 key 叫 item:detail:hash:881742,类型 hash,HLEN 182 万,MEMORY USAGE key SAMPLES 100 返回 2.41GB,平均 field 约 1.3KB。 它一个 key 占了实例 19% 的内存,但 --bigkeys 第一次只报了另一个 1.1GB 的 list,因为抽样扫描有概率漏。

图片

我是怎么在 40 分钟内定位并安全删掉的

步骤一,先看总量和碎片:INFO memory 重点看 used_memory、used_memory_rss、maxmemory、mem_fragmentation_ratio、allocator_frag_ratio、allocator_rss_ratio。 如果 allocator_frag_ratio > 1.4,才优先怀疑碎片;如果 used_memory 贴近 maxmemory,先找大 key。 步骤二,低峰扫描:redis-cli -h 10.0.3.12 -p 6379 --bigkeys -i 0.1,-i 0.1 表示每 100 条 SCAN 休息 0.1 秒,别在 QPS 高峰跑。 想要更准用 --memkeys,但 6.2 上它按采样估算,仍会漏。 步骤三,对可疑 key:TYPE、HLEN、MEMORY USAGE key SAMPLES 100、OBJECT ENCODING、TTL。 182 万 field 的 hash 编码一定是 hashtable,不是 ziplist,Redis 7.0 之后小对象才常叫 listpack。 步骤四,删除不要 DEL。 DEL 会同步释放 2.4GB,主线程能卡 1.5 秒;用 UNLINK item:detail:hash:881742,它把释放丢给 bio 线程。 但如果你不能直接删整个 key,就 HSCAN 游标分批 HDEL,每批 500 到 1000,批间 sleep 0.05,别写 while true 不休息。 list 用 LTRIM,set 用 SPOP/SREM,zset 用 ZREMRANGEBYRANK,每次删 1000 以内。 那晚我们先用 UNLINK 干掉 2.41GB 的 hash,再 CONFIG SET lazyfree-lazy-user-del yes,然后观察 used_memory 从 12.3GB 降到 9.8GB,P99 回到 11ms。

图片

碎片、lazyfree、activedefrag 的真实参数和坑

如果你确认是碎片,Redis 6.2 可以开 activedefrag yes,配套参数:active-defrag-ignore-bytes 100mb,active-defrag-threshold-lower 10,active-defrag-threshold-upper 100,active-defrag-cycle-min 5,active-defrag-cycle-max 75。 意思是在碎片浪费超过 100MB 且碎片率 10% 以上才动手,CPU 占用最低 5%、最高 75%。 我们另一套 Redis 7.0.11 开了之后,CPU 从 18% 升到 34%,但 mem_fragmentation_ratio 从 1.63 降到 1.21,花了 26 分钟。 别在从库先开,主库碎片整理会写内存页,从库通过复制也会受影响。 lazyfree 四个开关:lazyfree-lazy-eviction yes、lazyfree-lazy-expire yes、lazyfree-lazy-server-del yes、lazyfree-lazy-user-del yes,前三个建议开,最后一个看你是否大量用 DEL 删大 key。 注意 UNLINK 是显式异步,不受 lazyfree-lazy-user-del 控制;DEL 才受。 还有一个坑:大 key 删除后 used_memory 降了,used_memory_rss 不一定马上降,jemalloc 可能把页留在 arena 里,过一阵或重启才还操作系统。

图片

最后一句不太像总结的总结

我不建议你把 Redis 内存高直接等同于加内存。 那次如果从 16GB 升到 32GB,2.41GB 的 hash 还在,碎片率还是会涨,复制缓冲区还是会爆,下一次大促照样出事。 先看 INFO memory,再扫大 key,再决定是 UNLINK、分批删、lazyfree 还是 activedefrag。 顺序反了,钱花了,故障还在。 要查命令细节,去 Redis 官方文档搜 MEMORY USAGE、UNLINK、activedefrag;要查大 key 口径,去看云厂商开发规范,别背绝对值。

图片