当前位置: 首页 > article >正文

深入理解 Redis SDS:高效字符串存储的秘密

目录

1. 引言

1.1 Redis 中字符串的广泛应用

2. SDS 结构定义

2.1 Redis 3.2 之前的 SDS 结构

2.2 Redis 3.2 及之后的 SDS 结构

3. SDS 与传统 C 字符串的比较

3.1 获取字符串长度

3.2 缓冲区溢出问题

3.3 二进制安全性

3.4 内存分配次数

4. SDS 的内存分配策略

4.1 空间预分配

4.2 惰性空间释放

5. SDS 的其他特性

5.1 兼容 C 字符串函数

5.2 类型灵活

6. SDS 的使用场景

6.1 键值对存储

6.2 数据缓存

6.3 日志系统

6.4 发布/订阅

7. 总结


1. 引言

在 Redis 中,SDS(Simple Dynamic String,简单动态字符串)是一种用于替代传统 C 语言字符串的数据结构。作为一个高性能的内存数据库,Redis 频繁进行字符串操作,而传统 C 字符串在性能和安全性上存在诸多问题。为此,Redis 专门设计了 SDS,旨在满足更高效的字符串存储和操作需求。

1.1 Redis 中字符串的广泛应用

在 Redis 中,字符串(String)是最基础、最常用的数据类型之一。其应用场景包括:

  • 缓存数据(如 HTML 页面片段、用户会话信息)
  • 计数器(如网站访问量、点赞数)
  • 位图(Bitmap)地理位置(GEO)数据 等底层数据结构的实现
  • 发布/订阅(Pub/Sub)流水线(Pipeline) 等功能的数据交互

因此,优化字符串的存储和操作效率对 Redis 性能至关重要。


2. SDS 结构定义

2.1 Redis 3.2 之前的 SDS 结构

在 Redis 3.2 之前,SDS 的结构较为简单,定义如下:

struct sdshdr {
    int len;    // 字符串的实际长度
    int free;   // 未使用的字节数
    char buf[]; // 字符数组,末尾以 '\0' 兼容 C 字符串函数
};

解释

  • len:存储字符串当前的长度(不包括 \0)。
  • free:记录 buf 数组中未使用的空间,便于后续字符串增长。
  • buf[]:存储字符串数据,末尾保留 \0 以兼容 C 标准库字符串函数。

2.2 Redis 3.2 及之后的 SDS 结构

Redis 3.2 及更高版本对 SDS 进行了优化,采用了多种 SDS 结构以适应不同长度的字符串。以下是几种 SDS 类型的定义:

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 低 3 位表示类型,高 5 位表示长度 */
    char buf[]; // 适用于长度 < 32 字节的字符串
};

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len;
    uint8_t alloc;
    unsigned char flags;
    char buf[]; // 适用于长度 < 256 字节的字符串
};

struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len;
    uint16_t alloc;
    unsigned char flags;
    char buf[]; // 适用于长度 < 65536 字节的字符串
};

struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len;
    uint32_t alloc;
    unsigned char flags;
    char buf[]; // 适用于长度 < 2^32 字节的字符串
};

struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len;
    uint64_t alloc;
    unsigned char flags;
    char buf[]; // 适用于长度 ≥ 2^32 字节的字符串
};

关键点

  • flags 字段的低 3 位标识 SDS 类型(如 sdshdr8sdshdr16 等),高 5 位用于记录 sdshdr5 类型的字符串长度。
  • SDS 根据字符串长度选择合适的结构体类型,减少内存浪费,提升存储效率。

3. SDS 与传统 C 字符串的比较

3.1 获取字符串长度

  • 传统 C 字符串:需遍历字符串直到遇到 \0,时间复杂度为 O(n)
  • SDS:直接通过 len 字段获取长度,时间复杂度为 O(1)

3.2 缓冲区溢出问题

  • 传统 C 字符串strcat() 等函数可能因缓冲区空间不足导致溢出。
  • SDS:每次字符串修改前都会检查 free 字段中的剩余空间,若不足则自动扩展,避免溢出风险。

3.3 二进制安全性

  • 传统 C 字符串:以 \0 作为结束标志,无法存储带有 \0 的二进制数据。
  • SDS:使用 len 记录实际长度,允许存储任意二进制数据。

3.4 内存分配次数

  • 传统 C 字符串:每次修改都会重新分配内存,效率较低。
  • SDS:采用 空间预分配惰性空间释放 策略,减少内存重分配次数,提高性能。

4. SDS 的内存分配策略

4.1 空间预分配

  • 如果修改后的字符串长度 小于 1MB,则分配内存为 原长度的两倍 + 1 字节。
  • 如果修改后的字符串长度 大于等于 1MB,则分配内存为 原长度 + 1MB + 1 字节。

示例

  • 原字符串 hello,长度为 5。
  • append(" world") 后,字符串变为 "hello world",长度 11。
  • 此时,SDS 会分配 22 个字节的内存,满足空间预分配策略(11 * 2 + 1 = 22)。

4.2 惰性空间释放

当 SDS 进行截断等缩短操作时,并不会立即释放多余内存,而是将其保留在 free 字段中,供后续使用。这种策略减少了频繁的内存分配操作,进一步提升了性能。


5. SDS 的其他特性

5.1 兼容 C 字符串函数

SDS 在 buf 数组末尾保留了 \0,确保兼容如 strlen()strcat() 等 C 标准库字符串函数,降低了代码迁移的难度。

5.2 类型灵活

Redis 根据字符串长度选择最适合的 SDS 结构体:

  • sdshdr8 适用于短字符串,节省内存。
  • sdshdr64 适用于超长字符串,满足大数据量场景需求。

6. SDS 的使用场景

6.1 键值对存储

Redis 的 SETGET 命令广泛使用 SDS,快速获取字符串长度,提高查询效率。

6.2 数据缓存

SDS 的预分配机制减少了频繁的内存分配操作,特别适用于缓存场景。

6.3 日志系统

SDS 的二进制安全特性可存储多种格式的数据,是日志系统中的理想选择。

6.4 发布/订阅

在 Pub/Sub 场景中,SDS 可灵活地处理多种数据流格式,确保消息高效传递。


7. 总结

SDS 作为 Redis 专门设计的字符串结构,解决了传统 C 字符串在性能、安全性和内存管理方面的问题。

  • 通过 空间预分配惰性空间释放 策略,SDS 有效减少了内存分配次数,提高了字符串操作的性能。
  • SDS 兼容 C 标准库字符串函数,具备出色的灵活性和高效性。

在 Redis 的高性能环境下,SDS 作为底层字符串结构,充分展现了其强大优势,是 Redis 高效运作的核心之一。


http://www.kler.cn/a/596105.html

相关文章:

  • PostgreSQL 常用函数 覆盖九成场景
  • GitHub Copilot Extensions:解锁开发者新能力
  • 人工智能之数学基础:线性方程组求解的得力助手——增广矩阵
  • Kafka消息自定义序列化
  • golang接口用法-代码案例
  • vulhub靶场matrix-breakout-2-morpheus
  • java设计模式之工厂模式《铸剑风云录》
  • vue3:ref , reactive
  • 论华为 Pura X 折叠屏性能检测
  • S32K144外设实验(三):ADC单通道连续采样(中断)
  • AudioTrack
  • 树莓集团数字产业布局解读:战略+商业双驱动
  • 【数据挖掘】Python基础环境安装配置
  • 每日一题--C与C++的差别
  • Mac 上开发 Ragflow
  • PyCharm找不到包?一文解决 No module named报错~
  • 当DeepSeek走进生活:一场颠覆日常的智能革命
  • Trae AI 上新 SSHremote:服务器 Python 接口日志排查实战指南
  • ArkTS 基础语法介绍
  • 相机光学中一些疑难问题的解释