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 避免大字符串翻倍导致内存浪费
相关推荐
L·S·P3 小时前
25MB 管理 100+ 种数据库:开源轻量客户端 DBX 介绍与上手
数据库·mysql·开源·database·dbx
不会就选b5 小时前
算法日常・每日刷题--<贪心>25
数据结构·算法
笃行3505 小时前
KingbaseES KSQL Developer 的连接管理、数据处理与对象管理
数据库
ha_lydms5 小时前
MaxCompute中加密与解密函数
大数据·数据库·数据仓库·hadoop·dataworks·maxcompute·odps
Shadow(⊙o⊙)6 小时前
MySQL内置函数
数据库·mysql
笃行3506 小时前
金仓 KingbaseES PL/SQL 开发全攻略:异常处理、数据类型、调试优化与过程语句
数据库
快乐的木子李7 小时前
mysql-26.7.0-linux26.7.0下载安装
数据库·mysql
excel8 小时前
prisma 如何处理数据库竞态
前端·数据库·后端
知行产研9 小时前
易控智驾:毛利率跃升17.2个百分点,矿山无人驾驶从“规模换增长“迈入“盈利拐点“
数据库·mysql
Cccp.1239 小时前
【leetcode】(七)前缀树和贪心算法
数据结构·leetcode·贪心算法