西西软件园多重安全检测下载网站、值得信赖的软件下载站!
软件
软件
文章
搜索

首页 → 业内动态 → 业内资讯 → Redis大key热key排查:一条SCAN定位800MB的Hash

Redis大key热key排查:一条SCAN定位800MB的Hash

相关软件相关文章发表评论 来源:本站整理时间:2026/11/3 10:15:23字体大小:A-A+

作者:xiix点击:0次评论:0次标签: Redis大key Redis热key SCAN扫描 内存占用排查 redis-cli

  • 类型:数据库类大小:93M语言:中文 评分:10.0
  • 标签:
立即下载

事情的起因是一条监控告警。半夜两点,值班同事收到 Redis 实例内存使用率超过 85% 的短信,登上机器一看,used_memory 已经涨到 7.8G,而这个实例的业务数据量满打满算也就两个多 G。翻了一圈代码,最近一周没有任何新增功能上线,缓存的数据结构也没变过。那多出来的 5G 多是从哪来的?

排查了两个小时,最后落到两个东西上:一个是被当成普通缓存用、实际上存了 800MB 成员的 Hash,另一个是三个被压测脚本刷成热点的 key。前者是「设计失误」,后者是「流量异常」,但它们在监控面板上看起来几乎一模一样,都表现为内存占用突然上台阶。

Redis 内存吃紧的时候,除了扩机器,也可以先看看是不是数据结构和访问模式出了偏差;下面两家在 NVMe 和高内存配置上做得比较到位,可以先对比一下规格:liteserver(低延迟高性能云服务器,NVMe/SSD 存储),neburst(面向开发者的高性能云计算方案)

先分清:大 key 和热 key 是两回事

这两个词经常被混着说,但排查思路完全相反。大 key(bigkey)指的是单个 key 对应的 value 体积特别大,比如一个 String 塞了几百 MB,或者一个 Hash 有几十万个 field。它的问题在于:任何一次针对它的读写都要在主线程里把整个对象处理一遍,主线程被占住,其它命令全部排队,延迟直接飙上去;同时它还会在 RDB 快照或者 AOF 重写的时候把内存峰值推高一截,很容易把机器推到 OOM。

热 key(hotkey)指的是访问频率极高但体积很小的 key,比如一个首页配置、一个秒杀商品的库存、或者某个被反复读取的用户信息。它占的内存可以忽略不计,但所有请求都挤到同一个 key 上,一旦这个 key 所在的 slot 对应的是某台从节点、或者它是个会频繁过期的 key,QPS 和延迟的抖动会非常明显。

Redis大key示意图:单个超大数据块占满内存

简单说,大 key 伤的是内存和主线程,热 key 伤的是单点访问的吞吐。搞清楚是哪种问题,才知道该往哪个方向查。

第一步:用 SCAN 配合 MEMORY USAGE 把大 key 捞出来

很多人在这一步就栽了。最直觉的做法是执行 KEYS * 然后看着返回的几万个 key 一个个 MEMORY USAGE,这个操作在数据量大的时候是灾难KEYS * 是 O(N) 且会阻塞主线程,整个过程可能把实例卡住几十秒,故障直接升级。

正确的做法是用 SCAN,它用游标分批返回,不会阻塞主线程,可以一条一条慢慢算。我们当时的脚本大概是这样:

redis-cli --bigkeys

这个内置命令最省事,它会遍历所有 key 并按 size 排序,直接把最大的几个列出来,输出里会明确写出 Biggest string found so far、Biggest hash found so far 这类字样,一眼就能定位。唯一的缺点是它跑完一轮要花点时间,几百万 key 的实例上大概几十秒,期间要注意 --bigkeys 本身只读不写、不会改数据,但会消耗 CPU。

如果要更精细地控制,比如只找超过 1MB 的 key,我会自己写一个循环:

redis-cli --scan --pattern 'session:*' | while read k; do echo "$(redis-cli MEMORY USAGE "$k") $k"; done | sort -rn | head -20

注意这里必须用 --scan 而不是 KEYS *,前者是游标迭代,后者是全量阻塞。另外 MEMORY USAGE 返回的是字节数,这个值包含 key 本身、value 以及对象编码带来的额外开销,比 STRLEN 或 HLEN 看到的更接近真实占用,所以排查内存问题要以它为准。

工程师用redis-cli逐条扫描键值排查大key

我们就是靠这条命令定位到那个 800MB 的 Hash 的:MEMORY USAGE 显示 session:user:profile:v2 这个键占了 812MB,往下 HLEN 一看是 190 万个 field,而业务上这个「用户档案」只保留最近 7 天的数据,早就过期了没人清,是当初一个「先存着回头再优化」的临时决定留下的。

第二步:删大 key 千万别直接用 DEL

找到之后最直觉的操作是 DEL session:user:profile:v2,然后就出事了。Redis 的 DEL 是同步释放内存的,对于一个 800MB 的 Hash,主线程要把这个对象的内存还给操作系统,这段时间它是完全被阻塞的。我们当时在低峰期试过一次,DEL 执行了 1.4 秒,那 1.4 秒内这个实例的所有请求全部超时。

正确的做法有几种,按场景选:

UNLINK。Redis 4.0 引入的命令,先把 key 从键空间里摘掉,让它变成「待回收」对象,再由后台线程慢慢释放内存。返回几乎是瞬时的,主线程不会被卡住。这是删大 key 的首选。

UNLINK session:user:profile:v2

渐进式删除。如果是 List、Set、ZSet 这种集合类型,可以一批一批地往外取元素再删掉:

redis-cli --scan --pattern 'big:list:*' | xargs -n 1 -P 4 redis-cli UNLINK

或者用 LPOP / SPOP / ZPOPMIN 每次吐一批,再手动 DEL 空掉的键。这种做法适合要按业务节奏慢慢清理、同时又不希望内存瞬间掉下去的场景。

RENAME 再后台删。先用 RENAME 把大 key 挪到一个临时的名字上(比如 tmp:del:20261103),这一步是 O(1) 的,瞬间完成;然后在业务低峰用脚本慢慢删这个临时 key,同时监控它的 MEMORY USAGE 什么时候降下来。名字里带上日期,超过一周没删干净就报警,避免临时 key 变成新的垃圾。

第三步:热 key 怎么找

热 key 的排查思路和大 key 完全不同,它不看体积,看访问频率。Redis 4.0 之后提供了 redis-cli --hotkeys,但这个命令需要客户端有足够的采样率才有意义,官方文档的建议是至少跑满 maxmemory-samples 的采样窗口,一般要跑上几十秒到几分钟,而且它是用「随机采样 + LFU 计数」来近似统计的,低频 key 有概率被漏掉。

如果只是临时看一眼,最直接的还是 MONITOR:

redis-cli MONITOR | head -1000

MONITOR 会把所有命令都打出来,QPS 高的时候刷屏刷得非常快,但它能给你一个「哪些 key 被反复访问」的最直观印象。我们当时就是靠它看到 flash_sale:stock:10086 这个键每秒被访问两万多次的。

Redis热key场景:大量并发请求指向同一个热点节点

不过要提醒一句,MONITOR 会显著降低 Redis 的整体性能,因为它把每条命令都要格式化再往标准输出写,在高 QPS 实例上甚至可能造成雪崩。它是排查工具,不是监控工具,开了记得尽快 Ctrl+C 掉。生产环境更靠谱的做法是在 Redis 前面挂一层代理(比如 Twemproxy 或者基于 Envoy 的方案)来统计,或者直接在客户端埋点统计。

找到热 key 之后,处理思路是「打散」和「本地缓存」两条路:

一是把单个 key 拆成多个副本,比如 stock:10086:0 到 stock:10086:9,读的时候按用户 ID 或者随机数取模落到其中一个副本上,写的场景用加锁或者异步合并保证一致性。二是把几乎不变的配置类热 key 直接放到应用进程的本地缓存里,用 TTL 加主动推送失效,把这部分的读请求在客户端就消化掉。

我们后来加的监控项

这次排查结束之后,我们在 Redis 侧加了几个日常监控项,后来又靠它们抓到过两次类似的问题:

一是 used_memory 与 used_memory_rss 的比值。Redis 刚启动时这个比值很高(内存碎片),但如果稳定运行后比值持续大于 1.5,说明有大量内存被换出或者碎片化,需要考虑 maxmemory-policy 和 activedefrag。

二是 instantaneous_ops_per_sec 的分位数。我们抓了 latency doctor 和慢日志,发现大 key 的典型特征是「单条命令耗时特别长但平均耗时正常」,只看平均延迟是发现不了的。

三是 rejected_connections 和 evicted_keys。淘汰键数量突然上涨,说明内存已经打满开始清缓存了,这时候业务层可能还没报错,但缓存命中率已经在掉。

监控大屏显示内存使用率与延迟曲线飙升

还有一个容易被忽略的点:maxmemory 的设置要和 maxmemory-policy 一起看。如果用 allkeys-lru,大 key 也会被作为淘汰候选,反而可能把业务数据连带清掉;如果用 noeviction,内存满了就直接返回错误拒绝写入,这个行为对缓存场景是合适的宁可报错也不要悄悄丢数据,具体还得按业务对丢失的容忍度来定。

回过头看这次故障,最初那 5G 多内存其实一直都在,只是没人看 MEMORY USAGE 这个维度的数据,监控上只看了总内存曲线,觉得「涨得慢就是正常的」。等到曲线拉陡的那天才开始查,中间空了一整天。大 key 这种问题说到底不是性能问题而是设计问题:数据结构选型的时候先想清楚一个 key 会长多大、会不会被无限追加,比事后花两小时去捞要划算得多。

    相关评论

    阅读本文后您有什么感想? 已有人给出评价!

    • 8 喜欢喜欢
    • 3 顶顶
    • 1 难过难过
    • 5 囧囧
    • 3 围观围观
    • 2 无聊无聊

    发表评论

    昵称:
    表情: 高兴 可 汗 我不要 害羞 好 下下下 送花 屎 亲亲