乐闻世界logo
搜索文章和话题

Redis 的底层实现原理是什么?包括哪些核心数据结构和机制?

2月19日 19:35

Redis 的底层实现原理是理解 Redis 高性能的关键,主要包括数据结构、网络模型、内存管理等核心内容。

1. SDS(Simple Dynamic String)

基本概念: SDS 是 Redis 中字符串的底层实现,是对 C 语言字符串的封装。

SDS 结构

c
struct sdshdr { int len; // 字符串长度 int free; // 剩余可用空间 char buf[]; // 字节数组 };

SDS 优势

  1. O(1) 时间复杂度获取字符串长度:C 语言字符串需要遍历整个字符串才能获取长度,时间复杂度为 O(n)
  2. 避免缓冲区溢出:SDS 会检查剩余空间,空间不足时会自动扩容
  3. 减少内存重分配次数:SDS 使用空间预分配和惰性释放策略,减少内存重分配次数
  4. 二进制安全:SDS 可以存储任意二进制数据,包括 '\0' 字符
  5. 兼容 C 语言字符串函数:SDS 遵循 C 语言字符串以 '\0' 结尾的惯例

空间预分配策略

  • 当 SDS 长度小于 1MB 时,扩容时会分配 2 倍的长度
  • 当 SDS 长度大于等于 1MB 时,扩容时会分配 1MB 的额外空间

2. 链表

基本概念: Redis 的链表是双向链表,用于实现 List、发布订阅、慢查询等功能。

链表结构

c
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;

链表特点

  1. 双向链表:可以方便地进行前后遍历
  2. 无环链表:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL
  3. 带表头表尾指针:可以快速获取表头和表尾节点
  4. 带链表长度计数器:可以快速获取链表长度
  5. 多态:链表节点可以存储不同类型的值

3. 字典(Dict)

基本概念: 字典是 Redis 中 Hash 的底层实现,使用哈希表实现。

字典结构

c
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 表示没有进行 rehash } dict;

哈希算法: Redis 使用 MurmurHash2 算法计算哈希值,然后使用 hash & sizemask 计算索引。

哈希冲突解决: Redis 使用链地址法解决哈希冲突,即每个哈希表节点都有一个 next 指针,指向下一个哈希表节点。

Rehash 过程

  1. 为 ht[1] 分配空间,大小为第一个大于等于 ht[0].used * 2 的 2^n
  2. 将 ht[0] 中的所有键值对 rehash 到 ht[1]
  3. 释放 ht[0],将 ht[1] 设置为 ht[0],ht[1] 创建一个空白哈希表

渐进式 Rehash: 为了避免 rehash 对性能的影响,Redis 使用渐进式 rehash,即分多次将 ht[0] 中的键值对 rehash 到 ht[1]。

4. 跳跃表(Skip List)

基本概念: 跳跃表是 Redis 中 ZSet 的底层实现,是一种有序数据结构。

跳跃表结构

c
typedef struct zskiplistNode { sds ele; // 成员对象 double score; // 分值 struct zskiplistNode *backward; // 后退指针 struct zskiplistLevel { struct zskiplistNode *forward; // 前进指针 unsigned long span; // 跨度 } level[]; // 层 } zskiplistNode; typedef struct zskiplist { struct zskiplistNode *header, *tail; // 表头和表尾节点 unsigned long length; // 跳跃表长度 int level; // 跳跃表最大层数 } zskiplist;

跳跃表特点

  1. 多层结构:跳跃表有多层,最底层包含所有元素,上层是下层的子集
  2. 有序性:跳跃表中的元素按照 score 从小到大排序
  3. 查找效率高:跳跃表的查找时间复杂度为 O(log n)
  4. 空间换时间:跳跃表通过多层结构提高查找效率,但会占用更多空间

5. 整数集合(IntSet)

基本概念: 整数集合是 Redis 中 Set 的底层实现之一,用于存储整数。

整数集合结构

c
typedef struct intset { uint32_t encoding; // 编码方式 uint32_t length; // 集合包含的元素数量 int8_t contents[]; // 保存元素的数组 } intset;

整数集合特点

  1. 有序性:整数集合中的元素按照从小到大排序
  2. 唯一性:整数集合中的元素都是唯一的
  3. 升级机制:当新元素的类型比当前编码类型大时,会自动升级编码类型

升级过程

  1. 根据新元素的类型,扩展整数集合的底层空间
  2. 将原有元素转换为新类型,并放到正确的位置
  3. 将新元素添加到整数集合中

6. 压缩列表(ZipList)

基本概念: 压缩列表是 Redis 中 List、Hash、ZSet 的底层实现之一,用于存储少量元素。

压缩列表结构

shell
<zlbytes><zltail><zllen><entry><entry>...<zlend>

压缩列表特点

  1. 紧凑存储:压缩列表使用连续内存空间,存储紧凑
  2. 节省内存:压缩列表没有指针和额外开销,节省内存
  3. 查找效率低:压缩列表的查找时间复杂度为 O(n)
  4. 更新效率低:压缩列表的更新可能需要内存重分配

压缩列表转换: 当压缩列表的元素数量或大小超过阈值时,会转换为其他数据结构:

  • List 转换为 linkedlist 或 quicklist
  • Hash 转换为 hashtable
  • ZSet 转换为 skiplist

7. I/O 多路复用

基本概念: Redis 使用 I/O 多路复用模型,可以同时处理多个客户端连接。

I/O 多路复用模型: Redis 使用 epoll(Linux)、kqueue(BSD)、select(Windows)等 I/O 多路复用函数。

工作流程

  1. Redis 服务器创建 socket,绑定端口,监听连接
  2. 使用 epoll_ctl 将 socket 添加到 epoll 实例中
  3. 使用 epoll_wait 等待事件发生
  4. 当有事件发生时,epoll_wait 返回,处理事件

优势

  1. 高并发:可以同时处理多个客户端连接
  2. 非阻塞:不会因为某个客户端的阻塞而影响其他客户端
  3. 高效:使用事件驱动模型,效率高

8. 事件循环

基本概念: Redis 使用事件循环模型,处理文件事件和时间事件。

文件事件: 文件事件是 Redis 服务器对 socket 操作的抽象,包括可读事件和可写事件。

时间事件: 时间事件是 Redis 服务器对定时操作的抽象,包括定时任务和周期性任务。

事件循环流程

c
void aeMain(aeEventLoop *eventLoop) { eventLoop->stop = 0; while (!eventLoop->stop) { // 处理文件事件 aeProcessEvents(eventLoop, AE_ALL_EVENTS); // 处理时间事件 processTimeEvents(eventLoop); } }

9. 内存管理

内存分配器: Redis 使用 jemalloc 作为内存分配器,jemalloc 是一个高性能的内存分配器。

内存碎片: Redis 使用 jemalloc 的内存碎片整理功能,减少内存碎片。

内存统计: Redis 使用 INFO memory 命令查看内存使用情况,包括 used_memory、used_memory_rss、used_memory_peak 等指标。

总结

Redis 的底层实现原理包括 SDS、链表、字典、跳跃表、整数集合、压缩列表等数据结构,以及 I/O 多路复用、事件循环、内存管理等机制。这些底层实现保证了 Redis 的高性能和高可靠性。理解这些底层实现原理,有助于更好地使用 Redis 和优化 Redis 的性能。

标签:Redis