连载中 3/16

键设计与容量规划:数据进 Redis 之后的工程问题

2026-05-16 · 8430 阅读 · 26 评论 · 344 赞

模式之后,落地的三道坎

上篇把缓存读写模式讲透了,有同学说:模式我懂了,可真要把数据塞进 Redis,第一反应还是懵——键叫什么名字?内存给多少?一条数据多大算大?这三个问题看着琐碎,却是每个 Redis 使用者每天都要面对的工程细节,处理不好就是线上事故。

这一篇我们把「怎么塞」一次讲清:键设计、容量规划、大小 value 治理。

键设计:命名即文档

Redis 是键值数据库,键是你操作数据的唯一入口。键名起得好,排查问题、批量管理、监控分析全都不费力;起得差,三个月后你自己都不知道这个 key 是干嘛的。

社区惯例:冒号分层

键名的通行规范是 业务:对象:id,用冒号做层级分隔。看一组对比:

# 坏键名:看不出业务、掺了可变内容、超长
data_20260914_user_550e8400e29b41d4a716446655440000.json

# 好键名:三段结构,业务清晰
user:profile:10086
article:views:9527
order:list:10086:3

好键名的好处不只是好看,它直接决定了三件事:

  • 可批量操作:按前缀 SCAN 一把就能捞出某类数据(article:views:*),清理、迁移、统计都靠它;
  • 可视化友好:RDM 等工具会按冒号自动折叠成树,一眼看清业务分布;
  • 监控可归因:慢日志、大 key 报告里的键名带业务前缀,一眼定位到代码。

两条红线

第一,键里不要放可变内容。时间戳、随机数、毫秒级日期拼进键名,等于每次访问都在创建新键,旧键没人清理就变成内存黑洞——这通常是代码 bug,但只有键名规范清晰时才容易发现。第二,控制键名长度。键本身也占内存,而且元数据开销不小(下节细算),键名建议控制在 100 字节以内,用业务词汇,别用无意义缩写。

容量规划:别等 OOM 才想起来

Redis 最经典的翻车方式不是慢,是内存打满:maxmemory 到顶,写入开始报错,或者触发了没想到的淘汰策略,热数据被悄悄清掉。要避免这个局面,上线前先做估算:

容量估算 = 单条 value 平均大小 × 预计条数 × 冗余系数(1.5~2)

例:用户资料缓存
  单条 JSON 约 1KB,目标缓存 1000 万用户
  1KB × 1000万 × 2 = 20GB
  → 单机 32GB,maxmemory 建议配 24GB
  → 超过这个量级就该考虑分片集群

公式里最容易被低估的是键的元数据开销。每个键在 Redis 内部除了 value 本身,还有 dictEntry、SDS 头、对象头等固定开销,粗算 60~90 字节。这意味着:存 100 万个 100 字节的小 value,光键元数据就要吃掉几十 MB,占比比数据本身还高。所以超小 value 优先合并存储(用 hash 把多个小字段归到一个键下),而不是一个数据一个键。

另一个经验值:内存使用率到 70% 就要警惕。Redis 做 RDB 持久化时要 fork 子进程,写时复制会让内存短时间内逼近翻倍;碎片率一高,实际占用还会再涨一截。70% 是从容处理的红线,不是极限。

maxmemory 配多少、八种淘汰策略怎么选,第 11 篇调优专题会展开。这里先记住:容量规划在上线前做,淘汰策略是兜底不是规划。

大 value 治理:单线程的阿喀琉斯之踵

Redis 是单线程处理命令的,这意味着任何一条慢命令都会阻塞所有请求。大 value 就是天然的慢命令制造者:读它要占带宽,删它要同步释放内存,迁移它能让集群卡顿。多大的 value 算大?业界参考阈值:

数据类型大 key 参考阈值主要风险
Stringvalue 大于 10KB网络带宽、阻塞
Hash / List / Set / ZSet元素数大于 5000 或总大小大于 1MB操作阻塞、删除卡顿

先找到它们

# 扫描全库大 key(生产环境低峰期跑,控制采样节奏)
redis-cli --bigkeys -i 0.01

# 查看单个键的真实内存占用
MEMORY USAGE user:profile:10086

治理四招

找到大 key 之后,按场景选招:

  • 拆分:大 hash 按字段取模拆成多个子 hash,把单键操作压力摊开;
  • 压缩:JSON 先 Gzip 再存,文本类数据能压掉 70% 以上体积,代价是一次序列化开销;
  • 换结构:把整个对象 JSON 拆成 hash 的多个 field,读取时只取需要的字段,不再全量传输;
  • 只存 id:缓存里只放 id 列表,详情按需回源查库——适合详情大、列表操作频繁的场景。

举个例子,用户画像这种 50KB 的大 JSON,最常用的解法是拆成 hash:

// 大 hash 拆桶:按 id 取模拆到 100 个子 hash
public void cacheUser(User user) {
    long bucket = user.getId() % 100;
    redisTemplate.opsForHash().put("user:hash:" + bucket,
            String.valueOf(user.getId()), toJson(user));
}

public User getUser(Long id) {
    Object v = redisTemplate.opsForHash().get(
            "user:hash:" + (id % 100), String.valueOf(id));
    return v == null ? null : toUser(v);
}

拆完之后单键大小从 50MB 降到 500KB,读写不再阻塞,删除也能分批做。至于删除本身也有讲究:几百万元素的集合用 DEL 会卡顿,要用 UNLINK 异步释放,或者 HSCAN 分批删。

小结

  1. 键名:业务:对象:id 冒号分层,键里不放可变内容,长度控制在 100 字节内;
  2. 容量:单条大小 × 条数 × 冗余系数,上线前估算,内存 70% 是警戒线,小 value 记得合并存储省元数据;
  3. 大 value:String 大于 10KB、集合大于 5000 元素就该警惕,用 --bigkeys 找出来,拆分 / 压缩 / 换结构 / 只存 id 四招治理。

到这里,缓存的「选型」和「落地」都齐了。下一篇进入经典三问的第一问——缓存穿透:恶意请求拿着不存在的 id 疯狂打库,缓存形同虚设,四道防线怎么布?下篇见。

咖啡刚好到手,温度正好。

503

10 年全栈工程师 · 503咖啡馆主理人

#Redis#缓存#键设计#容量规划#大key

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 4 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞