ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Redis大Key监控与自动化处理:从原理到实战的全套方案

Redis大Key监控与自动化处理:从原理到实战的全套方案

监控大Key这种事,平时不做感觉没啥,一旦线上真出了事故,基本都是深夜拉群、疯狂刷新告警面板、一堆人围着Redis实例干瞪眼的场景。我之前处理过好几轮类似的问题,从最早手工遍历key、对着bigkeys结果一脸懵,到后来把扫描、分析、告警、处理串成一条自动化链路,中间踩的坑不少,但回头来看套路其实挺固定。这篇就把监控和预警Redis大Key的思路、工具原理、阈值怎么定、自动化处理方案怎么落地,一次性讲清楚,适合正在被大Key折磨,或者想提前建立防护体系的开发和运维同学参考。

1. 为什么大Key值得单独建一套监控防线:先算清它的破坏力

很多人第一次听说大Key,第一反应是“Redis不就是为了存大数据的吗?大一点怎么了”。这个想法害人不浅。Redis虽然以快著称,但它的快是有前提的——所有命令基本都在单线程里跑,任何一条命令耗时太长,后面的命令全得排队。大Key恰恰是让单条命令变慢的头号因素。

1.1 一次典型的由大Key引发的线上故障复盘

某购物平台的订单服务在晚高峰突然出现大面积超时,耗时从平时3毫秒涨到接近200毫秒,网关层快速熔断,紧接着一堆上游服务跟着报错。排查了一圈数据库和容器,最后定位在Redis集群中的某一个分片:那个分片的CPU跑满,响应时间拉满,INFO里面blocked_clients和slowlog指标非常异常。

顺着慢日志查到源头,是该平台把某一个店铺的全部订单ID塞进了同一个list结构,这个list存了上百万个元素。每次做商品维度聚合时,客户端会LRANGE拉取这个list的全部内容,一条命令要在网络上传输几十MB的数据,Redis内部还需要在内存里把这些元素逐个序列化。一次高峰期的全量拉取,直接把那个分片打瘫,连带影响分片上其他所有租户的数据访问。

这类事故的共性特别明显:大Key的破坏从来不是一个人遭殃,而是同实例、同分片上所有业务的命一起搭上。所以给大Key建立监控和预警体系,本质上不是给某个业务擦屁股,而是在保护整个Redis集群的稳定性基线。

1.2 单线程模型下大Key的四个扩散路径

理解了Redis的单线程模型之后,大Key的危害路径就很清楚了,我平时总结成四条线:

  • 阻塞路径:对大集合执行KEYS、LRANGE、HGETALL、SMEMBERS这类需要全量遍历或全量序列化的命令时,Redis会一直占用CPU执行,期间所有其他命令排队等待。慢日志里最常见的就是这类命令。
  • 网络路径:大Key的value在内存里可能只有几MB,但序列化后通过网络传输给客户端同样是几MB。高并发访问时网卡流量飞速上涨,带宽成为瓶颈,小命令也跟着遭殃。
  • 持久化路径:RDB在做快照时,需要对所有key进行序列化写入磁盘,大Key会让fork子进程和写盘时间明显变长。AOF重写同理。主从复制时大Key的全量同步数据量巨大,还会拉长主从断开的追平时间。
  • 集群倾斜路径:在Cluster模式下,大Key只能落在某个分片,大量访问集中在这个分片,其他分片空闲,形成“一核有难,八核围观”的局面。数据倾斜和访问倾斜同时叠加,问题会被放大数倍。

1.3 判定标准:大Key不只是“大”,还要看具体数据类型

业内对“大Key”并没有一个统一数值定义,因为不同业务场景的容忍度完全不一样。但有一个经验基线大家可以参考:单个key的value大小超过10KB,或者集合类结构(hash、list、set、zset)元素数量超过5000个,就值得纳入重点关注名单。注意这只是一个起跑线,后面阈值怎么细化,我会在第三节详细讲。

总之,大Key是所有Redis稳定性问题里“投入产出比”极高的一种防护对象——你只需要花一点成本去做巡检,就能避开一类破坏力极强、事故复现率极高的故障。这也是我坚持把监控预警做成自动化而不是靠人肉巡检的核心原因。

2. 排查大Key的两种主流手段:主动扫描与离线分析,各有各的坑

工欲善其事,必先利其器。大Key监控的第一步是把“家底”摸清楚——实例里到底存了哪些大Key、有多大、长什么样。现在市面上最常用的手段可以分成两大类:运行时主动扫描和离线文件分析。

2.1 redis-cli --bigkeys:最方便,但只能做粗筛

大多数人和大Key的第一次亲密接触,都是从redis-cli --bigkeys开始的。它的原理并不复杂:使用SCAN命令增量遍历整个keyspace,对每种数据类型单独维护一个当前最大的key,最后汇总输出。

redis-cli -h 192.0.2.10 -p 6379 --bigkeys -i 0.1

-i 0.1表示每遍历100个key就暂停100毫秒,目的是给Redis留出喘息时间,降低对线上服务的影响。这个命令的好处是零成本、开箱即用;坏处也很明显:它只是“抽样性”地找出每种类型里最大的那几个,并没有精确计算每个key的内存占用,对恰好小于最大值的其他大Key会漏报。另外它遍历的是全量key,实例很大的时候要跑很久,期间即使加了-i也会有性能磨损。

所以我一直把它定位为“应急快速摸底工具”,适合出事故时第一时间抓一个大概,不适合作为常态化监控底座。

2.2 运行时精确测量:MEMORY USAGE命令的正确打开方式

要精确知道一个key到底占多少内存,不能靠猜,得用MEMORY USAGE。这条命令会实际序列化并计算key及其value的内存成本,结果相当精确。它的代价是计算过程中需要在内存里实例化整个value,如果key非常大,这条命令本身就有可能成为慢命令。

redis-cli -h 192.0.2.10 -p 6379 MEMORY USAGE product:detail:100023 --bytes

实操时我一般不会对全量key跑MEMORY USAGE,而是先用SCAN+ 抽样逻辑粗略扫一遍,筛选出可疑的大集合或大字符串,再对这些可疑目标精确测量。这样既避免了在大Key上执行重命令,又把资源开销控制在了可接受范围。

2.3 在合适的数据模式下做主从/副本侧扫描

如果主实例的压力实在不容许做任何扫描动作,还有一个常用技巧:对只读副本执行扫描分析。副本上的数据和主实例一致,但扫描带来的CPU开销落在副本上,不影响主实例服务。缺点是一部分架构中副本的数据可能存在秒级延迟,扫描结果只能作为参考。

2.4 RDB离线分析:拿到全量规律,但注意时效性

如果想拿到全实例大Key分布的全貌——不只是top N,而是所有超过阈值的key清单——那就要靠RDB文件分析。流程大体是:定期从Redis主节点通过BGSAVE生成RDB快照,把RDB文件拉到分析机上,用解析工具读取里面的所有key和value大小。

# 在分析机上拉取RDB文件 redis-cli -h 192.0.2.10 -p 6379 --rdb /data/backup/dump.rdb # RDB文件解析后输出按内存占用排序的Top大Key

RDB方案的优点非常突出:对线上实例完全没有运行时扫描压力,可以拿到全量精准数据;生成的报告可以保留历史版本,方便做趋势对比,看大Key是变大了还是变小了。缺点则是分析有延迟——从生成RDB到拉取、解析、产出报告,最快也要几分钟,无法做到实时告警。

我实际推进监控体系时,思路是把这两种手段配合起来:线上用低频SCAN做准实时巡检,达不到精确但足够预警;每个周期再用RDB分析做一次全量体检兜底,补上漏网之鱼,同时给告警处置提供精确的key清单。

3. 预警阈值怎么定:不拍脑袋、分类型、结合业务和规格综合落地

很多团队做监控失败,不是扫描工具不行,而是阈值定得太随意——要么默认10MB,结果业务里1MB的key一堆也没人管,告警天天响被无视;要么把阈值设成512KB,结果全是大Key告警,看得人麻木。真正的阈值设计应当分类型、分层级、结合实例规格一起算。

3.1 不同数据类型的多维判定维度

大Key的“大”,不能只看单个维度。不同的数据结构内部存储形态天差地别,判定维度很不一样,我列了一张日常使用的参考表:

数据类型判定维度建议关注基线高危基线
Stringvalue字节长度> 10 KB> 1 MB
Hash字段数量 / 整体内存> 5000个字段 或 > 1 MB> 10000个字段
List元素数量 / 整体内存> 5000个元素 或 > 1 MB> 50000个元素
Set成员数量 / 整体内存> 5000个成员 或 > 1 MB> 10000个成员
ZSet成员数量 / 整体内存> 5000个成员 或 > 1 MB> 20000个成员

当然这些数字不能死记硬背。比如一个String如果只有30KB,但每秒被读取一万次,它的网络序列化成本就足以把它变成实际意义上的大Key;反过来,一个hash有8000个字段,但平时只会HGET单个字段,几乎不做HGETALL,风险相对可控。判定维度应该从“会不会被整体操作”出发,而不仅仅是看体量。

3.2 按实例规格做性能拐点估算

我比较推荐的做法是,把阈值和实例的带宽、CPU规格挂钩。原理是预估“命中最坏情况”。拿一个string类型的大Key举例:假设value大小是1MB,在10Gbps带宽的内网环境下,光一个客户端拉取,就需要约0.8毫秒的网络传输时间;如果每秒被读500次,光这一个key就会产生500MB/s的带宽占用,瞬间击穿带宽预算。

所以我定阈值的思路是反推:设实例能接受的峰值流量为P,单key最大size应小于P除以该key的预期QPS。业务上如果明确知道某个key每秒最多被读100次,那它的value大小哪怕只有500KB,也得标记成高危。这个逻辑比“超过1MB才告警”科学得多。

3.3 预警分级和抑制策略

阈值定好之后还要设计分级,否则告警疲劳迟早会把整个体系带崩。我常用的配置是:

  • P3级别(预警):超过关注基线没有超过高危基线,说明存在潜在风险但不紧急。这类告警合并成每日巡检摘要通知,不单独打扰。
  • P1/P2级别(告警):超过高危基线,或者虽未超基线但命令调用频率极高,立刻触发即时通知,同时自动拉起诊断任务,把大Key清单、调用方、慢日志关联出来一起推送给值班同事。
  • 抑制策略:同一个key连续多轮巡检都超阈值时,如果处置工单未关闭,不再重复告警,只更新状态,避免一个key把告警面板刷屏。

阈值不是设定一次就完事。实例规格扩容、业务高峰期流量暴涨、新业务上线走了新的数据形态,这些都会让既有阈值失真。我一般每季度或者每次大版本变更后,会把历史扫描数据和真实故障史拿出来重新校准一遍,而不是永远迷信当初拍的数字。

4. 自动化监控链路搭建:采集、识别、告警、触达四段式闭环

阈值聊完,接下来是怎么把整套流程自动化。大Key自动监控链路本质上是一条数据流水线:定时把实例数据采过来,识别哪些key超标,再把结果送进告警系统,最后由告警系统把精准信息触达给值班人员。下面按四个环节挨个拆解。

4.1 采集层的三种模式与成本权衡

  • SCAN低频巡检:用一个后台定时任务,每5分钟对Redis执行一次SCAN全量遍历,配合MEMORY USAGE对可疑key做二次确认。优点是部署简单,缺点是遍历全量key在超大实例上成本偏高。这点可以通过只巡检热点前缀、按业务分片巡检来优化。
  • RDB周期分析:每天凌晨业务低谷期执行BGSAVE,RDB文件自动上传到分析服务解析,产出全量大Key报表。这个模式拿的数据最准,但因为延迟,无法胜任实时预警。
  • 流量旁路采集:在客户端侧把每条Redis命令的执行参数、耗时、key名称、value长度上报到统一日志平台,通过聚合计算找出value长度分布Top的key。这个模式最接近真实访问热度,能发现“虽然不大但访问极热”的key,但需要业务侧接入SDK,改造量较大。

我目前的落地组合是“SCAN低频巡检+RDB每日全量”,这两种模式配合起来性价比最高,几乎不需要业务侧改造。

4.2 识别与分析:命中判断、代价评估与关联定位

采集到的数据只是原材料,真正要做的是在分析层判断“这个key要不要告警”。分析模块要做三件事:

  • 第一,跑阈值规则,判断是否命中P3/P1。
  • 第二,评估代价。比如分析该key最近一小时在slowlog中出现的次数,如果频繁出现且耗时普遍超过50毫秒,那就不是“潜在问题”而是“正在发生的事故”。
  • 第三,关联上下文。分析模块输出告警信息时不能只给一个key名字,应该自动携带:key所属业务线(通过命名前缀推测)、最近24小时内存变化趋势、相关调用方接口、所在分片节点等字段。这些信息能让值班人员在收到告警后不做任何额外查询,直接开始决策。

4.3 告警触达:不打无准备的仗,每一次告警都有处理预案

告警不只是“发一条消息”,我理解它是一个完整的动作集合。告警触发后至少应该拉起这几个动作:

  • 通知值班人和业务接口人,推荐直接推到企业群,标题带上实例ID和key名,方便人员判断级别。
  • 自动打开对应的故障处理手册,或者直接在通知里带上处理脚本的链接,减少“收到告警但不知道怎么办”的尴尬。
  • 定期对告警做复盘,每周统计哪些大Key是重复告警、哪些没有被及时处理,持续优化规则。

这里有个经验很重要:告警的目的是让人决策,不是让人去研究。如果一条告警需要收到的人花20分钟才能定位到根因,那这条告警链路的设计就是失败的。好的告警应该把“是什么、在哪、影响多大、怎么处理”浓缩成一眼能看懂的信息。

4.4 巡检调度与控制面

巡检任务本身要有严禁自伤的保护机制。扫描是消耗资源的操作,在业务高峰执行或者频率太密都会把Redis打亚健康。我建议:巡检脚本的运行时间避开整点高峰,扫描过程中设置最大执行时长、自适应限流,扫到了大Key先记下来不立即纠缠,等采集周期结束后统一分析处理。另外所有巡检任务必须有开关和熔断,一旦发现巡检任务自身成为慢查询来源,立刻暂停并从副本执行。

5. 自动化处理方案:拆分、冷热分离、裁剪与删除,四类手段的取舍

监控预警是“防”,真正把大Key消灭掉才是“治”。处理大Key不能靠运维手动删数据——第一不优雅,第二删错了就是严重事故。自动化处理方案的核心是在匹配到特定模式的key时,按预置策略执行处理动作。下面是我用过且验证有效的四类方案。

5.1 集合类大Key的拆分方案:hash取模落子key

最常见的大Key类型就是集合结构被单业务无限写入。典型场景:一个店铺的全部订单全塞进一个list,一个用户的全部关注关系全塞进一个set。这种key的value可以无上限地增长,除了拆分没有别的路。

拆分思路是不要再指望一个key装下所有数据。以hash为例,可以把之前的:

HSET user:123:follow 1001 "value" 1002 "value" ...

改造成加桶号的形式:

HSET user:123:follow:0 1001 "value" 1002 "value" ... HSET user:123:follow:1 1003 "value" ...

桶号可以通过hash(user_id) % N计算。改造时需要注意双向兼容:客户端读取时要能够遍历所有子key再合并数据,写入时也要根据同样的规则路由到正确的子key。集合同理,改用SADD user:123:fans:0 member,统计总量时SCARD所有子key再sum。

实测下来拆分的收益非常直接:原来单次HGETALL几百毫秒的命令,拆成几十个子key后各自毫秒级返回,单分片压力骤降。但要提醒一点,拆分方案最好在key刚开始变大时介入,如果已经变成巨无霸,迁移和改造本身也有成本。

5.2 热点大Key的多级缓存治理

大Key如果同时是热点Key(高QPS),问题会被乘数放大。此时光拆key还不够,因为拆分后所有子key依然全部在Redis里被高频访问。我的建议是引入一层本地缓存(如Caffeine或业务进程内缓存)来承接读流量,Redis只作为数据源。

这类方案的经典场景是“商品详情”和“配置信息”:一个String存了几百KB的JSON,每秒却被上万个请求读取。在进程内加一层缓存,TTL设5秒,Redis上的QPS能骤降至原来的几十分之一,网络开销和CPU成本同时下降。注意这里要处理缓存一致性,如果数据实时性要求高,本地缓存只适合做兜底,实时一致性要求高的场景不建议硬套。

5.3 字段裁剪、TTL治理与异步删除

有些大Key不是“不能拆”,而是“存了不该存的东西”。最常见的问题是存在过期字段或冗余字段。比如一个hash里存了用户的全部历史操作记录,真正业务只需要最近30天,那最直接的处理就是裁剪掉过期字段。写一个定时任务,低频扫描该key的字段时间戳,HDEL删除过期字段,就能让key的体积稳定在可控区间。

另外,TTL治理是很多人忽视的利器。很多大Key根本没有设置过期时间,或者设置了非常大甚至永不失效的TTL。给大Key配上合理的TTL,让它到了时间自动消失,是成本最低的处理方式。但注意大Key删除本身也是风险操作:不要用DEL删除超大集合,Redis在释放大内存块时会阻塞主线程。标准的做法是用UNLINK命令,让回收工作在后台线程完成:

UNLINK product:detail:100023

5.4 冷热分离与存储降级

如果大Key承载的是真实业务数据,既拆不掉也不能删,那就要考虑把数据从Redis搬到更便宜、更合适的存储里。典型场景:某个hash里每条字段对应的都是大体积的图片描述或者富文本,几万个字段加起来几十MB,但真正常用的只有其中几百个。解决方案是——Redis只保留热数据的精简索引或摘要字段,完整数据存到对象存储或数据库,读取时先查Redis快速返回,未命中再回源存储。

这种方案牺牲了第一次读取的延迟,换取了Redis内存和网络的大幅释放,普遍应用于内容型系统。实施时要注意回源链路要加缓存,防止大面积缓存穿透直接把存储压垮。

5.5 自动化处理中的保护开关与回滚机制

自动化处理最怕的是什么?是脚本判断失误,把不该处理的数据处理了。比如拆分逻辑有bug导致部分子key数据丢失,或者裁剪任务把还在使用的字段删了。所以自动化处理一定要有保护机制:

  • 每次处理动作前先记录key的当前体积、元素数、最后访问时间,形成审计日志,方便事后回滚和追查。
  • 处理动作要有灰度开关:先在某个低优先级的key上验证再批量执行。
  • 预留回滚脚本,一旦监控发现处理后有数据缺失或访问异常,能按日志反向恢复。

我见过太多“自动化了但没有考虑失败的自动化”,结果和人为误删没什么两样。自动化处理不是让方案无人值守,而是把“人的重复劳动”自动化,把“人的决策和经验”仍然保留在关键节点上。

6. 一次线上大Key处理过程的完整复盘:从告警到验收的全链路

前面讲的都是方法论,这一节我想用一个贴近实际的案例把整个链路串起来,方便你照着设计自己的方案。为了方便说明,我称这条业务为“模拟项目X的会员中心”,Redis实例代号为cache-members-prod-01。

6.1 发现:凌晨的自动巡检抓住了正在变大的隐患

某天凌晨,自动巡检脚本照例执行SCAN扫描,在cache-members-prod-01的某个分片上发现了一个hash类型的key:member:profile:summary:88213,内存占用约3.8MB,字段数约两万。该key的命名前缀是会员摘要,平时应该很小,这个体积明显异常。

按照预设分级规则,此key超过1MB高优基线,触发P1告警。告警信息自动带上了一组诊断数据:键所在节点、最近1小时访问次数、最近24小时体积变化趋势(从1.2MB涨到3.8MB),以及最近24小时是否有相关慢日志(答案是有,一条HGETALL耗时340毫秒的慢日志)。

6.2 定位:从“这key很大”到“为什么大”只用了十分钟

值班同事按手册执行诊断,发现一个有意思的细节:这个key是“会员画像汇总”,理论上用户每天刷新一次画像,数据量应该稳定。查看上游写入逻辑后发现,某个新版本在上线时会把用户所有历史浏览类目都追加到这个hash里,且没有清理逻辑。日积月累字段数暴涨。

这个定位过程并不难,难的是“为什么这条链路能在没人察觉的情况下跑了一个多月”。复盘根因是上游代码没有对hash字段数量做上限保护,积累到最近才超过阈值,也侧面说明实时巡检出现的价值——等人工发现时恐怕已经拖垮分片了。

6.3 处理:拆分加裁剪双管齐下

确认根因后,方案分成两步:

  • 第一步,改造写入逻辑,新数据落进按日期拆分的子keymember:profile:summary:88213:202406,历史的大Key短期内保留并只读。
  • 第二步,写一个裁剪脚本,扫描该hash中超过90天未更新的字段并HDEL掉,把key体积压缩回合理水平。

处理过程中启用了灰度:先对该key执行只读方案验证,确认业务无感后,开启定时裁剪任务。每个裁剪批次都有一个审计日志记录删除了哪些字段、删除前后体积变化。整个流程借助开关一键暂停,遇到异常随时回滚。

6.4 验证:关注时间线和关联指标

处理结束后要重点验证的是:分片节点的大Key告警是否消失、HGETALL慢日志频率是否下降、该分片的响应时间是否回到基线。实际可见的结果:慢日志归零,分片CPU从高峰的70%回落到20%,行为非常明显。

后面我把这个case沉淀成一个处理模板:凡是命中member:profile:summary:*前缀且体积超过2MB的hash,自动进入观察名单,超过3MB自动触发裁剪流程。经过几个月的观察,这类问题在该集群中彻底绝迹。

最后说几句大白话经验

搞大Key监控这件事,技术本身并不炫酷,核心价值在于把“那万一哪天出事”变成“我已经知道你会出什么事”。我最大的体会是:监控体系建立起来之后,管理成本才是更长的战役。阈值需要校准、告警需要复盘、自动化动作需要持续打磨灰度策略,坚持做日常运营远比第一天上线几个脚本重要得多。

另外一个容易被忽略的实战细节是:处理大Key时,永远记得把客户端逻辑一起改造好再动Redis里的数据。很多团队费劲拆了半天key,结果客户端还在用老代码读原key,流量根本没切过去,等于白忙一场。先加兼容读写新子key的逻辑,再逐步下线旧的key,这个先后次序一定不能乱。

最后分享一个实用小技巧:给所有大Key监控和分析任务配上独立的日志文件,每轮巡检扫到哪些key、分析了多久、释放了多少内存,输出成结构化字段。这些历史数据积累下来,再遇到新的大Key问题,直接搜索相似前缀,很容易就能定位到是同一个上游链条在制造麻烦。这套数据资产,比你临时抓包和分析高效得多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进