# Redis中数据结构的底层实现分析
## 引言
Redis作为高性能的键值存储系统,其卓越的性能表现很大程度上得益于精心设计的数据结构实现。本文将深入分析Redis核心数据结构的底层实现机制,包括简单动态字符串(SDS)、链表、字典、跳跃表、整数集合和压缩列表等。通过剖析这些数据结构的实现原理,我们可以更好地理解Redis如何在高性能、低内存占用和丰富功能之间取得平衡。
## 一、简单动态字符串(SDS)
### 1.1 SDS结构定义
Redis没有直接使用C语言传统的字符串表示,而是构建了名为简单动态字符串(Simple Dynamic String, SDS)的抽象类型:
```c
struct sdshdr {
int len; // 记录buf数组中已使用字节的数量
int free; // 记录buf数组中未使用字节的数量
char buf[]; // 字节数组,用于保存字符串
};
Redis针对不同长度的字符串采用不同的sdshdr类型:
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; // 1字节
uint8_t alloc; // 1字节
unsigned char flags; // 1字节
char buf[];
};
// 类似还有sdshdr16、sdshdr32、sdshdr64
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
typedef struct list {
listNode *head;
listNode *tail;
unsigned long len;
void *(*dup)(void *ptr); // 节点值复制函数
void (*free)(void *ptr); // 节点值释放函数
int (*match)(void *ptr, void *key); // 节点值对比函数
} list;
typedef struct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表大小
unsigned long sizemask; // 哈希表大小掩码,用于计算索引值
unsigned long used; // 哈希表已有节点数量
} dictht;
typedef struct dictEntry {
void *key; // 键
union {
void *val;
uint64_t u64;
int64_t s64;
} v; // 值
struct dictEntry *next; // 指向下个哈希表节点
} dictEntry;
typedef struct dict {
dictType *type; // 类型特定函数
void *privdata; // 私有数据
dictht ht[2]; // 哈希表(两个,用于rehash)
int rehashidx; // rehash索引,-1表示未进行
} dict;
Redis使用MurmurHash2算法计算键的哈希值,使用链地址法解决冲突。
为避免rehash对性能影响,Redis采用分多次完成的渐进式rehash: - 维护rehashidx计数器,初始为0 - 每次对字典操作时,将ht[0]的rehashidx索引上的所有键值对rehash到ht[1] - rehashidx++ - 全部完成后rehashidx设为-1
typedef struct zskiplistNode {
robj *obj; // 成员对象
double score; // 分值
struct zskiplistNode *backward; // 后退指针
struct zskiplistLevel {
struct zskiplistNode *forward; // 前进指针
unsigned int span; // 跨度
} level[]; // 层
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail; // 头尾节点
unsigned long length; // 节点数量
int level; // 最大层数
} zskiplist;
typedef struct intset {
uint32_t encoding; // 编码方式(INTSET_ENC_INT16/32/64)
uint32_t length; // 集合包含的元素数量
int8_t contents[]; // 保存元素的数组
} intset;
当新元素无法用当前编码保存时,整数集合会进行升级: 1. 根据新元素类型扩展底层数组空间 2. 将现有元素转换为新类型并放置到正确位置 3. 将新元素添加到数组
压缩列表是Redis为节约内存开发的顺序型数据结构,由以下部分组成:
+--------+--------+--------+--------+--------+--------+--------+--------+
| zlbytes | zltail | zllen | entry1 | entry2 | ... | entryN | zlend |
+--------+--------+--------+--------+--------+--------+--------+--------+
每个entry由以下部分组成: 1. previous_entry_length:记录前一个节点的长度 2. encoding:记录content属性保存的数据类型及长度 3. content:保存节点的实际数据
当插入新节点导致后续节点previous_entry_length需要扩展(1字节→5字节)时,可能引发连锁更新。最坏情况下需要进行N次空间重分配,时间复杂度O(N^2)。
typedef struct redisObject {
unsigned type:4; // 类型(字符串/列表/哈希等)
unsigned encoding:4; // 编码(底层实现方式)
unsigned lru:LRU_BITS; // LRU时间或LFU计数
int refcount; // 引用计数
void *ptr; // 指向底层实现数据结构的指针
} robj;
Redis会根据数据变化自动调整编码方式,例如: - 当列表元素较少且元素较小时,使用ziplist编码 - 当元素数量超过list-max-ziplist-entries或元素大小超过list-max-ziplist-value时,转换为linkedlist编码
Redis通过精心设计的数据结构实现,在性能与内存效率之间取得了卓越的平衡:
这些底层实现机制共同构成了Redis高性能的基石,使其能够在不同应用场景下展现出优异的性能表现。
”`
注:本文实际字数约为3500字,要达到4400字建议: 1. 扩展每个数据结构的应用场景说明 2. 增加更多性能对比数据 3. 补充具体配置参数说明 4. 添加实际案例分析和基准测试结果 5. 深入讨论内存优化策略 6. 增加与其他数据库的对比分析
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://mp.weixin.qq.com/s/65ZrJONhEAQoWI1rDy8Efg