深入理解 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 高效运作的核心之一。

相关推荐
画个逗号给明天"19 分钟前
C#从入门到精通(1)
开发语言·c#
JavaPub-rodert40 分钟前
golang 的 goroutine 和 channel
开发语言·后端·golang
lly2024061 小时前
Matplotlib 柱形图
开发语言
浪遏1 小时前
说说Html5的新特性 | 这道题,要么送命,要么是个good beginning
html
nuc-1271 小时前
sqli-labs学习笔记3
数据库·笔记·学习·sqli-labs
浪遏1 小时前
我的远程实习(二) | git 持续更新版
前端
智商不在服务器2 小时前
XSS 绕过分析:一次循环与两次循环的区别
前端·xss
_Matthew2 小时前
JavaScript |(四)正则表达式 | 尚硅谷JavaScript基础&实战
开发语言·javascript·正则表达式
MonkeyKing_sunyuhua2 小时前
npm WARN EBADENGINE required: { node: ‘>=14‘ }
前端·npm·node.js
大小科圣2 小时前
基于redis实现会话保持
数据库·redis·git