首页/数据/Redis 进阶速查/String 与 SDS 结构:编码差异与内存
数据结构与选型42 条

String 与 SDS 结构:编码差异与内存

String 用 SDS(简单动态字符串)存储,短字符串走 embstr 一次分配,超过 44 字节转 raw 两次分配;整数值直接用 int 编码省内存。

Redis 进阶速查数据更新于 2026-10-13

String 与 SDS

Redis 的字符串不是 C 风格的以零结尾字符数组,而是自己实现的 SDS(Simple Dynamic String,简单动态字符串):结构头里保存了长度与已分配容量,因此取长度是 O(1),追加前能预判是否需要扩容,内容里出现零字节也能安全保存。为了省内存,SDS 头按字符串长度分为 sdshdr5/8/16/32/64 几档,短的用窄字段,长的才用宽字段;在此之上,字符串对象再按内容形态选择 int、embstr、raw 三种编码,这决定了它是一块内存还是两块内存、能否原地修改。

基本信息

对象编码int / embstr / raw,用 OBJECT ENCODING 查看
SDS 头部sdshdr5/8/16/32/64,按实际长度选择最省的一档
embstr 上限44 字节以内,头部与内容一次分配(Redis 3.2 起口径)
超限转换超过 44 字节、或遇到 APPEND/SETRANGE 一类原地修改,转为 raw
长度语义STRLEN 按字节数返回,与编码无关
适用版本Redis 2.6 起编码体系稳定,跨大版本以实测 OBJECT ENCODING 为准

典型使用场景

  • 缓存单值对象:会话、Token、页面片段,用 SET 加过期时间即可覆盖大部分读取场景
  • 计数器:INCR / INCRBY / DECR 对 int 编码做原地运算,原子且没有解析开销,适合限流与 PV 统计
  • 分布式锁:SET key value NX PX 毫秒数 一条命令完成加锁与过期,value 用唯一随机串便于校验持有者
  • 位图与基数统计的载体:SETBIT/GETBIT 操作的是字符串的二进制位,HyperLogLog 也以字符串形式存储
  • 短时效数据:验证码、短信码这类几字节日志,embstr 一次分配,写入与淘汰都很轻

观察编码变化

观察编码变化
redis-cli SET n 100                  # 值可解析为整数 → int 编码
redis-cli OBJECT ENCODING n          # 输出 int
redis-cli STRLEN n                   # 仍按字符串长度算,输出 3
redis-cli SET s hello                # 5 字节,小于 44 → embstr
redis-cli OBJECT ENCODING s          # 输出 embstr
redis-cli APPEND s world             # 原地追加会把 embstr 改成 raw
redis-cli OBJECT ENCODING s          # 输出 raw
redis-cli SETRANGE s 0 H             # 局部改写同样触发转 raw
redis-cli MEMORY USAGE s             # 查看该键实际占用的字节数

三种编码对照

编码触发条件内存特点常用命令
int值可解析为 64 位整数且长度不超过 20 字节内容直接以整数保存,不额外存字符INCR / DECR / INCRBY
embstr长度不超过 44 字节的普通字符串对象头与内容一次分配,只读场景友好SET / GET / STRLEN
raw超过 44 字节,或对短字符串做过原地修改对象头与内容两次分配,支持原地追加APPEND / SETRANGE / GETRANGE
编码变更方向embstr 可转 raw,raw 不会退回 embstr删除元素不影响对象编码OBJECT ENCODING
int 编码下 INCR 是直接在整数上运算,没有字符串转数字的开销;但对 raw 编码的数值字符串执行 INCR,Redis 需要先解析再写回,写回后编码可能变回 int。带前导零的 007 不会被识别为整数,因此也不享受 int 编码。
键多而值小的时候,内存大头往往不是内容本身,而是每个键的字典条目、过期时间条目与对象头。大量几十字节的小对象按业务维度聚合成 Hash 后,总量通常能明显下降,但聚合会改变过期粒度与并发写入路径,改造前先压测再决定。
把整个大 JSON 作为一个字符串值存放时,任何一次字段修改都要读回全量内容再整体写回,网络与 CPU 开销随体积线性增长,还容易产生大 key 阻塞。字段会被单独更新的对象应改用 Hash 分字段操作。

易错点

  • 以为 APPEND 只在尾部续写:实际它会把 embstr 转成 raw 并重新分配内存,对短字符串高频追加会放大耗时与内存碎片
  • 用 SETRANGE 跳过中间位置写数据时,Redis 会用零字节补齐空缺,读取方按 C 字符串处理会因为零字节被截断,解析必须依赖长度或使用二进制安全的读取方式
  • 把大 JSON 整体存成一个键,每次更新都全量读写,网络抖动时会拖慢整个实例的单线程
  • 认为数字一定走 int 编码省内存:带前导零、超出 64 位范围或含空白字符的值都会落回字符串编码
  • 跨版本迁移时凭印象判断 embstr 阈值,不同大版本阈值口径不同,应在新旧实例上直接用 OBJECT ENCODING 实测

数据来源与参考

  • Redis 官方文档:Redis Strings 数据类型
  • Redis 源码 sds.c(简单动态字符串实现)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌