redis-源码带读-01-架构概览与启动流程
Redis 源码带读 第1篇:架构概览与启动流程
本篇目标
建立Redis整体架构的全局视角,理解服务器启动的完整流程。
前置知识
- C语言基础
- 网络编程基础(socket)
- Linux系统编程
1. Redis是什么
Redis是一个开源的内存数据结构存储系统(in-memory data structure store)。它可以用作数据库、缓存和消息中间件。
核心特点:
- 单线程模型:主线程处理所有命令,避免锁竞争
- 丰富的数据结构:String、List、Hash、Set、Sorted Set、Stream等
- 持久化:RDB快照 + AOF日志
- 高可用:主从复制 + Sentinel + Cluster
2. 源码目录结构
所有源码都在 src/ 目录下平铺,约120个 .c 文件:
服务器核心
| 文件 | 行数 | 职责 |
|---|---|---|
server.c |
~8429 | main()、命令分发表、serverCron、共享对象 |
server.h |
~4933 | 所有核心结构体定义 |
config.c |
~2500 | 配置文件解析、CONFIG GET/SET |
debug.c |
~1200 | DEBUG子命令、崩溃模拟 |
release.c |
~50 | 版本信息 |
网络层
| 文件 | 行数 | 职责 |
|---|---|---|
networking.c |
~6115 | 客户端结构、RESP解析、I/O |
anet.c |
~1100 | TCP socket辅助函数 |
connection.c |
~600 | 连接类型抽象层 |
resp_parser.c |
~1500 | RESP协议解析器 |
blocked.c |
~900 | 阻塞命令状态机 |
tls.c |
~2000 | TLS连接实现 |
数据结构
| 文件 | 行数 | 职责 |
|---|---|---|
sds.c |
~1599 | 简单动态字符串(Simple Dynamic String) |
dict.c |
~2397 | 哈希表(渐进式Rehash) |
t_string.c |
~1678 | String命令实现 |
t_list.c |
~1754 | List命令实现(quicklist) |
t_hash.c |
~6908 | Hash命令实现(最大类型文件) |
t_zset.c |
~5033 | Sorted Set命令实现(skiplist) |
t_set.c |
~2047 | Set命令实现 |
t_stream.c |
~6453 | Stream命令实现(Radix Tree) |
object.c |
~2007 | 对象系统、编码转换 |
kvstore.c |
~1500 | 基于slot的哈希表 |
rax.c |
~2000 | 压缩前缀树(Radix Tree) |
listpack.c |
~1000 | 紧凑列表 |
持久化
| 文件 | 行数 | 职责 |
|---|---|---|
rdb.c |
~2500 | RDB快照持久化 |
aof.c |
~3000 | AOF追加日志持久化 |
bio.c |
~500 | 后台I/O线程 |
集群与复制
| 文件 | 行数 | 职责 |
|---|---|---|
cluster.c |
~2000 | Redis Cluster API/调度 |
cluster_legacy.c |
~6000 | Gossip协议实现 |
sentinel.c |
~5000 | Sentinel高可用 |
replication.c |
~4000 | 主从复制 |
事件系统
| 文件 | 行数 | 职责 |
|---|---|---|
ae.c |
~516 | 事件循环抽象层 |
ae_epoll.c |
~300 | Linux epoll实现 |
ae_kqueue.c |
~250 | macOS kqueue实现 |
ae_select.c |
~200 | select实现(备选) |
其他
| 文件 | 行数 | 职责 |
|---|---|---|
multi.c |
~500 | MULTI/EXEC事务 |
eval.c |
~1500 | EVAL Lua脚本 |
pubsub.c |
~800 | 发布订阅 |
zmalloc.c |
~600 | 内存分配追踪 |
module.c |
~2000 | Redis Module API |
3. 核心数据结构
3.1 server.h —— Redis的"总纲"
server.h 有4933行,是整个Redis的头文件。它定义了几乎所有核心结构体。
3.2 redisServer
redisServer 结构体保存整个服务器的运行状态:
typedef struct redisServer {
// 网络
int port; // 监听端口
int fd; // 监听socket
aeEventLoop *el; // 事件循环
int maxclients; // 最大客户端数
// 数据库
redisDb *db; // 数据库数组
int dbnum; // 数据库数量(默认16)
// 客户端
list *clients; // 所有客户端列表
list *clients_to_close; // 待关闭客户端
list *clients_pending_write; // 待写入客户端
client *current_client; // 当前正在处理的客户端
// 命令
struct redisCommand *commands; // 命令表
dict *commands_dict; // 命令字典(快速查找)
// 持久化
int rdb_child_pid; // RDB子进程PID
int aof_child_pid; // AOF子进程PID
sds aof_buf; // AOF写缓冲区
// 复制
int repl_state; // 复制状态
int repl_slaves; // 从服务器数量
// 集群
int cluster_enabled; // 是否启用集群
clusterState cluster; // 集群状态
// 统计
long long stat_numcommands; // 已执行命令数
long long stat_numconnections; // 已接受连接数
long long stat_expiredkeys; // 已过期key数
long long stat_peak_memory; // 峰值内存
// ... 数百个字段
} redisServer;
3.3 redisClient
typedef struct client {
uint32_t flags; // 客户端标志(CLIENT_SLAVE等)
int fd; // 客户端socket
sds querybuf; // 查询缓冲区
sds argv[MAX_ARGS]; // 命令参数
int argc; // 参数数量
int dbid; // 当前选择的数据库
list *reply; // 回复链表
size_t reply_bytes; // 回复缓冲区大小
size_t buf_peak; // 缓冲区峰值
mstime_t buf_peak_last_reset_time;
int bufpos; // 回复缓冲区位置
char buf[PROTO_REPLY_CHUNK_BYTES]; // 回复缓冲区(16KB)
// 超时
time_t lastinteraction; // 上次交互时间
time_t created_at; // 创建时间
// 输入/输出限制
size_t querybuf_peak;
size_t client_max_querybuf_len;
// 待执行命令(IO线程解析后)
list *pending_commands;
int tid; // IO线程ID
int running_tid; // 正在执行的线程ID
// ...
} client;
3.4 redisObject / kvobj
struct redisObject {
unsigned type:4; // OBJ_STRING / OBJ_LIST / OBJ_SET / OBJ_ZSET / OBJ_HASH / OBJ_STREAM
unsigned encoding:4; // 编码方式(RAW/INT/EMBSTR/LISTPACK/SKIPLIST等)
unsigned refcount:23; // 引用计数
unsigned iskvobj:1; // 是否是kvobj
unsigned metabits:8; // kvobj的元数据位
unsigned lru:24; // LRU时钟或LFU信息
void *ptr; // 指向实际数据的指针
};
编码方式:
| 类型 | RAW | INT | EMBSTR | LISTPACK | HT | SKIPLIST | INTSET | QUICKLIST | STREAM |
|---|---|---|---|---|---|---|---|---|---|
| String | ✓ | ✓ | ✓ | ||||||
| List | ✓ | ✓ | |||||||
| Hash | ✓ | ✓ | |||||||
| Set | ✓ | ✓ | ✓ | ||||||
| ZSet | ✓ | ✓ | |||||||
| Stream | ✓ |
4. 启动流程
4.1 main() 函数入口
// src/server.c:8065
int main(int argc, char **argv) {
// 1. 初始化随机数种子(防止哈希碰撞攻击)
unsigned int hashseed;
getRandomBytes((unsigned char*)&hashseed, sizeof(hashseed));
dictSetHashFunctionSeed(hashseed);
// 2. 初始化默认配置
initServerConfig();
// 3. 初始化各种子系统
ACLInit();
moduleInitModulesSystem();
connTypeInitialize();
keyMetaInit();
// 4. 检查是否是Sentinel模式
checkForSentinelMode(argc, argv);
// 5. 解析命令行参数,转换为配置文件格式
for (i = 1; i < argc; i++) {
// 参数转换为 -key value 格式
sdscatprintf(options, "%s ", argv[i]);
}
// 6. 加载配置文件
loadServerConfig(configfile, options);
// 7. 初始化服务器
initServer();
// 8. 初始化集群(如果启用)
clusterCommonInit();
// 9. 初始化监听socket
initListeners();
// 10. 启动后台线程
InitServerLast(); // 启动bio和IO线程
// 11. 从磁盘加载数据
loadDataFromDisk();
// 12. 进入事件循环(永不返回)
aeMain(server.el);
}
4.2 initServer()
// src/server.c:3009
void initServer(void) {
// 1. 设置信号处理器
signal(SIGHUP, SIG_IGN); // 忽略HUP
signal(SIGPIPE, SIG_IGN); // 忽略PIPE
// 2. 创建客户端管理数据结构
server.clients = listCreate();
server.clients_to_close = listCreate();
server.clients_pending_write = listCreate();
server.clients_index = raxCreate(); // 客户端ID索引
server.clients_timeout_table = raxCreate(); // 超时客户端表
// 3. 创建事件循环
server.el = aeCreateEventLoop(server.maxclients + CONFIG_FDSET_INCR);
// 4. 初始化数据库
server.db = zmalloc(sizeof(redisDb) * server.dbnum);
for (j = 0; j < server.dbnum; j++) {
redisDb *db = &server.db[j];
// 创建key空间(kvstore)
db->keys = kvstoreCreate(...);
db->expires = kvstoreCreate(...);
// 创建过期字典
db->subexpires = estoreCreate(...);
// 创建阻塞/就绪/监视key字典
db->blocking_keys = dictCreate(&blockedKeysDictType);
db->ready_keys = dictCreate(&readyKeysDictType);
db->watched_keys = dictCreate(&watchedKeysDictType);
}
// 5. 注册serverCron定时事件
// 默认每秒10次(100ms间隔)
aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL);
// 6. 设置事件循环钩子
aeSetBeforeSleepProc(server.el, beforeSleep);
aeSetAfterSleepProc(server.el, afterSleep);
}
4.3 serverCron()
// src/server.c:1571
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
// 1. 动态调整hz(根据客户端数量)
if (server.clients_per_tick > MAX_CLIENTS_PER_CLOCK_TICK) {
server.hz = min(server.hz * 2, CONFIG_MAX_HZ);
}
// 2. 每100ms:更新即时统计
run_with_period(100) {
trackOperationsPerSecond(); // 计算QPS
updateCachedTime(0); // 更新时间缓存
}
// 3. 每秒:更新LRU时钟
run_with_period(1000) {
updateLRUClock();
}
// 4. 处理客户端过期
clientsCron();
// 5. 处理数据库操作(过期键删除、rehash等)
databasesCron();
// 6. 检查是否需要BGSAVE
for (j = 0; j < server.saveparamslen; j++) {
struct saveparam *sp = &server.saveparams[j];
if (server.dirty >= sp->changes &&
unixtime()-server.lastsave > sp->seconds) {
rdbSaveBackground(server.rdb_filename);
}
}
// 7. 检查是否需要AOF重写
if (server.aof_state == AOF_ON &&
server.aof_rewrite_perc &&
server.aof_current_size > server.aof_rewrite_min_size) {
long long growth = (server.aof_current_size*100/server.aof_rewrite_base_size) - 100;
if (growth >= server.aof_rewrite_perc) {
rewriteAppendOnlyFileBackground();
}
}
// 8. 执行复制检查
run_with_period(1000) {
replicationCron();
}
// 9. 执行集群检查
run_with_period(server.cluster_enabled ? 100 : 1000) {
clusterCron();
}
// 返回下次执行间隔(毫秒)
return 1000/server.hz;
}
4.4 beforeSleep()
// src/server.c:1956
void beforeSleep(struct aeEventLoop *eventLoop) {
// 1. 发送TLS/压缩数据
handleClientsWithPendingWritesUsingThreads();
// 2. 处理集群相关操作
clusterBeforeSleep();
// 3. 处理被阻塞的客户端
blockedBeforeSleep();
// 4. 主动过期键删除(每次100个key)
int expired;
do {
expired = activeExpireCycle(ACTIVE_EXPIRE_FAST);
} while (expired > ACTIVE_EXPIRE_FAST_CYCLES * 10);
// 5. 向从服务器发送GETACK
feedReplicationBacklog();
// 6. 刷新AOF缓冲区
if (server.aof_state == AOF_ON) {
flushAppendOnlyFile(0);
}
// 7. 处理待写入客户端
handleClientsWithPendingWrites();
}
4.5 aeMain()
// src/ae.c:497
void aeMain(aeEventLoop *eventLoop) {
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS | AE_CALL_BEFORE_SLEEP | AE_CALL_AFTER_SLEEP);
}
}
4.6 aeProcessEvents()
// src/ae.c:365
int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
int processed = 0;
// 1. 调用beforesleep
if (eventLoop->beforesleep && (flags & AE_CALL_BEFORE_SLEEP))
eventLoop->beforesleep(eventLoop);
// 2. 计算最近定时器的超时时间
tvp = usUntilEarliestTimer(eventLoop);
// 3. 调用epoll_wait(或kqueue/select)
numevents = aeApiPoll(eventLoop, tvp);
// 4. 调用aftersleep
if (eventLoop->aftersleep && (flags & AE_CALL_AFTER_SLEEP))
eventLoop->aftersleep(eventLoop);
// 5. 处理就绪事件
for (j = 0; j < numevents; j++) {
int fd = eventLoop->fired[j].fd;
int mask = eventLoop->fired[j].mask;
// 先处理读事件(可能产生写数据)
if (fd != -1 && mask & AE_READABLE && !reverse) {
eventLoop->events[fd].rfileProc(eventLoop, fd,
eventLoop->events[fd].clientData, mask);
}
// 再处理写事件
if (fd != -1 && mask & AE_WRITABLE) {
eventLoop->events[fd].wfileProc(eventLoop, fd,
eventLoop->events[fd].clientData, mask);
}
processed++;
}
// 6. 处理定时器事件
processTimeEvents(eventLoop);
return processed;
}
5. 数据流:从请求到响应
客户端发送 SET key value
|
v
readQueryFromClient() [networking.c]
-> read(fd, c->querybuf, ...) 读取数据
-> processInputBuffer(c) 解析RESP协议
-> processMultibulkBuffer() 解析*3\r\n$3\r\nSET\r\n...
-> c->argv[0] = "SET"
-> c->argv[1] = "key"
-> c->argv[2] = "value"
|
v
processCommand() [server.c]
-> lookupCommand("SET") 查找命令
-> call(c, CMD_CALL_FULL) 执行命令
-> setCommand(c) [t_string.c]
-> tryObjectEncoding(val) 编码优化
-> setGenericCommand(c, flags, key, val, ...)
-> dbAdd() 添加到数据库
-> signalModifiedKey() 触发keyspace通知
|
v
addReply(c, shared.ok) [networking.c]
-> 写入 c->buf: "+OK\r\n"
|
v
writeToClient() [networking.c]
-> write(c->fd, c->buf, c->bufpos) 发送响应
6. 配置系统
6.1 加载流程
redis.conf -> loadServerConfig() -> 覆盖到 redisServer 字段
6.2 动态配置
CONFIG SET 命令可以在运行时修改配置。config.c 中的 setConfig() 函数处理这些修改。
6.3 关键默认值
| 配置项 | 默认值 | 含义 |
|---|---|---|
hz |
10 | 每秒定时器频率 |
maxclients |
10000 | 最大客户端数 |
timeout |
0 | 客户端超时(0=不超时) |
databases |
16 | 数据库数量 |
tcp-backlog |
511 | TCP连接队列大小 |
client-output-buffer-limit |
0 0 0 | 客户端输出缓冲区限制 |
7. 本篇小结
| 概念 | 要点 |
|---|---|
| 架构 | 单线程事件驱动,所有代码在src/平铺 |
| 核心结构 | redisServer保存全局状态,redisClient保存连接状态 |
| 启动流程 | main -> initServer -> aeMain(事件循环) |
| 配置系统 | redis.conf + CONFIG SET动态修改 |
| 数据流 | read -> parse -> execute -> reply |
| serverCron | 每秒10次,处理过期/统计/持久化 |
| beforeSleep | 事件循环前的维护工作 |
思考题
- Redis为什么选择单线程模型?在什么场景下这会成为瓶颈?
- server.h有4933行,这反映了Redis架构的什么特点?
- 如果要给Redis添加一个新的数据结构,需要修改哪些文件?
- aeMain的while循环中为什么要有beforesleep和aftersleep?
思考题解答
1. Redis为什么选择单线程模型?有什么限制作为瓶颈?
选择单线程的原因:
- 命令串行化:单线程执行所有命令,天然保证操作的原子性和顺序性,不需要加锁。
- 数据结构简化:不需要考虑线程安全,数据结构设计更简单高效(如不需要原子操作)。
- 避免锁竞争:多线程需要细粒度的锁,锁竞争会严重影响性能。
- CPU瓶颈不是主要问题:Redis是内存数据库,瓶颈在网络I/O而非CPU。单线程足以处理数万QPS。
单线程的限制:
- 只能使用一个CPU核心
- 阻塞操作(如key数量过多的SORT)会阻塞整个服务器
- 无法充分利用多核CPU
Redis 6.0的改进:引入了多线程I/O(io-threads),但命令执行仍然是单线程。多线程只用于网络读写,不用于命令处理。
2. server.h的4933行代码反映了Redis架构的什么特点?
- 全局状态集中:
redisServer结构体包含了服务器的所有状态(数据库、客户端、配置、统计等),是一个"上帝结构体"。 - 配置驱动:几乎所有行为都可以通过配置文件或CONFIG SET调整。
- 功能丰富:一个结构体包含了内存管理、持久化、集群、复制、Lua脚本等所有功能的字段。
- 历史积累:4933行代码反映了Redis从简单KV存储发展为全功能数据库的演进过程。
3. 如果要为Redis添加一个新的数据结构,需要修改哪些文件?
- t_xxx.c:新数据结构的核心实现(命令处理、数据操作)
- server.h:定义新的object type常量和编码方式
- object.c:创建/释放新类型的对象
- db.c:修改通用键值操作以支持新类型
- config.c:添加相关配置项(如果需要)
- networking.c:添加新命令的处理函数
- redis.conf:文档化新配置项
4. aeMain的while循环中为什么需要beforesleep和aftersleep?
beforesleep的作用:
- 处理关闭客户端的请求
- 复制积压缓冲区(repl_backlog)
- 更新统计信息
- 触发AOF重写
- 执行主动过期键删除
aftersleep的作用:
- 处理被阻塞的客户端(BLPOP等)
- 更新时间缓存
设计目的:将一些需要定期执行但不适合放入定时器的任务放在事件循环的特定阶段执行,确保在每次事件处理前后都有机会执行维护工作。

浙公网安备 33010602011771号