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

目录

[1. 引言](#1. 引言)

[1.1 Redis 中字符串的广泛应用](#1.1 Redis 中字符串的广泛应用)

[2. SDS 结构定义](#2. SDS 结构定义)

[2.1 Redis 3.2 之前的 SDS 结构](#2.1 Redis 3.2 之前的 SDS 结构)

[2.2 Redis 3.2 及之后的 SDS 结构](#2.2 Redis 3.2 及之后的 SDS 结构)

[3. SDS 与传统 C 字符串的比较](#3. SDS 与传统 C 字符串的比较)

[3.1 获取字符串长度](#3.1 获取字符串长度)

[3.2 缓冲区溢出问题](#3.2 缓冲区溢出问题)

[3.3 二进制安全性](#3.3 二进制安全性)

[3.4 内存分配次数](#3.4 内存分配次数)

[4. SDS 的内存分配策略](#4. SDS 的内存分配策略)

[4.1 空间预分配](#4.1 空间预分配)

[4.2 惰性空间释放](#4.2 惰性空间释放)

[5. SDS 的其他特性](#5. SDS 的其他特性)

[5.1 兼容 C 字符串函数](#5.1 兼容 C 字符串函数)

[5.2 类型灵活](#5.2 类型灵活)

[6. SDS 的使用场景](#6. SDS 的使用场景)

[6.1 键值对存储](#6.1 键值对存储)

[6.2 数据缓存](#6.2 数据缓存)

[6.3 日志系统](#6.3 日志系统)

[6.4 发布/订阅](#6.4 发布/订阅)

[7. 总结](#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 的结构较为简单,定义如下:

cs 复制代码
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 类型的定义:

cs 复制代码
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 高效运作的核心之一。

相关推荐
天空属于哈夫克31 天前
企业微信二次开发:精准实现关键词自动回复
架构·企业微信
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
小羊没烦恼!1 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
伞伞悦读1 天前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
晨米酱1 天前
AGENTS.md:Agent 的上下文策略层
面试·架构·agent
这个DBA有点耶1 天前
MVCC深入:Read View、版本链与快照读——InnoDB并发控制的内核
数据库·mysql·架构