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 一个很大的 valueRedis 需要把整块数据从内存拷贝到网络缓冲区再发给客户端。这时慢的不一定是 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 或热 keyused_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也不要写个脚本瞬间全部删掉。分批、限速、可回滚比“我一次性清干净”更适合线上系统。五、哪些设计容易制造 BigKeyBigKey 很多时候不是 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 变得越来越重问题迟早会从缓存层冒到业务接口上。
返回列表