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

相关推荐
猷咪8 分钟前
C++基础
开发语言·c++
IT·小灰灰10 分钟前
30行PHP,利用硅基流动API,网页客服瞬间上线
开发语言·人工智能·aigc·php
快点好好学习吧11 分钟前
phpize 依赖 php-config 获取 PHP 信息的庖丁解牛
android·开发语言·php
秦老师Q12 分钟前
php入门教程(超详细,一篇就够了!!!)
开发语言·mysql·php·db
烟锁池塘柳012 分钟前
解决Google Scholar “We‘re sorry... but your computer or network may be sending automated queries.”的问题
开发语言
是誰萆微了承諾12 分钟前
php 对接deepseek
android·开发语言·php
2601_9498683616 分钟前
Flutter for OpenHarmony 电子合同签署App实战 - 已签合同实现
java·开发语言·flutter
打工的小王21 分钟前
redis(四)搭建哨兵模式:一主二从三哨兵
数据库·redis·缓存
星火开发设计30 分钟前
类型别名 typedef:让复杂类型更简洁
开发语言·c++·学习·算法·函数·知识
qq_1777673742 分钟前
React Native鸿蒙跨平台数据使用监控应用技术,通过setInterval每5秒更新一次数据使用情况和套餐使用情况,模拟了真实应用中的数据监控场景
开发语言·前端·javascript·react native·react.js·ecmascript·harmonyos