初步理解 Redis 源码:SDS(Simple Dynamic Strings)剖析
SDS(Simple Dynamic Strings,简单动态字符串)是 Redis 实现高性能和高可用的基础数据结构之一,几乎所有字符串和整数数据都基于 SDS 存储。本文将系统梳理 SDS 的底层实现原理、演进过程及其 API 设计。
1. 前置思考:C 语言字符串的局限
C 语言原生字符串以 \0
结尾,存在两个主要问题:
- 非二进制安全:若字符串中包含
\0
,读取时会被截断,无法完整表示任意二进制数据。 - 获取长度低效:每次获取字符串长度都需遍历直到遇到
\0
,时间复杂度为 O(n)。
如何实现二进制安全的字符串?
只需在结构体中额外维护一个长度字段,即可实现:
- 任意数据(包括
\0
)都能安全存储和读取; - O(1) 时间获取字符串长度。
2. Redis 3.2 之前的 SDS 实现
Redis 早期 SDS 结构如下:
struct sdshdr {
unsigned int len; // 已占用长度
unsigned int free; // 剩余可用空间
char buf[]; // 实际数据
};
结构优点:
- O(1) 获取长度和剩余空间;
- 动态数组
buf
,兼容 C 字符串操作; - 二进制安全。
结构缺陷:
- 对于短字符串,
len
和free
各占 4 字节,空间利用率低; - 无法根据字符串实际长度灵活调整头部大小。
3. Redis 3.2+:多类型 SDS 结构
3.1 多类型 SDS 设计
为提升空间效率,Redis 3.2 后引入5种 SDS 结构,根据字符串长度动态选择:
类型 | 长度字段 | 分配空间 | 适用范围 |
---|---|---|---|
sdshdr5 | 5 bits | 31字节内 | 超短字符串 |
sdshdr8 | 1字节 | 255字节 | 短字符串 |
sdshdr16 | 2字节 | 64KB | 中等字符串 |
sdshdr32 | 4字节 | 4GB | 长字符串 |
sdshdr64 | 8字节 | 极大字符串 | 超长字符串 |
类型选择流程图:
3.2 结构体定义
以 sdshdr8
为例:
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; // 已用长度
uint8_t alloc; // 分配空间
unsigned char flags; // 3位类型+5位预留
char buf[]; // 数据
};
- flags 字段:前3位存类型,后5位存长度(sdshdr5)或预留。
- packed 修饰:强制1字节对齐,节省内存。
内存布局示意图(以 sdshdr8 为例):
4. SDS API 核心实现
4.1 创建字符串
sds sdsnewlen(const void *init, size_t initlen);
主要流程:
- 根据字符串长度选择合适类型;
- 计算头部长度,分配内存(头部+数据+
\0
); - 初始化各字段,返回
buf
指针。
注意事项:
- 空字符串直接使用 sdshdr8,避免频繁扩容;
- 分配空间为
头部+数据+1
,最后1字节存\0
,兼容 C 字符串。
4.2 清空字符串
彻底释放内存:
void sdsfree(sds s); // s 指向 buf,需减去头部长度获得结构体首地址再释放
仅重置内容(高效复用):
void sdsclear(sds s); // len 设为0,buf[0]='\0',内存不释放
4.3 更新长度
如直接操作 buf
内容(如插入 \0
),需手动更新 len
字段:
void sdsupdatelen(sds s) {
size_t reallen = strlen(s);
sdssetlen(s, reallen);
}
4.4 拼接与扩容
拼接字符串:
sds sdscatsds(sds s, const sds t) {
return sdscatlen(s, t, sdslen(t));
}
核心扩容策略:
sds sdsMakeRoomFor(sds s, size_t addlen) {
size_t avail = sdsavail(s);
if (avail >= addlen) return s;
size_t len = sdslen(s);
size_t newlen = len + addlen;
// <1MB,扩容为2倍;>=1MB,扩容为+1MB
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
// 重新分配并迁移数据
...
}
扩容流程图:
5. SDS 优势总结
- 二进制安全:独立维护长度字段,支持任意内容;
- 高效空间利用:多类型结构,极致节省内存;
- 高性能:O(1) 获取长度/剩余空间,兼容 C 字符串操作;
- 灵活扩容:自动扩容策略,减少内存分配次数。