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 字符串操作;
  • 二进制安全。

结构缺陷:

  • 对于短字符串,lenfree 各占 4 字节,空间利用率低;
  • 无法根据字符串实际长度灵活调整头部大小。

3. Redis 3.2+:多类型 SDS 结构

3.1 多类型 SDS 设计

为提升空间效率,Redis 3.2 后引入5种 SDS 结构,根据字符串长度动态选择:

类型长度字段分配空间适用范围
sdshdr55 bits31字节内超短字符串
sdshdr81字节255字节短字符串
sdshdr162字节64KB中等字符串
sdshdr324字节4GB长字符串
sdshdr648字节极大字符串超长字符串

类型选择流程图:

类型选择流程图.png

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 为例):

内存布局示意图(以 sdshdr8 为例).png


4. SDS API 核心实现

4.1 创建字符串

sds sdsnewlen(const void *init, size_t initlen);

主要流程:

  1. 根据字符串长度选择合适类型;
  2. 计算头部长度,分配内存(头部+数据+\0);
  3. 初始化各字段,返回 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;
    // 重新分配并迁移数据
    ...
}

扩容流程图:

扩容流程图.png


5. SDS 优势总结

  • 二进制安全:独立维护长度字段,支持任意内容;
  • 高效空间利用:多类型结构,极致节省内存;
  • 高性能:O(1) 获取长度/剩余空间,兼容 C 字符串操作;
  • 灵活扩容:自动扩容策略,减少内存分配次数。

6. 参考资料

标签: none

添加新评论