对于redis框架的理解(二)

之前梳理过redis main函数主体流程

`大体是 initServerConfig() -> loadServerConfig()

-> daemonize() -> initServer() -> aeSetBeforeSleepProc()

->aeMain() -> aeDeleteEventLoop();

initServerConfig() 初始化server的配置

loadServerConfig()会从配置文件里加载对应的配置

daemonize()创建守护进程

看一下daemonize的函数组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void daemonize(void) {
  int fd;

  if (fork() != 0) exit(0); /* parent exits */
  //设置为首进程
  //之前parent和child运行在同一个session里,

  //parent是会话(session)的领头进程,
  //parent进程作为会话的领头进程,如果exit结束执行的话,

  //那么子进程会成为孤儿进程,并被init收养。
  //执行setsid()之后,child将重新获得一个新的会话(session)id。
  setsid(); /* create a new session */

  /* Every output goes to /dev/null. If Redis is daemonized but
  * the 'logfile' is set to 'stdout' in the configuration file
  * it will not log at all. */

  // /dev/null相当于黑洞文件,所有写入他的数据都会消失,

  //从他里面读不出数据
  if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
    dup2(fd, STDIN_FILENO); //将标准输入重定向到fd
    dup2(fd, STDOUT_FILENO); //将标准输出重定向到fd
    dup2(fd, STDERR_FILENO); //将错误输出重定向到fd
    if (fd > STDERR_FILENO)

      close(fd);

   }
}

着重解释下dup2这个函数

int dup(int oldfd);

复制oldfd所指向的文件描述符,返回系统目前未使用的最小的文件描述符。

新的文件描述符和oldfd指向一个文件,

他们共享读写,枷锁等权限当一个文件描述符操作lseek,另一个也会随着偏移

但是他们不共享close-on-exec

int dup2(int oldfd, int newfd);

复制oldfd到newfd,如果newfd指向的文件打开,那么会关闭该文件。

dup2失败后返回-1,成功则共享文件状态。

接下来看看initServer函数做了些什么 下面是initServer内部的几个步骤

//由于initserver是守护进程,忽略sighup
//sighub在控制终端关闭的时候会发给session首进程
//session首进程退出时,该信号被发送到该session中的前台进程组中的每一个进程
signal(SIGHUP, SIG_IGN);

//写管道发现读进程终止时产生sigpipe信号,

//写已终止的SOCK_STREAM套接字同样会产生此信号
signal(SIGPIPE, SIG_IGN);

1
2
3
4
5
6
7
8
9
10
11
12
server.pid = getpid();
server.current_client = NULL;
server.clients = listCreate(); //创建客户队列
server.clients_to_close = listCreate(); //创建关闭队列
server.slaves = listCreate(); //创建从机队列
server.monitors = listCreate(); //创建监控队列
server.slaveseldb = -1; /* Force to emit the first SELECT command. */
server.unblocked_clients = listCreate(); //创建非堵塞客户队列
server.ready_keys = listCreate(); //创建可读键队列
server.clients_waiting_acks = listCreate(); //客户等待回包队列
server.get_ack_from_slaves = 0;
server.clients_paused = 0;

//创建别的模块公用的对象
createSharedObjects(); //创建共享对象
adjustOpenFilesLimit(); //改变可打开文件的最大数量

可以看看adjustOpenFilesLimit() 里边做了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
void adjustOpenFilesLimit(void) {
    rlim_t maxfiles = server.maxclients+REDIS_MIN_RESERVED_FDS;
    struct rlimit limit;

    //获取每个进程能创建的各种系统资源的最大数量
    if (getrlimit(RLIMIT_NOFILE,&limit) == -1) {
    redisLog(REDIS_WARNING,"Unable to obtain the current NOFILE limit (%s),

    assuming 1024 and setting the max clients configuration accordingly.",
    strerror(errno));
    //1024 - 目前redis最小保留的用于其他功能的描述符
    server.maxclients = 1024-REDIS_MIN_RESERVED_FDS;
    } else {
        //系统当前(软)限制
        rlim_t oldlimit = limit.rlim_cur;

        /* Set the max number of files if the current limit is not enough
        * for our needs. */
        if (oldlimit < maxfiles) {
        //最大软件限制
        rlim_t bestlimit;
        int setrlimit_error = 0;

        /* Try to set the file limit to match 'maxfiles' or at least
        * to the higher value supported less than maxfiles. */
        //尝试设置限制数符合maxfiles或者最接近
        bestlimit = maxfiles;
        //循环判断,如果最大的连接数大于当前系统能使用的最大软件限制
        while(bestlimit > oldlimit) {
        //设置每次递减的数量
        rlim_t decr_step = 16;
        //设置当前软件限制
        limit.rlim_cur = bestlimit;
        limit.rlim_max = bestlimit;
        //设置成功则断开,不成功继续循环,找到最接近的最大限制
        if (setrlimit(RLIMIT_NOFILE,&limit) != -1) break;
        //设置限制错误码
        setrlimit_error = errno;

        /* We failed to set file limit to 'bestlimit'. Try with a
        * smaller limit decrementing by a few FDs per iteration. */

        //最大限制递减,如果小于规定值那么退出循环
        if (bestlimit < decr_step) break;
        bestlimit -= decr_step;
            /* Assume that the limit we get initially is still valid if

      * our last try was even lower. */
      //最大连接数小于当前系统允许的资源数量,那么扩充为系统允许的数量
      if (bestlimit < oldlimit) bestlimit = oldlimit;

      //这个条件用于处理最大连接数
      if (bestlimit < maxfiles) {
      //保存当前服务器客户队列最大数量
        int old_maxclients = server.maxclients;
        //服务器客户队列最大数量
        server.maxclients = bestlimit-REDIS_MIN_RESERVED_FDS;
        if (server.maxclients < 1) {
          redisLog(REDIS_WARNING,"Your current 'ulimit -n' "
          "of %llu is not enough for Redis to start. "
          "Please increase your open file limit to at least "
          "%llu. Exiting.",
          (unsigned long long) oldlimit,
          (unsigned long long) maxfiles);
          exit(1);
        }

   }

回到initServer函数内部

adjustOpenFilesLimit()函数过后是调用

server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);

这个函数是创建基本的时间循环结构。这个api写在Ae.c中,这个文件主要负责创建事件轮询的结构,创建文件读写事件,删除文件读写事件,删除事件轮询结构,派发文件读写功能等,下一节再讲。

然后是创建了一个定时器回调函数

if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) { redisPanic("Can't create the serverCron time event."); exit(1); }

接着创建了 TCP的回调函数,主要用于有新的连接到来触发acceptTcpHandler

for (j = 0; j < server.ipfd_count; j++) { if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler,NULL) == AE_ERR) { redisPanic( "Unrecoverable error creating server.ipfd file event."); } }

//然后添加了udp的回调
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE, acceptUnixHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.sofd file event.");

这就是initServer大体的流程