- SDS 概述
1.1 什么是 SDS
SDS (Simple Dynamic String) 是 Redis 中用于存储字符串的核心数据结构,相比传统的 C 字符串具有显著优势:
// C 字符串的问题
char *str = "hello";
int len = strlen(str); // O(n) 时间复杂度
// SDS 的优势
sds s = sdsnew("hello");
size_t len = sdslen(s); // O(1) 时间复杂度
1.2 SDS vs C 字符串对比

image.png
- SDS 数据结构详解
2.1 五种 SDS 结构
Redis 根据字符串长度使用不同的结构,实现空间优化:
typedef char *sds;
// 1. 超小字符串 (< 32字节)
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; // 标志位 + 长度信息
char buf[]; // 字符串数据
};
// 2. 小字符串 (32-255字节)
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; // 已使用长度 (1字节)
uint8_t alloc; // 总分配长度 (1字节)
unsigned char flags; // 标志位
char buf[]; // 字符串数据
};
// 3. 中等字符串 (256字节-64KB)
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; // 已使用长度 (2字节)
uint16_t alloc; // 总分配长度 (2字节)
unsigned char flags; // 标志位
char buf[]; // 字符串数据
};
// 4. 大字符串 (64KB-4GB)
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; // 已使用长度 (4字节)
uint32_t alloc; // 总分配长度 (4字节)
unsigned char flags; // 标志位
char buf[]; // 字符串数据
};
// 5. 超大字符串 (>4GB)
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; // 已使用长度 (8字节)
uint64_t alloc; // 总分配长度 (8字节)
unsigned char flags; // 标志位
char buf[]; // 字符串数据
};
- 内存优化原理
3.1 packed 属性的作用
// ❌ 没有 __packed__ - 浪费空间
struct sdshdr32_normal {
uint32_t len; // 4字节
uint32_t alloc; // 4字节
unsigned char flags; // 1字节
// 编译器自动填充 3字节
};
// 总大小:12字节
// ✅ 使用 __packed__ - 节省空间
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; // 4字节
uint32_t alloc; // 4字节
unsigned char flags; // 1字节
// 无填充
};
// 总大小:9字节,节省 25% 空间!
3.2 分级结构的空间节省
# 如果统一使用最大结构存储所有字符串
存储 "hello" (5字符):
- 统一方案:17字节头部 + 5字节数据 = 22字节
- SDS方案:3字节头部 + 5字节数据 = 8字节
- 节省:64% 的空间!
3.3 整数类型选择原理

image.png
- Redis 字符串对象编码类型
4.1 三种编码方式
Redis 字符串对象会根据内容自动选择最优编码:
INT 编码
# 适用:纯数字字符串
redis> SET count "123"
redis> OBJECT ENCODING count
"int"
# 内存结构:仅占用 redisObject,约16字节
EMBSTR 编码
# 适用:长度 ≤ 44字节的字符串
redis> SET name "Alice"
redis> OBJECT ENCODING name
"embstr"
# 内存结构:redisObject + SDS 连续存储
[redisObject][sdshdr8][字符串数据]
RAW 编码
# 适用:长度 > 44字节 或 需要修改的字符串
redis> SET article "很长的文章内容..."
redis> OBJECT ENCODING article
"raw"
# 内存结构:redisObject 和 SDS 分别分配
redisObject → ptr → [sdshdr*][字符串数据]
4.2 编码自动转换
# 数字 → 字符串
redis> SET val "123" # INT编码
redis> APPEND val "abc" # 转为EMBSTR编码
# 短串 → 长串
redis> SET msg "short" # EMBSTR编码
redis> APPEND msg "很长很长..." # 转为RAW编码
# 只读 → 可写
redis> SET greeting "hello" # EMBSTR编码
redis> APPEND greeting "!" # 转为RAW编码
- SDS 核心算法
5.1 长度获取算法
static inline size_t sdslen(const sds s) {
unsigned char flags = s[-1]; // 获取flags字段
switch(flags & SDS_TYPE_MASK) {
case SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8:
return SDS_HDR(8,s)->len;
case SDS_TYPE_16:
return SDS_HDR(16,s)->len;
case SDS_TYPE_32:
return SDS_HDR(32,s)->len;
case SDS_TYPE_64:
return SDS_HDR(64,s)->len;
}
return 0;
}
5.2 空间预分配策略
sds sdsMakeRoomFor(sds s, size_t addlen) {
size_t free = sdsavail(s);
size_t len, newlen;
if (free >= addlen) return s; // 空间足够
len = sdslen(s);
newlen = (len + addlen);
// 预分配策略
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2; // 小于1MB时,分配2倍空间
else
newlen += SDS_MAX_PREALLOC; // 大于1MB时,额外分配1MB
return sdsResize(s, newlen);
}
- 内存布局图解
SDS内存布局示例 (sdshdr16):
┌──────────┬──────────┬───────┬─────────────────┬────┐
│ len │ alloc │ flags │ buf[] │ \0 │
│ (2字节) │ (2字节) │(1字节)│ (字符串数据) │ │
└──────────┴──────────┴───────┴─────────────────┴────┘
↑ ↑
头部信息 sds指针指向这里