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 避免大字符串翻倍导致内存浪费
相关推荐
Dwzun32 分钟前
基于SpringBoot的共享单车调度系统【附源码+文档+部署视频+讲解)
java·数据库·vue.js·spring boot·后端·毕业设计·maven
little_kid_pea34 分钟前
Oracle:从收费明细中扣减退费数据
java·服务器·数据库
Coder-coco35 分钟前
选题管理|基于springboot + vue毕业设计选题管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
无限进步_39 分钟前
基于顺序表的通讯录系统设计与实现
c语言·开发语言·数据结构·c++·后端·算法·visual studio
长河42 分钟前
Record-API 性能优化实战:从“锁”到“快”的深度治理
数据库·性能优化
吃喝不愁霸王餐APP开发者43 分钟前
霸王餐试吃资格发放:Redis HyperLogLog亿级去重与Lua脚本原子性
数据库·redis·lua
q***48411 小时前
【MySQL】视图
数据库·mysql·oracle
007php0071 小时前
nginx加速缓存导致Event-Stream消息延迟问题的解决方案
运维·网络·数据库·nginx·缓存·面试·职场和发展
o***11141 小时前
智能生成ER图工具。使用 SQL 生成 ER 图:让数据库设计更高效
数据库·sql·oracle