说说 redis 的启动流程。html
首先要找到启动函数,咱们知道 C 程序从 main
函数开始,因此,就找到了“梦想”开始的地方 server.c
-> main
。
这里主要讲启动过程当中的主要部分,因此并不会一一涉及到。node
整个代码中最重要的结构体莫过于 struct redisServer server
,它以一个全局变量的形式出现。本函数主要是对它的成员进行赋值操做,这些成员基本上是能够经过 redis.conf 文件来配置。redis
好比:数据库
server 字段 | 含义 |
---|---|
runid | 节点标识占用 40B |
port | 启动端口默认为 6379 |
tcp_backlog | 默认 511B |
aof_fsync | 默认 aof 每秒刷盘,可是 aof 默认关闭 |
aof_filename | 默认 aof 文件名为 appendonly.aof |
rdb_filename | 默认 rdb 文件名为 dump.rdb |
cluster_node_timeout | 默认 15s,默认 cluster 模式关闭 |
appendServerSaveParams(60 * 60,1); /* save after 1 hour and 1 change */ appendServerSaveParams(300,100); /* save after 5 minutes and 100 changes */ appendServerSaveParams(60,10000); /* save after 1 minute and 10000 changes */
包含对 backlog 的相关设置。安全
浮点数据精度设置。服务器
一共有三种类型,以下:app
clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = { {0, 0, 0}, /* normal */ {1024*1024*256, 1024*1024*64, 60}, /* slave */ {1024*1024*32, 1024*1024*8, 60} /* pubsub */ };
初始化 redis 命令表放到 server.commands
中,这主要是在 populateCommandTable
函数中完成的。tcp
注意:考虑到在 redis.conf 配置文件中可使用 rename-command 来对 Command 进行重命名(一般是为了安全考虑而禁用某些命令),所以命令表保存了两份,即 server.commands
和 server.orig_commands
。ide
同时还对一些常常查询的命令单独提出来,分别放到如下变量中,函数
struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand, *rpopCommand, *sremCommand, *execCommand;
默认时间为 10ms。
如下方式进行该模式的开启:
int checkForSentinelMode(int argc, char **argv) { int j; if (strstr(argv[0],"redis-sentinel") != NULL) return 1; for (j = 1; j < argc; j++) if (!strcmp(argv[j],"--sentinel")) return 1; return 0; }
使用命令行参数 --sentinel
,或者直接使用二进制文件 redis-sentinel
。
若是开启了该模式,那么进行相应的初始,没开启就跳过。
if (server.sentinel_mode) { initSentinelConfig(); // sentinel 默认端口 26379 initSentinel(); // sentinel 变量赋初值 }
主要仍是得到配置文件的绝对路径 server.configfile = getAbsolutePath(configfile)
。
配置文件的载入有专门的函数
void loadServerConfig(char *filename, char *options){}
载入配置文件后,会覆盖以前对于 server 的某些默认配置。实际上,当 redis-server 启动后,一些配置能够经过 config get
命令查看,也能够经过 config set
命令进行修改,修改后 config rewrite
刷盘。
不一样于 initServerConfig
函数,该函数主要初始化一些 redis-server 运行中的成员。
经过 redis 来复习下信号处理。
// 忽略SIGHUP和SIGPIPE信号 signal(SIGHUP, SIG_IGN); signal(SIGPIPE, SIG_IGN);
void setupSignalHandlers(void) { struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = sigShutdownHandler; sigaction(SIGTERM, &act, NULL); sigaction(SIGINT, &act, NULL); return; }
主要是程序退出的善后工做。
if (server.syslog_enabled) { openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT, server.syslog_facility); }
前提是使用到了系统的 rsyslog。
该函数把一些经常使用的字符串保存起来,目的就是为了减小不断申请释放时CPU时间,内存碎片等等。
好比 shared.ok = createObject(OBJ_STRING,sdsnew("+OK\r\n"))
。
额外说明的是,这里还初始化了一个很大的共享数字对象,0 到 999。所以在设置 value 时可使用这些数字能够减小内存的使用。
#define OBJ_SHARED_INTEGERS 10000 for (j = 0; j < OBJ_SHARED_INTEGERS; j++) { // 10000 个数字 shared.integers[j] = createObject(OBJ_STRING,(void*)(long)j); shared.integers[j]->encoding = OBJ_ENCODING_INT; }
struct sharedObjectsStruct shared
也是一个全局变量。
该函数根据配置文件中配置的最大 client 数量增大能够打开的最多文件数。
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR)
这里假设 io 多路复用使用的是 epoll,这也是用的最多的。
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
数据库对象 struct redisDb
,有 16 个。
if (server.port != 0 && listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR) exit(1);
监听 server.port
,并把返回的 fd 存储在 server.ipfd
中,有报错就返回。
if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) { serverPanic("Can't create the serverCron time event."); exit(1); }
注册定时时间,绑定回调函数 serverCron
,在该函数中咱们能够看到,执行周期为 1000/server.hz
ms,所以每秒会执行server.hz
(该值用户可配)。
那为何是这个频率呢?redis 中对于事件处理在以前的一篇博客中写过,能够参考下 Redis 中的事件,这里也能够简单回顾下。
时间事件处理函数 ae.c
-> processTimeEvents
中,会根据时间事件的回调返回值来决定这时一个周期事件仍是一次性事件,即
{ int retval; id = te->id; retval = te->timeProc(eventLoop, id, te->clientData); processed++; if (retval != AE_NOMORE) { aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms); } else { te->id = AE_DELETED_EVENT_ID; } }
for (j = 0; j < server.ipfd_count; j++) { if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, // 监听可读事件 acceptTcpHandler,NULL) == AE_ERR) { serverPanic( "Unrecoverable error creating server.ipfd file event."); } }
接收用户请求(用户链接会从这里进来),监听可读事件,注册回调函数 acceptTcpHandler
。
若是开启了 cluster mode,会进行相应的初始化。
if (server.cluster_enabled) clusterInit();
replicationScriptCacheInit(); scriptingInit(1); slowlogInit(); latencyMonitorInit(); bioInit();
这个函数很实用的,方便 ps 看到良好格式的进程名。一块儿来复习下。
void redisSetProcTitle(char *title) { #ifdef USE_SETPROCTITLE char *server_mode = ""; if (server.cluster_enabled) server_mode = " [cluster]"; else if (server.sentinel_mode) server_mode = " [sentinel]"; setproctitle("%s %s:%d%s", title, server.bindaddr_count ? server.bindaddr[0] : "*", server.port, server_mode); #else UNUSED(title); #endif }
若是不是以 sentinel 模式启动的,那么会加载持久化的数据,处理函数为 loadDataFromDisk
。
若是开启了 aof,那么就加载 aof 文件,不然加载 rdb 文件。
该函数用来记载 aof 文件,主要流程就是建立一个伪客户端,从 aof 文件中解析出来命令,让 server 从新执行一遍。
if (buf[0] != '*') goto fmterr; // 判断协议是否正确 if (buf[1] == '\0') goto readerr; // 判断数据完整判断 argc = atoi(buf+1); if (argc < 1) goto fmterr; argv = zmalloc(sizeof(robj*)*argc); // argc 个 robj 对象 fakeClient->argc = argc; fakeClient->argv = argv; for (j = 0; j < argc; j++) { if (fgets(buf,sizeof(buf),fp) == NULL) { // 每行最多 128B fakeClient->argc = j; /* Free up to j-1. */ freeFakeClientArgv(fakeClient); goto readerr; } if (buf[0] != '$') goto fmterr; len = strtol(buf+1,NULL,10); // 命令的长度 argsds = sdsnewlen(NULL,len); if (len && fread(argsds,len,1,fp) == 0) { sdsfree(argsds); fakeClient->argc = j; /* Free up to j-1. */ freeFakeClientArgv(fakeClient); goto readerr; } argv[j] = createObject(OBJ_STRING,argsds); if (fread(buf,2,1,fp) == 0) { // \r\n fakeClient->argc = j+1; /* Free up to j. */ freeFakeClientArgv(fakeClient); goto readerr; /* discard CRLF */ } } cmd = lookupCommand(argv[0]->ptr); if (!cmd) { serverLog(LL_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr); exit(1); } // 用 fakeClient 执行命令 cmd->proc(fakeClient);
以上函数就是 aof 文件解析过程。
附上一段 redis 协议数据,方便分析函数。
*3 $3 SET $2 xx $2 yy *3
注意:在加载 aof 文件过程当中,会暂时关闭 aof。
该函数用来加载 rdb 文件。与 aof 加载不一样的是,解析 rdb 文件后直接放入内存中。
// 进入事件循环以前执行 beforeSleep() 函数 aeSetBeforeSleepProc(server.el,beforeSleep); // 开始事件循环 aeMain(server.el); // 服务器关闭,删除事件循环 aeDeleteEventLoop(server.el);
画了一个流程图,能够很好的体现以上流程。