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 分字段操作。