Redis里面什么是sdshdr,可以详细介绍一下吗?

发布于:2025-08-06 ⋅ 阅读:(12) ⋅ 点赞:(0)


我们来详细解析一下 Redis 的核心数据结构之一: sdshdr

sdshdr 是 “Simple Dynamic String header” 的缩写,意为“简单动态字符串头”。它是在 Redis 自己实现的字符串库(SDS)中,用于定义字符串对象的头部结构。理解了 sdshdr,就能明白为什么 Redis 的字符串操作如此高效和安全。

简单来说,sdshdr 是 Redis 字符串(SDS)的元数据部分,它紧邻实际的字符串数据存放在同一块连续的内存中,记录了字符串的长度、空余空间等信息。

为什么 Redis 不直接使用 C 语言的字符串?

要理解 sdshdr 的重要性,首先要明白传统 C 语言字符串(以 \0 结尾的字符数组)的缺陷:

  1. 获取长度效率低: C 语言字符串本身不记录长度,要获取其长度必须遍历整个字符串,直到遇到 \0,时间复杂度为 O ( N ) O(N) O(N)
  2. 容易造成缓冲区溢出(Buffer Overflow): 当使用 strcat 等函数拼接字符串时,如果目标数组空间不足,就会发生缓冲区溢出,这是一种严重的安全漏洞。
  3. 二进制不安全: C 语言字符串以 \0 (空字符) 作为结束符,这意味着字符串内容不能包含 \0。因此,无法用它来存储图片、音频等二进制数据。
  4. 内存管理复杂: 每次增长或缩短字符串,都需要手动进行复杂的内存重分配,容易出错且效率不高。

为了解决这些问题,Redis 设计了 SDS。而 SDS 的核心就是 sdshdr 头部结构。

sdshdr 的结构

一个完整的 SDS 字符串在内存中由两部分组成:

  1. 头部(Header):sdshdr 结构体。
  2. 数据(Data): 紧跟在头部后面的实际字符串内容。

sdshdr 并不是一个单一的结构,为了节省内存,Redis 根据字符串的实际长度,定义了多种不同的 sdshdr 类型(在 sds.h 源码中定义)。

在 Redis 5.0 及以后的版本中,sdshdr 的通用结构可以看作是:

struct __attribute__ ((__packed__)) sdshdr<T> {
    T len;          // 已使用长度 (length of the string)
    T alloc;        // 总分配长度 (total allocated length, excluding header and null terminator)
    unsigned char flags;  // 标志位 (flags, indicating the header type)
    char buf[];       // 柔性数组 (flexible array member), 代表实际的字符串数据
};

关键字段解释:

  • len: 记录了 buf 中已存储字符串的实际长度。有了它,Redis 获取字符串长度的时间复杂度是 O ( 1 ) O(1) O(1),极其高效。
  • alloc: 记录了不包括头部和末尾 \0 的情况下,总共为 buf 分配的内存空间大小。lenalloc 的差值就是剩余可用空间。
  • flags: 一个3位的字段,用来表示当前 sdshdr 的具体类型。
  • buf[]: 这是一个“柔性数组成员”,是 C99 的一个特性。它表示 buf 指向 sdshdr 结构体之后紧跟的内存地址,这里存放着实际的字符串内容。字符串的末尾同样会追加一个 \0,以兼容部分 C 语言函数库。

__attribute__ ((__packed__)) 是一个 GCC 的指令,用于告诉编译器取消结构体在编译过程中的内存对齐优化,使得结构体成员紧凑排列,从而节省内存。

sdshdr 的不同类型

根据 flags 字段的值,Redis 会使用不同的 sdshdr 结构,主要区别在于 lenalloc 字段的数据类型,从而节省头部占用的空间:

flags 类型 lenalloc 的数据类型 头部大小
0 sdshdr5 (没有 len/alloc 字段) 1 字节
1 sdshdr8 uint8_t (8位无符号整数) 3 字节
2 sdshdr16 uint16_t (16位无符号整数) 5 字节
3 sdshdr32 uint32_t (32位无符号整数) 9 字节
4 sdshdr64 uint64_t (64位无符号整数) 17 字节

特别说明 sdshdr5:
sdshdr5 是一个特例,它没有 lenalloc 字段。它的 flags 字段本身就编码了字符串的长度(高5位存长度,低3位存类型)。它只能用于存储非常短的字符串。

Redis 会根据字符串的长度自动选择最小的、能容纳该字符串的 sdshdr 类型,实现极致的内存优化。

sdshdr 带来的优势总结

基于 sdshdr 结构,Redis 的 SDS 相比 C 语言字符串获得了巨大优势:

  1. 常数时间复杂度的长度获取: 直接读取 len 属性即可,时间复杂度为 O ( 1 ) O(1) O(1)
  2. 杜绝缓冲区溢出: 当对 SDS 进行修改时(如 APPEND),SDS 的 API 会先检查 alloc - len 的剩余空间是否足够。如果不足,它会自动进行内存重分配,扩展 buf 的大小,然后再执行操作,从而保证了安全。(对这里len和alloc不理解的可以看文章末尾!!!)
  3. 空间预分配与惰性释放(减少内存重分配次数):
    • 空间预分配: 当对 SDS 进行扩展时,如果修改后字符串长度小于 1MB,程序会分配 len * 2 的空间;如果超过 1MB,则会额外多分配 1MB 的空间。这种策略避免了每次增加字符串都重新分配内存,提升了性能。
    • 惰性空间释放: 当缩短 SDS 字符串时,程序并不会立即释放多出来的空间,而是更新 len 字段,将这部分空间记录为未使用,以备将来再次使用。
  4. 二进制安全: SDS 使用 len 属性来判断字符串结束,而不是 \0。因此 buf 中可以包含任意字符,包括 \0。这使得 SDS 可以安全地存储任何二进制数据。
  5. 兼容部分 C 语言函数: SDS 字符串的末尾依然保留了一个 \0 字符(这个 \0 不计入 len 长度),这使得那些只读取而不修改字符串的 C 语言函数(如 printfstrcmp)可以直接处理 SDS 的 buf 部分。

综上所述,sdshdr 是 Redis 高性能字符串实现的关键基石。它通过一个精巧的头部设计,解决了传统 C 语言字符串的诸多痛点,为 Redis 提供了高效、安全且功能丰富的字符串处理能力。

下一篇:
Redis中的sdshdr的len和alloc那块的知识点详解


网站公告

今日签到

点亮在社区的每一天
去签到