redis7.2源码解析
redis7.2源码解析
[toc]
背景
学习redis7.2源码并分析redis的IO模型,基本的数据结构。
分析
epoll模型
// 以下只是一个demo
// 创建监听 socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, ...);
listen(listenfd, 128);
// 创建 epoll 实例
// 一棵红黑树(存储所有被监控的 FD)。
// 一个就绪链表(存储已经发生事件的 FD)
// 一个等待队列(用于阻塞 epoll_wait 的进程)
int epfd = epoll_create(1024);
// 把监听 socket 加入 epoll
struct epoll_event event;
event.events = EPOLLIN | EPOLLET; // 边缘触发
event.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event);
//进入事件循环
while (1) {
int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
for (i=0; i<n; i++) {
if (events[i].data.fd == listenfd) {
// 新连接建立后epoll_ctl将conn_fd添加到epoll中
accept()
} else {
// 客户端数据
read()
// 处理
write()
}
}
}
整体架构
dispatch read
read done
execute cmd
dispatch write
write done
redis 6 引入IO线程且默认是关闭的。
IO线程开启场景
-
1. 高网络吞吐瓶颈:当redis服务器CPU在主线程处理网络IO上花费大量时间而命令执行本身很快时候。 -
2. 多核CPU环境:服务器拥有较多CPU核心 -
3. 宽带密集型业务:业务涉及大量数据传输,网络带宽称为主要瓶颈,而非内存操作速度。
开启后使用redis-benchmark进行压测对比QPS以及latency。
命令执行
-
1. 初始化命令表:server.c main -> server.c initServerConfig() -> server.c populateCommandTable() 填充命令表 -
2. 解析执行命令:networking.c readQueryFromClient -> processInputBuffer() -> processCommandAndResetClient() -> server.c processCommand() -
1. 查命令表:server.c lookupCommand -
2. 执行命令:server.c call
数据结构
String
-
1. 结构 struct sdshdr {
int len; // 当前长度
int alloc; // 总容量
char buf[];
}; -
2. 特点 O(1) 获取长度
预分配
惰性释放
二进制安全:C中的字符串char * \0 空字节为字符串结束,不能完整的表示二进制流。
List
-
1. 结构 “双向链表 + 每个节点是一个 listpack(压缩连续内存块)”
quicklist
↓
head <-> node <-> node <-> node <-> tail
↓
listpack
↓
element element element
listpack:
[total_bytes]
[element1]
[element2]
[element3]
...
[end_mark]typedefstruct quicklist {
quicklistNode *head;
quicklistNode *tail;
unsigned long count; // 元素总数
unsigned long len; // 节点数
int fill; // 每个节点最大容量
int compress; // 压缩深度
} quicklist;
typedefstruct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *entry; // 指向 listpack
size_t sz; // 当前大小
unsigned int count; // 该节点元素数量
} quicklistNode; -
2. 特点 特点 说明 混合结构 链表 + 压缩块 减少 malloc 多元素共享一个块 cache 友好 连续内存 支持压缩 降低内存占用 两端 O(1) 保留链表优势
Hash
-
1. 结构 逻辑结构:
user:1
├── name → "Tom"
├── age → "20"
└── city → "Tokyo"
小 Hash:listpack
listpack:
[total_bytes] [tail_offset] [num_elements] [entry1] [entry2] ... [entryN] [end_marker]
特点:连续内存,非常节省空间,适合小对象,查找需要遍历
大 Hash:hashtable
typedef struct dict {
dictht ht[2];
long rehashidx;
} dict;
typedef struct dictht {
dictEntry **table; // 哈希桶数组
unsigned long size; // 桶数量
unsigned long sizemask;
unsigned long used; // 已使用 entry 数量
} dictht;
typedef struct dictEntry {
void *key;
union {
void *val;
} v;
struct dictEntry *next;
} dictEntry;
table
↓
+------+--------+------+--------+
| NULL | * | NULL | * |
+------+--------+------+--------+
| |
↓ ↓
dictEntry dictEntry
(name,Tom) (age,20)
|
↓
dictEntry
(city,Tokyo)编码 使用场景 备注 listpack 小 Hash hash-max-listpack-entries 512
hash-max-listpack-value 64hashtable 大 Hash -
2. 特点 listpack:连续内存,非常节省空间,适合小对象,查找需要遍历
hashtable:平均 O(1) 查找,允许哈希冲突(拉链法),内存不是连续的,支持渐进式 rehash,空间换时间,删除效率高,不保证顺序(插入顺序不保留,遍历顺序不可预测)
Set
-
1. 结构 编码 使用场景 intset 全是整数且数量少。可以忽略 hashtable 普通情况。同hash结构中的hashtable只是value = NULL -
2. 特点 同hash中的hashtable
Zset
-
1. 结构 编码 使用场景 listpack 小 ZSet。同上 skiplist + dict 大 ZSet typedefstruct zset {
dict *dict; // 哈希表
zskiplist *zsl; // 跳表
} zset;
typedefstruct zskiplist {
struct zskiplistNode *header; // 头节点
struct zskiplistNode *tail; // 尾节点
unsigned long length; // 元素数量
int level; // 当前最大层数
} zskiplist;
typedefstruct zskiplistNode {
sds ele; // 元素
double score; // 分数
struct zskiplistNode *backward; // 后退指针,指向直接前驱节点 (用于反向遍历)
// 层数组 (Level Array)
// 每个元素代表一层,level[0] 是底层,level[N] 是高层
struct zskiplistLevel {
struct zskiplistNode *forward; // 前进指针,指向该层的下一个节点
unsigned long span; // 跨度:forward 指针跨越了多少个节点 (用于计算排名)
} level[];
} zskiplistNode;随机层高是随机生成的有一个确定算法。Level 0 永远完整有序。LevelN 随机层高>= N+1的节点 根据score排序并连接起来
| 节点 | score | 随机层高 |
| -- | ----- | ---- |
| A | 10 | 3 |
| B | 20 | 1 |
| C | 30 | 2 |
| D | 40 | 1 |
| E | 50 | 3 |
| F | 60 | 1 |
| G | 70 | 2 |
Level2: A -------- E
Level1: A ---- C ---- E ---- G
Level0: A <-> B <-> C <-> D <-> E <-> F <-> G
查找路径示例(找 G)
查找 G(70):
Level2:A → E
E.forward=NULL → 下到 Level1
E → G
下到 Level0 精确定位
移动次数远小于 7。 -
2. 特点 listpack:同上
skiplist + dict: 有序结构(按 score 排序);多层索引;支持范围查询;支持快速排名
总结
-
1. 整体架构总结: 单线程命令执行 + 多路复用 IO + 可选 IO 线程 -
2. 命令执行流程总结 readQueryFromClient
↓
processInputBuffer
↓
processCommand
↓
lookupCommand
↓
call阶段 特点 命令表初始化 启动时一次性构建 命令查找 O(1) 字典查找 命令执行 函数指针调用 写回客户端 统一事件循环 -
3. 核心数据结构总结: 小数据节省内存,大数据保证性能
Redis 快的根本原因:内存操作;单线程避免锁;精心设计的数据结构;IO 事件驱动;空间与时间的极致平衡。
夜雨聆风
