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 避免大字符串翻倍导致内存浪费
相关推荐
万法若空5 小时前
【算法-查找】查找算法
java·数据结构·算法
阿成学长_Cain6 小时前
Linux ipcs 命令超全详解:查看共享内存 / 消息队列 / 信号量,IPC 运维必备
linux·运维·服务器·网络·数据库
OceanBase数据库官方博客8 小时前
OceanBase AI 时代,数据库的变与不变(技术解析与实践)
数据库·人工智能·oceanbase
阿成学长_Cain9 小时前
Linux dirs命令详解|Bash目录堆栈管理快速切换目录实战教程
linux·运维·前端·数据库
ywl4708120879 小时前
Mysql 平衡二叉树、红黑树、B树、B+树区别以及应用场景(五)
数据库·b树·mysql
IvorySQL10 小时前
IvorySQL Agent 探索与实践
数据库·人工智能·postgresql·oracle·ivorysql
SelectDB11 小时前
美团数十 PB 规模 Apache Doris 实践:从统一 OLAP 到 AI-Native 数据基座
大数据·数据库·性能优化
Database_Cool_11 小时前
阿里云RDS主从延迟解决方案_只读实例半同步复制最佳实践
数据库·人工智能
KaMeidebaby12 小时前
卡梅德生物技术快报|小 RNA 适配体合成 + 多方法亲和力表征全流程标准化操作手册
前端·网络·数据库·人工智能·算法
卓怡学长12 小时前
w269基于spring boot + vue 候鸟监测数据管理系统
java·数据库·spring boot·spring·intellij-idea