Redis中单机数据库的实现 (3)

在anet.h与anet.c中, 对*nix Tcp Socket相关接口进行了一层薄封装, 基本上就是在原生接口的基础上做了一些错误处理, 并整合了一些接口调用. 这部分代码很简单, 甚至很无聊.

4. 单机数据库的启动

Redis进程中, 有一个全局变量 struct redisServer server, 这个变量描述的就是Redis服务端. (结构体定义位于server.h中, 全局变量的定义位于server.c中), 结构体的定义如下:

struct redisServer { // ... aeEventLoop * el; // 事件处理器 // ... redisDb * db; // 数据库数组. // ... int ipfd[CONFIG_BINDADDR_MAX]; // listening tcp socket fd int ipfd_count; int sofd; // listening unix socket fd // ... list * clients; // 所有活动客户端 list * clients_to_close; // 待关闭的客户端 list * clients_pending_write; // 待写入的客户端 // ... client * current_clients; // 当前客户端, 仅在crash报告时才使用的客户端 int clients_paused; // 暂停标识 // ... list * clients_waiting_acks; // 执行WAIT命令的客户端 // ... uint64_t next_client_id; // 下一个连接至服务端的客户端的编号, 自增编号 }

这个结构体中的字段实再是太多了, 这里仅列出一些与单机数据库相关的字段

结构体中定义了一个指针字段, 指向struct redisDb数组类型. 这个结构的定义如下(也位于server.h中)

typedef struct redisDb { dict *dict; /* 数据库的键空间 */ dict *expires; /* 有时效的键, 以及对应的过期时间 */ dict *blocking_keys; /* 队列: 与阻塞操作有关*/ dict *ready_keys; /* 队列: 与阻塞操作有关 */ dict *watched_keys; /* 与事务相关 */ int id; /* 数据库编号 */ long long avg_ttl; /* 统计值 */ } redisDb;

在介绍阻塞操作与事务之前, 我们只需要关心三个字段:

dict 数据库中的所有k-v对都存储在这里, 这就是数据库的键空间

expires 数据库中所有有时效的键都存储在这里

id 数据库的编号

从数据结构上也能看出来, 单机数据库的启动其实需要做以下的事情:

初始化全局变量server, 并初始化server.db数组, 数组中的每一个元素就是一个数据库

创建一个事件处理器, 用于监听来自用户的命令, 并进行处理

在server.c文件中的main函数跟下去, 就能看到上面两步, 这里的代码很繁杂, 下面只选取与单机数据库启动相关的代码进行展示:

int main(int argc, char **argv) { // 初始化基础设计 // ... server.sentinel_mode = checkForSentinelMode(argc,argv); // 从命令行参数中解析, 是否以sentinel模式启动server initServerConfig(); // 初始化server配置 moduleInitModulesSystem(); // 初始化Module system // ... if (server.sentinel_mode) { // sentinel相关逻辑 initSentinelConfig(); initSentinel(); } // 如果以 redis-check-rdb 或 redis-check-aof 模式启动server, 则就是调用相应的 xxx_main()函数, 然后终止进程就行了 if (strstr(argv[0],"redis-check-rdb") != NULL) redis_check_rdb_main(argc,argv,NULL); else if (strstr(argv[0],"redis-check-aof") != NULL) redis_check_aof_main(argc,argv); // 处理配置文件与命令行参数, 并输出欢迎LOGO... // ... // 判断运行模式, 如果是后台运行, 则进入daemonize()中, 使进程守护化 server.supervised = redisIsSupervised(server.supervised_mode); int background = server.daemonize && !server.supervised; if (background) daemonize(); // 单机数据库启动的核心操作: 初始化server initServer(); // 琐事: 创建pid文件, 设置进程名, 输出ASCII LOGO, 检查TCP backlog队列等 //... if(!server.sentinel_mode) { // 加载自定义Module // ... } else { sentinelIsRunning(); } // 检查内存状态, 如有必要, 输出警告日志 // ... // 向事件处理器注册事前回调与事后回调, 并开启事件循环 // 至此, Redis服务端已经启动 aeSetBeforeSleepProc(server.el,beforeSleep); aeSetAfterSleepProc(server.el,afterSleep); aeMain(server.el) // 服务端关闭的善后工作 aeDeleteEventLoop(server.el); return 0; }

从main函数中能大致了解启动流程, 也基本能猜出来, 在最核心的initServer函数的调用中, 至少要做两件事:

初始化事件处理器server.el

初始化服务端的数据库server.db

初始化监听socket, 并将监听socket fd加入事件处理回调

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpsydf.html