平时没事,出事要命
大 Key 的定义是个参考线:string 超过 10KB、集合类型成员超过几千个,就该多看两眼;string 到 MB 级、集合到十万成员,基本可以定性。它平时跑得好好的,读写都正常——危险藏在 Redis 的单线程模型里:读一个大 key,序列化传输要几十上百毫秒;删一个十万成员的 hash,逐个释放内存同样要上百毫秒。这期间单线程被占满,整个实例上的所有请求跟着排队。热 Key 是流量的问题,大 Key 是数据本身的问题;热 Key 打满自己所在的分片,大 Key 拖慢整个实例的每一个人。
连锁反应清单
把危害摆成一列,方便对号入座:阻塞——读写删除都可能占用单线程几十毫秒到秒级,全部请求陪绑;网络——几 MB 的 value 一次传输打满内网带宽,其他请求的报文挤在后面;内存倾斜——Cluster 下大 Key 住哪个分片,哪个分片内存水涨船高,其余分片闲着;主从与迁移——全量同步要复制大 Key,数据迁移和恢复时它是最慢的一环;过期删除——TTL 到期后的清理同样可能阻塞(Redis 4.0 前没有异步删除)。一颗炸弹,多条引信。
先发现:扫出来
大 Key 不会自己举手,得扫。线上最常用的是自带的采样扫描:
# 按类型采样统计最大的 key(生产环境低峰跑)
redis-cli --bigkeys -i 0.1
# 精确测量某个 key 的内存占用
MEMORY USAGE item:detail:10086--bigkeys 是采样近似,适合日常巡检;要完整清单,用 RDB 离线分析工具(rdb-tools、redis-rdb-cli)对备份文件做全量分析,零线上影响。发现之后的纪律是把大 Key 纳入监控:内存占用 Top N、集合成员数 Top N 定期出报表,新进榜的 key 追根溯源。
治理:拆、压、异步删
存量治理三板斧。拆是根治:十万成员的 hash 按 id 取模拆成一百个小桶(user:tags:{id%100}),MB 级的 JSON 把不变的字段和热的字段拆开存——热数据小而快,冷数据另有去处;压是缓冲:大文本先压缩再存,体积常常砍到三分之一,但要评估 CPU 成本与客户端兼容性;异步删是止损:Redis 4.0 起 UNLINK 把释放内存的工作丢给后台线程,主线程只做摘链,删除大 Key 不再阻塞——存量清理统一用它:
// 拆分示例:大 hash 分桶
String bucket = "user:tags:" + (userId % 100);
redis.hset(bucket, String.valueOf(userId), tagsJson);
// 删除大 key:异步,不阻塞主线程
redis.unlink("user:tags:legacy");没法一次拆完的,用 SCAN 分批搬数据再删旧 key,全程低峰、限速。
预防靠规矩
比治理更重要的是让大 Key 少产生:代码评审盯住「往集合里无限 add」「把整个对象 JSON 塞一个 value」这类写法;序列化对象只存业务需要的字段;集合容量有设计上限,到量自动分桶。规矩立住,新的大 Key 增长曲线就平了。
预热、热 Key、大 Key,讲的都是「量」的失衡。接下来换个视角看 Redis 自身:内存终归是有限的,满了腾谁、留谁——淘汰策略的选择题,下一篇开讲。
评论 (0)