• 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!

  • 相关阅读:
    海量数据框架变迁——阿里巴巴上市背后的技术力量
    redis集群配置
    【等待优化】sql server CXPACKET 等待 导致 CPU飙高、CPU100%
    (4.39)sql server如何配置分布式事务(MSDTC)
    mysql断电,mysql ibdata 文件损坏(批量利用mysql表空间导入导出)
    mysqlfrm使用
    (1.1)zabbix 基础概念及工作原理整理【转】
    (5.17)mysql集群技术概述(LVS、Keepalived、HAproxy)
    事务日志备份失败错误:Backup detected log corruption in database
    sql server事务日志解析工具(开源,类似apexsql log)
  • 原文地址:https://www.cnblogs.com/liuhao/p/2526943.html
Copyright © 2020-2023  润新知