Redis 的底层实现原理是理解 Redis 高性能的关键,主要包括数据结构、网络模型、内存管理等核心内容。
1. SDS(Simple Dynamic String)
基本概念: SDS 是 Redis 中字符串的底层实现,是对 C 语言字符串的封装。
SDS 结构:
cstruct sdshdr { int len; // 字符串长度 int free; // 剩余可用空间 char buf[]; // 字节数组 };
SDS 优势:
- O(1) 时间复杂度获取字符串长度:C 语言字符串需要遍历整个字符串才能获取长度,时间复杂度为 O(n)
- 避免缓冲区溢出:SDS 会检查剩余空间,空间不足时会自动扩容
- 减少内存重分配次数:SDS 使用空间预分配和惰性释放策略,减少内存重分配次数
- 二进制安全:SDS 可以存储任意二进制数据,包括 '\0' 字符
- 兼容 C 语言字符串函数:SDS 遵循 C 语言字符串以 '\0' 结尾的惯例
空间预分配策略:
- 当 SDS 长度小于 1MB 时,扩容时会分配 2 倍的长度
- 当 SDS 长度大于等于 1MB 时,扩容时会分配 1MB 的额外空间
2. 链表
基本概念: Redis 的链表是双向链表,用于实现 List、发布订阅、慢查询等功能。
链表结构:
ctypedef 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;
链表特点:
- 双向链表:可以方便地进行前后遍历
- 无环链表:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL
- 带表头表尾指针:可以快速获取表头和表尾节点
- 带链表长度计数器:可以快速获取链表长度
- 多态:链表节点可以存储不同类型的值
3. 字典(Dict)
基本概念: 字典是 Redis 中 Hash 的底层实现,使用哈希表实现。
字典结构:
ctypedef 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 过程:
- 为 ht[1] 分配空间,大小为第一个大于等于 ht[0].used * 2 的 2^n
- 将 ht[0] 中的所有键值对 rehash 到 ht[1]
- 释放 ht[0],将 ht[1] 设置为 ht[0],ht[1] 创建一个空白哈希表
渐进式 Rehash: 为了避免 rehash 对性能的影响,Redis 使用渐进式 rehash,即分多次将 ht[0] 中的键值对 rehash 到 ht[1]。
4. 跳跃表(Skip List)
基本概念: 跳跃表是 Redis 中 ZSet 的底层实现,是一种有序数据结构。
跳跃表结构:
ctypedef 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;
跳跃表特点:
- 多层结构:跳跃表有多层,最底层包含所有元素,上层是下层的子集
- 有序性:跳跃表中的元素按照 score 从小到大排序
- 查找效率高:跳跃表的查找时间复杂度为 O(log n)
- 空间换时间:跳跃表通过多层结构提高查找效率,但会占用更多空间
5. 整数集合(IntSet)
基本概念: 整数集合是 Redis 中 Set 的底层实现之一,用于存储整数。
整数集合结构:
ctypedef struct intset { uint32_t encoding; // 编码方式 uint32_t length; // 集合包含的元素数量 int8_t contents[]; // 保存元素的数组 } intset;
整数集合特点:
- 有序性:整数集合中的元素按照从小到大排序
- 唯一性:整数集合中的元素都是唯一的
- 升级机制:当新元素的类型比当前编码类型大时,会自动升级编码类型
升级过程:
- 根据新元素的类型,扩展整数集合的底层空间
- 将原有元素转换为新类型,并放到正确的位置
- 将新元素添加到整数集合中
6. 压缩列表(ZipList)
基本概念: 压缩列表是 Redis 中 List、Hash、ZSet 的底层实现之一,用于存储少量元素。
压缩列表结构:
shell<zlbytes><zltail><zllen><entry><entry>...<zlend>
压缩列表特点:
- 紧凑存储:压缩列表使用连续内存空间,存储紧凑
- 节省内存:压缩列表没有指针和额外开销,节省内存
- 查找效率低:压缩列表的查找时间复杂度为 O(n)
- 更新效率低:压缩列表的更新可能需要内存重分配
压缩列表转换: 当压缩列表的元素数量或大小超过阈值时,会转换为其他数据结构:
- List 转换为 linkedlist 或 quicklist
- Hash 转换为 hashtable
- ZSet 转换为 skiplist
7. I/O 多路复用
基本概念: Redis 使用 I/O 多路复用模型,可以同时处理多个客户端连接。
I/O 多路复用模型: Redis 使用 epoll(Linux)、kqueue(BSD)、select(Windows)等 I/O 多路复用函数。
工作流程:
- Redis 服务器创建 socket,绑定端口,监听连接
- 使用 epoll_ctl 将 socket 添加到 epoll 实例中
- 使用 epoll_wait 等待事件发生
- 当有事件发生时,epoll_wait 返回,处理事件
优势:
- 高并发:可以同时处理多个客户端连接
- 非阻塞:不会因为某个客户端的阻塞而影响其他客户端
- 高效:使用事件驱动模型,效率高
8. 事件循环
基本概念: Redis 使用事件循环模型,处理文件事件和时间事件。
文件事件: 文件事件是 Redis 服务器对 socket 操作的抽象,包括可读事件和可写事件。
时间事件: 时间事件是 Redis 服务器对定时操作的抽象,包括定时任务和周期性任务。
事件循环流程:
cvoid 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 的性能。