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!

posted @ 2012-05-30 21:23  刘浩de技术博客  阅读(4365)  评论(2编辑  收藏  举报