Redis底层数据结构

一、前言

我们都知道Redis 是用C语言实现的,但是C 本身并没有内置高级数据结构

如:String(字符串)List(链表)Set(集合)Hash(哈希表)Zset(有序集合)Stream(流)

所以,Redis 必须自己从零设计一套高度定制化的数据结构 ,满足其作为高性能内存数据库的独特需求。

二、Redis底层数据结构 →SDS

1、SDS简介

SDS (Simple Dynamic String,简单动态字符串) 是 Redis 自主实现的一种高效、安全、二进制友好的动态字符串结构 ,用于替代 C 语言原生的 char* 字符串

2、为什么需要SDS

C语言并没有字符串结构,而是通过字符数组来间接的表示字符串,对应的存储结果,如下:

C 语言中的字符串本质是 以 \0(空字符)结尾的字符数组,这种设计存在严重问题:

问题 说明
🔴 获取长度需 O(n) 必须遍历到\0才知道长度(strlen())越长越慢
🔴 二进制不安全 如果数据中间有 \0,会被截断,被误认为字符串结束 (无法存图片、加密数据等)
🔴 缓冲区溢出风险高 strcat(dest, src) 不检查dest空间是否够大,极易导致内存越界
🔴 修改效率低 无法预分配空间 ,每次拼接都可能触发 realloc(重新分配内存)
🔴 内存浪费或碎片 没有统一管理,频繁分配小字符串导致碎片

为了解决这些问题,Redis 设计了 SDS,它是一个带有元数据头的结构,支持:

SDS 优点
获取长度O(1) : 通过 len 字段直接记录当前字符串长度,无需遍历
二进制安全: 字符串内容由len决定,可包含任意字节(包括 \0),适用于任意二进制数据
杜绝缓冲区溢出: 所有修改操作(如 sdscat)都会先检查 alloc 是否足够,不足则自动扩容后再写入
高效修改 + 空间预分配: 采用"预分配冗余空间"策略(<1MB 时翻倍,≥1MB 时 +1MB),大幅减少 realloc 次数
惰性空间释放 + 多种 header 优化内存:   1、缩短字符串时不立即释放内存(仅更新 len),后续可复用   2、根据字符串长度自动选择 sdshdr 5/8/16/32/64,最小化结构体开销

3、SDS 的结构

Redis 使用了多种 SDS 类型来优化内存使用,根据字符串长度选择不同 header

c 复制代码
// Redis源码中的 sds.h 文件
/*
 sdshdr8:用于存储长度在 32 ~ 255 字节之间的字符串。
 len 和 alloc 使用 uint8_t(1字节),最多表示 255。
 当字符串长度 >= 32 且 <= 255 时,Redis 自动选用此结构以节省内存。
*/
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len;         /* 已使用字节数(字符串实际长度) */
    uint8_t alloc;       /* 已分配的总容量(不包括头部和结尾的空字符 \0) */
    unsigned char flags; /* 低3位表示SDS类型(此处为SDS_TYPE_8),高5位保留未用 */
    char buf[];          /* 柔性数组,用于存储实际的字符串内容(末尾自动加 \0) */
};

/*
 sdshdr16:用于存储长度在 256 ~ 65535 字节(即 64KB - 1)之间的字符串。
 len 和 alloc 使用 uint16_t(2字节),适用于中等大小的字符串。
 当字符串长度 >= 256 且 <= 65535 时使用。
*/
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len;        /* 已使用字节数(字符串实际长度) */
    uint16_t alloc;      /* 已分配的总容量(不包括头部和结尾的空字符 \0) */
    unsigned char flags; /* 低3位表示SDS类型(此处为SDS_TYPE_16),高5位保留未用 */
    char buf[];          /* 柔性数组,用于存储实际的字符串内容(末尾自动加 \0) */
};

/*
 sdshdr32:用于存储长度在 65536 ~ 4,294,967,295 字节(约 4GB)之间的字符串。
 len 和 alloc 使用 uint32_t(4字节),适用于大字符串(如大JSON、序列化对象等)。
 在 32 位或 64 位系统上,只要字符串不超过 4GB,通常优先使用此结构。
*/ 
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len;        /* 已使用字节数(字符串实际长度) */
    uint32_t alloc;      /* 已分配的总容量(不包括头部和结尾的空字符 \0) */
    unsigned char flags; /* 低3位表示SDS类型(此处为SDS_TYPE_32),高5位保留未用 */
    char buf[];          /* 柔性数组,用于存储实际的字符串内容(末尾自动加 \0) */
};

/*
 sdshdr64:用于存储长度超过 4GB 的超大字符串(极少见,生产环境几乎不用)。
 len 和 alloc 使用 uint64_t(8字节),仅在 64 位系统上可能被使用。
 当字符串长度 > 2^32 - 1(即 > 4,294,967,295 字节)时启用。
*/
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len;        /* 已使用字节数(字符串实际长度) */
    uint64_t alloc;      /* 已分配的总容量(不包括头部和结尾的空字符 \0) */
    unsigned char flags; /* 低3位表示SDS类型(此处为SDS_TYPE_64),高5位保留未用 */
    char buf[];          /* 柔性数组,用于存储实际的字符串内容(末尾自动加 \0) */
};

4、SDS 扩容规则(当需要更多空间时)

当 SDS 的当前分配空间(alloc )不足以容纳新增数据时,系统会自动执行扩容操作:
申请一块更大的内存区域,将原有数据 (包括头部元信息和字符串内容)完整复制到新内存,随后释放旧内存,并更新指针使其指向新的地址

条件 扩容策略 目的
当前字符串长度 < 1MB 新容量 = 2 × 所需总长度(即至少翻倍) 减少小字符串频繁 realloc(重新分配内存),提升性能
当前字符串长度 ≥ 1MB 新容量 = 所需总长度 + 1MB 避免大字符串翻倍导致内存浪费
相关推荐
qq_3181215910 小时前
互联网大厂Java面试故事:从Spring Boot到微服务架构的技术挑战与解答
java·spring boot·redis·spring cloud·微服务·面试·内容社区
计算机毕设VX:Fegn089510 小时前
计算机毕业设计|基于springboot + vue医院设备管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
Mr__Miss10 小时前
保持redis和数据库一致性(双写一致性)
数据库·redis·spring
666HZ66611 小时前
数据结构2.0 线性表
c语言·数据结构·算法
Knight_AL11 小时前
Spring 事务传播行为 + 事务失效原因 + 传播行为为什么不用其他模式
数据库·sql·spring
倔强的石头_11 小时前
时序数据时代的“存储与分析困局”解析及金仓解决方案
数据库
计算机毕设VX:Fegn089511 小时前
计算机毕业设计|基于springboot + vue小型房屋租赁系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
余瑜鱼鱼鱼11 小时前
Java数据结构:从入门到精通(十二)
数据结构
倔强的石头_12 小时前
场景化落地指南——金仓时序数据库在关键行业的应用实践
数据库
SelectDB12 小时前
驾驭 CPU 与编译器:Apache Doris 实现极致性能的底层逻辑
运维·数据库·apache