redis源码笔记 - initServer
initServer是redis对server进行初始化的入口,其由main调用,位于initServerConfig、命令行参数解析、守护进程判定之后,是server最重要的入口点。
尽管代码看似简单(102行代码,且大量的赋值语句),但顺藤摸瓜,有很多点值得仔细看看。接下来逐行分析:
函数第一件事是对信号进行处理:
899 signal(SIGHUP, SIG_IGN); 900 signal(SIGPIPE, SIG_IGN); 901 setupSignalHandlers();
redis多作为守护进程运行,这时其不会有控制终端,首先忽略掉SIGHUP信号。(见APUE2 237页);
SIGPIPE信号是在写管道发现读进程终止时产生的信号,写已终止的SOCK_STREAM套接字同样会产生此信号。redis作为server,不可避免的会遇到各种各样的client,client意外终止导致产生的信号也应该在server启动后忽略掉;
setupSignalHandlers函数处理的信号分两类:
1)SIGTERM
SIGTERM是kill命令发送的系统默认终止信号。也就是我们在试图结束server时会触发的信号。对这类信号,redis并没有立即终止进程,其处理行为是,设置一个server.shutdown_asap,然后在下一次执行serverCron时,调用prepareForShutdown做清理工作,然后再退出程序。这样可以有效的避免盲目的kill程序导致数据丢失,使得server可以优雅的退出。
2)SIGSEGV、SIGBUS、SIGFPE、SIGILL
上述信号分别为无效内存引用(即我们常说的段错误),实现定义的硬件故障,算术运算错误(如除0)以及执行非法硬件指令。这类是非常严重的错误,redis的处理是通过sigsegvHandler,记录出错时的现场、执行必要的清理工作,然后kill自身。
除上面提到的7个信号意外,redis不再处理任何其他信号,均保留默认操作。
接下来,initServer通过四行代码设置日志设施,如下:
903 if (server.syslog_enabled) { 904 openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT, 905 server.syslog_facility); 906 }
记录自己的线程ID:
908 server.mainthread = pthread_self();
然后将当前处理的client(current_client)设置为NULL,将clients、slaves、monitors、unblocked_clients通通初始化为空的list。
接下来,调用createSharedObjects(),完成共同object的初始化。这里解释下这个函数。redis在初始化时会把后续server执行过程中普遍需要的对象构造出来,如对执行成功的反馈值“+OK”,特定类型的错误值“+-ERR no such key\r\n”等等,这些对象多用在与客户端的响应的纯文本协议之中,现在版本共有30+,避免了临时申请对象的开销,同时也简化了资源管理。
在执行此函数后,将会初始化事件循环server.el以及维护db所需要的数据结构,代码如下:
915 server.el = aeCreateEventLoop(); 916 server.db = zmalloc(sizeof(redisDb)*server.dbnum);
aeCreateEventLoop函数已经在介绍redis事件框架ae.c时提到了(http://www.cnblogs.com/liuhao/archive/2012/05/15/2502322.html),这里不再赘述。
接下来,初始化监听的连接,包括SOCK_STREAM和UNIX_STREAM,如果创建失败,或是均未设置,则退出程序的执行流程。
918 if (server.port != 0) { 919 server.ipfd = anetTcpServer(server.neterr,server.port,server.bindaddr); 920 if (server.ipfd == ANET_ERR) { 921 redisLog(REDIS_WARNING, "Opening port %d: %s", 922 server.port, server.neterr); 923 exit(1); 924 } 925 } 926 if (server.unixsocket != NULL) { 927 unlink(server.unixsocket); /* don't care if this fails */ 928 server.sofd = anetUnixServer(server.neterr,server.unixsocket,server.unixsocketper m); 929 if (server.sofd == ANET_ERR) { 930 redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr); 931 exit(1); 932 } 933 } 934 if (server.ipfd < 0 && server.sofd < 0) { 935 redisLog(REDIS_WARNING, "Configured to not listen anywhere, exiting."); 936 exit(1); 937 }
接下来,程序初始化server的db数据结构,如下:
938 for (j = 0; j < server.dbnum; j++) { 939 server.db[j].dict = dictCreate(&dbDictType,NULL); 940 server.db[j].expires = dictCreate(&keyptrDictType,NULL); 941 server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL); 942 server.db[j].watched_keys = dictCreate(&keylistDictType,NULL); 943 if (server.vm_enabled) 944 server.db[j].io_keys = dictCreate(&keylistDictType,NULL); 945 server.db[j].id = j; 946 }
这里,对db数据结构内的各个dict类型加以说明。
db.dict的类型是dbDictType,它是数据库所有数据的总的存储和索引,存的是string->redisObject的一个映射,比如简单的key-value,那么redisObject就是一个string,存储链表结构,redisObject保存的就是链表。
db.expires的类型是keyptrDictType,它存储的是设置了超时的key和对应的超时时间,即string->time_t的一个映射,这在介绍redis对过期值的处理时有所介绍(http://www.cnblogs.com/liuhao/archive/2012/05/25/2518185.html)。
db.blocking_keys和db.watched_keys均是keylistDictType类型,对应的value是list类型,key是redisObject。其value链表中存的是一系列client,表示特定redisObject状态有变化时(如执行BLPOP,队列中有新的元素即为状态有变化)通知list中的所有客户端。
因为新版中vm已经彻底废弃,所以和vm相关联的代码都略过不表。
在对db的数据结构进行初始化后,对pubsub_channels进行了初始化,pubsub_channels同样是keylistDictType的dict,用来记录订阅的所有client。
然后对pubsub_patterns进行了初始化。(这里插一句,redis的pubsub是个极其简陋的实现,对持久化、网络瞬断均无处理,不推荐在项目中使用)
然后将两个后台save子进程(bgsavechildpid和bgrewritechildpid)的pid初始化为-1,将用于aof和rewrite的buf初始化为empty的字符串,然后初始化了一系列的统计信息,略去不表。
有两点需要解释下:
957 server.dirty = 0;
用来后续计算server维护的数据是否有更新,如果有,需要记录aof和通知replication.
967 server.unixtime = time(NULL);
用于时间值保留,其精度为s,类似于一个缓存。redis的代码中有很多需要时间值的地方,只要其精度要求不是很高,server.unixtime又有合理的机制进行更新,就可以避免在每次需要时间值的时候执行昂贵的time系统调用。
接下来,注册serverCron函数,这是个定期执行的函数,执行周期是100ms,这个函数也是个重点,以后会专门介绍。这里注册是在1ms后调度serverCron,但:-),这里其实运行起来并不要求(保证)1ms后serverCron一定被调用,aeCreateTimeEvent只是注册函数,真正何时执行取决于initServer执行后aeMain函数的执行,该函数触发事件循环真正转起来。
968 aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL);
然后,initServer将监听的描述符(ipfd - TCP or sofd - UNIX_STREAM)加入事件监控列表,这里以ipfd举例:
969 if (server.ipfd > 0 && aeCreateFileEvent(server.el,server.ipfd,AE_READABLE, 970 acceptTcpHandler,NULL) == AE_ERR) oom("creating file event");
在有连接请求进来后,acceptTcpHandler将会被调用,该函数调用accept接收连接,然后用accept函数返回的文件描述符创建一个client桩(一个redisClient对象),在server端代表连接进来的真正client。在创建client桩的时候,会将返回的这个描述符同样添加进事件监控列表,监控READABLE事件,事件发生代表着客户端发送数据过来,此时调用readQueryFromClient接收客户端的query。
在创建上述监听时间后,如果server设置了aof模式做持久化,将会打开对应的文件,保存相关的描述符,代码如下:
974 if (server.appendonly) { 975 server.appendfd = open(server.appendfilename,O_WRONLY|O_APPEND|O_CREAT,0644); 976 if (server.appendfd == -1) { 977 redisLog(REDIS_WARNING, "Can't open the append-only file: %s", 978 strerror(errno)); 979 exit(1); 980 } 981 }
接下来,对于32位架构的系统,如果没有设置最大内存占用限制(maxmemory),则将此限制设定为3.5G,并把maxmemory_policy设置为REDIS_MAXMEMORY_NO_EVICTION,表示在程序达到最大内存限制后,拒绝后续会增大内存使用的客户端执行的命令。不过redis作为一个内存大杀器,3.5G、32位系统实在已经无法满足日益增长的需求了。
函数执行最后,初始化slowlog,bio和一个随机数种子。
slowlogInit()参见http://www.cnblogs.com/liuhao/archive/2012/05/20/2510725.html
bioInit()参见http://www.cnblogs.com/liuhao/archive/2012/05/17/2506810.html
旅程到此为止,over!