1. Redis原理 SDS(简单动态字符串)

SDSSimple Dynamic String 的首字母缩写,翻译为简单的动态字符串,是 Redis自定义的一种表示字符串的特殊数据结构。相较于传统的C语言字符串,SDS 具有更多的功能和更高的性能,当前也会占用更多的内存。

Redis 3.2 之前的版本:

struct sdshdr {
    int len; // 记录buf数组中已使用字节的数量,即字符串的实际长度
    int free; // 存储剩余(空闲)的空间
    char buf[]; // 存储实际数据
};

Redis 3.2 之后的版本,SDS 变成了 5 种数据结构,不同类型的存储空间大小不一样:

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; // 已用空间的长度,占 4 个字节,不包括 ‘\0’;
    uint8_t alloc; // 实际分配长度,占 4 个字节,不包括 ‘\0’;
    unsigned char flags; // 标记当前 buf[] 类型( sdshdr8/16/32/64)
    char buf[]; // 存储实际数据
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

相比于C语言字符串,SDS 有以下优点:

  • 二进制安全
  • 查询字符串长度效率高
  • 缓冲区溢出保护
  • 空间预分配
  • 惰性空间释放

2. 二进制安全

C 语言中,用字符 \0 表示字符串的结束,如果字符串中本身就有 \0 字符,字符串就会被截断,即非二进制安全,例如 hello \0world 会被截断解析为 helloSDS 不受 \0 字符影响,能够准确存储任意二进制数据,因为它基于 len 属性而非遇到 \0 来判断字符串的结束位置。

3. 查询字符串长度效率高

C 语言中,获取一个字符串长度时,需对整个字符串进行遍历,直至遇到结束符号。在高并发场景下频繁遍历字符串,势必会造成性能瓶颈。SDS 中于 len 属性记录了字符串的长度,可以直接通过 STRLEN 命令获取长度即可。

4. 缓冲区溢出保护

C 语言中,字符串本身不记录可用空间大小,在进行字符串拼接等操作时,如果没有检查就可能会导致缓冲区溢出。SDS 中于 free 属性记录了额外未使用的空间大小,每次进行字符串操作前都会检查剩余空间是否足够,不足时会自动扩容,有效避免了缓冲区溢出问题。

5. 空间预分配

C 语言中,每次修改字符串长度(增加或减少)时,通常需要重新分配内存,这可能导致频繁的内存操作。SDS 优化了字符串增长操作,当修改字符串并需对 SDS 的空间进行扩展时,不仅会为SDS分配修改所必要的空间,还会分配额外的未使用空间(free属性),下次再修改就先检查未使用空间free是否满足,满足则不用在扩展空间。有效的减少字符串连续增长操作,减少所产生的内存重分配次数。

6.惰性空间释放

惰性空间释放策略则用于优化 SDS 字符串缩短 操作,当缩短 SDS 字符串后,并不会立即执行内存重分配来回收多余的空间,而是用free 属性将这些空间记录下来,如果后续有增长操作,则可直接使用。