拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Redis BigKey 到底危险在哪里:内存、阻塞、网络和删除风险怎么排查

Redis BigKey 到底危险在哪里:内存、阻塞、网络和删除风险怎么排查

很多 Redis 问题,表面上看是“偶发卡顿”“接口突然慢”“某个实例内存涨得很快”,最后往下查,常常会落到一个很朴素的原因:某些 key 太大。

这里的“大”不是一个绝对标准。字符串 value 很大,当然是 BigKey;一个 hash、set、zset、list 里元素数量特别多,也一样是 BigKey。它危险的地方不只是占内存,而是它会把 Redis 单线程模型、网络传输、删除释放、持久化和复制这些链路一起拖慢。

这篇文章不想把 BigKey 讲成一句“不要用大 key”。真正有用的是:知道它会在哪里出问题,线上应该怎么判断,已经存在时怎么处理。

一、BigKey 不是一种类型,而是一种风险形态

BigKey 常见有两类。

第一类是 value 本身很大。

例如一个 string key 里塞了几百 KB、几 MB 的 JSON。这个 key 可能只对应一个业务对象,但每次读取都要把整块数据从 Redis 拿出来,网络和客户端反序列化都会被放大。

第二类是集合元素很多。

例如一个 hash 里有几十万个 field,一个 set 里有上百万个 member,一个 zset 里存了大量排序数据。单个元素可能不大,但整个结构很大,而且很多命令会和元素数量相关。

所以 BigKey 的判断不能只看“key 名是什么”,要看两个维度:

  • 单个 key 占用多少内存。
  • 对这个 key 的常用命令复杂度是多少。

一个几 MB 的字符串会带来网络和内存问题;一个几十万元素的集合还可能带来慢命令和删除阻塞。

二、为什么 BigKey 会让 Redis 变慢

Redis 快,一个重要前提是主线程每次处理的命令都足够短。BigKey 会破坏这个前提。

1. 读取成本变高

如果业务每次都 GET 一个很大的 value,Redis 需要把整块数据从内存拷贝到网络缓冲区,再发给客户端。

这时慢的不一定是 Redis 查找 key,而是后面的数据搬运:

  • Redis 输出缓冲区压力变大。
  • 网络传输时间变长。
  • 客户端解析和反序列化变慢。
  • 并发一上来,多个大 value 会一起挤占带宽。

这也是为什么有些接口在本机压测看着还行,到了线上就抖动:线上有真实网络、真实并发、真实 payload。

2. 某些集合命令会放大主线程耗时

Redis 不是所有命令都是 O(1)。对大集合执行全量命令,非常容易把主线程拖住。

风险比较高的操作包括:

  • 对超大 hash 执行 HGETALL。
  • 对超大 set 执行 SMEMBERS。
  • 对超大 zset 执行 ZRANGE 大范围查询。
  • 对超大 list 执行 LRANGE 0 -1。

这些命令不是“不能用”,而是不能在集合很大的情况下无脑全量取。一次命令处理时间过长,后面的请求就只能排队。

Redis 的单线程不是缺点,前提是每个命令都短平快。一旦业务把一个大集合当成本地 Map 一样全量拉取,问题就开始了。

3. 删除 BigKey 也可能卡

很多人只关注读写,忽略删除。

删除一个很大的集合时,Redis 需要释放大量内存对象。如果使用 DEL,同步释放可能会让主线程停顿。这个停顿在低峰期可能看不出来,在高峰期就会变成接口毛刺。

更稳妥的做法是优先使用 UNLINK。它会把内存释放交给后台线程异步处理,主线程只做摘除 key 的动作。

但要注意,UNLINK 也不是让成本消失,只是把释放工作从主线程挪走。后台释放太多,实例仍然会有资源压力。所以真正的治理还是要减少 BigKey 本身。

4. 持久化和复制也会被影响

BigKey 还会影响 Redis 周边链路。

RDB 快照时,大对象会增加 fork 后写时复制的压力;AOF 重写时,大对象相关命令会让重写成本变高;主从复制时,大 value 也会占用复制链路。

所以有些问题不是马上表现为慢命令,而是表现为:

  • RDB 或 AOF 重写期间内存抖动。
  • 从库复制延迟增加。
  • 网络出口打满。
  • 实例内存碎片和峰值变得不可控。

这也是 BigKey 难处理的地方:它不一定每天报错,但会让系统在流量波峰时更脆。

三、怎么判断一个 key 是不是 BigKey

判断 BigKey 不能只凭感觉。比较实用的方式有三层。

1. 先看实例层面的信号

如果实例有这些现象,就值得怀疑是否存在 BigKey 或热 key:

  • used_memory 增长很快,但 key 数量增长不明显。
  • 慢查询里出现 HGETALL、SMEMBERS、LRANGE、ZRANGE 大范围命令。
  • 客户端超时集中发生在某几个接口。
  • 网络输入输出带宽经常打高。
  • 主从复制延迟偶尔拉大。

这些信号不能直接证明 BigKey,但能告诉你该往哪个方向查。

2. 用扫描方式找可疑 key

线上不要用 KEYS 去全量扫库。KEYS 会阻塞 Redis,数据量大时风险很高。

更合适的是 SCAN 思路:分批扫描,逐步检查。Redis 自带的 redis-cli 也有 bigkeys 模式,可以粗略找出每种数据类型里较大的 key。

不过 bigkeys 更偏“元素数量”视角,不一定能精确反映内存占用。比如一个 string 可能只有一个 key,但 value 很大;一个 hash 可能 field 不多,但每个 field 的 value 很长。

所以排查时最好组合使用:

  • 先扫描出疑似大 key。
  • 再看它的类型和长度。
  • 必要时用 MEMORY USAGE 看内存占用。
  • 最后结合业务访问方式判断风险。

3. 不要只看大小,也要看访问模式

一个 2 MB 的 key,如果一天只在离线任务里读一次,风险可能可控。

一个 500 KB 的 key,如果核心接口每秒读几百次,那就是高风险。

一个包含十万元素的 zset,如果每次只查小范围排名,可能还能接受;如果业务经常全量拉出来排序、过滤、分页,那就很危险。

所以 BigKey 治理不能只列一个固定阈值。更合理的是:

  • string value 超过几百 KB 就要关注。
  • 集合元素数达到几千、几万后就要看访问方式。
  • 读写频率越高,阈值越要保守。
  • 核心链路上的 key,要比非核心链路更严格。

四、已经有 BigKey 了,怎么处理

1. 大字符串:拆字段、拆对象、减少整包读

如果一个 string 里塞了完整 JSON,先问一个问题:业务真的每次都需要整份数据吗?

很多时候并不需要。比如用户画像、商品详情、配置对象,接口可能只用其中几个字段,但缓存里放的是整块 JSON。

可选处理方式:

  • 把稳定字段和高频字段拆开。
  • 用 hash 存结构化字段,按需 HGET。
  • 把大对象拆成多个小 key。
  • 对冷门大字段延迟加载。
  • 减少把数据库整行或整份响应原样塞进 Redis。

拆分后要注意一致性。不要为了拆 BigKey,把一次更新拆成很多个没有约束的小操作,最后让业务读到半新半旧的数据。必要时可以用版本号、短 TTL、双写顺序或 Lua 来控制。

2. 大集合:分桶、分页、限制单次返回量

大集合的核心思路是:不要让一个 key 承担无限增长的数据。

常见拆法包括:

  • 按用户、业务维度、时间分桶。
  • 按 hash slot 或取模拆成多个子 key。
  • 只缓存最近一段时间或 Top N 数据。
  • 全量数据放数据库或搜索系统,Redis 只做索引或热点缓存。

例如“某个用户的全部行为记录”不适合一直塞进一个 list;“某个活动的全部参与用户”也不适合无限塞进一个 set。更稳的是按日期、分页游标、分片 key 去组织。

同时,接口层面要避免全量命令:

  • 用 HSCAN 替代 HGETALL 的全量遍历。
  • 用 SSCAN 替代 SMEMBERS 的一次性全取。
  • 用 ZRANGE 小范围分页,不要一次取完整榜单。
  • 对 list 使用固定窗口,不要 LRANGE 0 -1。

这里的重点不是“SCAN 一定更快”,而是它可以把一次大阻塞拆成多次小操作,让系统更可控。

3. 删除:优先异步删除,避开流量高峰

如果已经确认某个 key 很大,删除时不要随手 DEL。

更稳的流程是:

  • 先确认 key 类型和大致大小。
  • 如果业务允许,先停止继续写入或切换到新 key。
  • 使用 UNLINK 做异步删除。
  • 大批量清理时分批执行,避开高峰。
  • 清理后观察延迟、内存和复制状态。

如果要删除的是一组大 key,也不要写个脚本瞬间全部删掉。分批、限速、可回滚,比“我一次性清干净”更适合线上系统。

五、哪些设计容易制造 BigKey

BigKey 很多时候不是 Redis 的问题,而是缓存建模的问题。

几个常见坑:

  • 把 Redis 当数据库,什么都往一个 key 里堆。
  • 为了减少 key 数量,把大量字段塞进一个 hash。
  • 为了接口省事,把完整响应 JSON 缓存起来。
  • 排行榜、粉丝列表、活动参与列表只增不裁剪。
  • 日志、消息、行为记录用 list 一直追加。
  • 缺少 TTL,历史数据长期留在内存里。

Redis 适合保存热点、短链路、访问模式明确的数据。它不适合承接无限增长的数据湖。

如果一个 key 的数据规模没有上限,基本就应该提前设计拆分策略。

六、一个比较实用的排查顺序

线上怀疑 BigKey 时,我会按这个顺序看。

第一步,看 Redis 指标。

重点看内存、慢查询、客户端输出缓冲区、网络流量、主从复制延迟。如果某个时间段接口慢,就对齐那个时间段的 Redis 指标。

第二步,看业务接口。

找出超时或耗时抖动最明显的接口,看它们访问了哪些 Redis key,是否有全量读取、全量集合查询、大对象反序列化。

第三步,扫描疑似 key。

用 SCAN 类方式分批扫描,结合类型长度和 MEMORY USAGE 找出大 key。不要在线上直接 KEYS。

第四步,判断处理策略。

如果是冷数据,考虑迁移或删除;如果是热数据,优先拆 key、拆字段、分页读取;如果短期不能改业务,至少先限制单次返回量,避免继续放大。

第五步,清理和观察。

删除用 UNLINK,批量清理要限速。清理后观察延迟、内存、慢查询、复制延迟,不要只看 key 没了就结束。

七、总结

BigKey 的本质不是“Redis 里有一个大东西”,而是这个大东西会让很多成本从 O(1) 变成不可控。

它可能让一次读取占满网络,让一个集合命令拖住主线程,让一次删除造成延迟毛刺,也可能在持久化和复制时把风险放大。

处理 BigKey 的关键不是背命令,而是建立三个习惯:

  • 缓存建模时给数据规模设上限。
  • 访问集合时避免一次性全量操作。
  • 清理大 key 时考虑主线程、后台释放和业务高峰。

Redis 很快,但它快在“每个操作都足够轻”。当一个 key 变得越来越重,问题迟早会从缓存层冒到业务接口上。

返回列表