乐趣区

关于redis:Redis启动会做哪些操作

Redis server 启动后会做哪些操作?

Redis 是典型的 Client-Server 架构,一旦 Redis 实例开始运行,Redis server 也就会启动,而 main 函数其实也会负责 Redis server 的启动运行。

Redis 针对以下三个问题的实现思路:

Redis server 启动后具体会做哪些初始化操作?
Redis server 初始化时有哪些要害配置项?
Redis server 如何开始解决客户端申请?

(1) Redis Server 启动

源码地址 https://github.com/redis/redi…

//file: src/server.c

// #L5297  5297 行
int main(int argc, char **argv) {

  // 省略局部代码... 

  // 1. 启动 RedisServer 时 初始化配置及资源
  initServer();

  // 2. 循环解决申请及事件,直到服务器敞开为止
  aeMain(server.el); 

}

(1.1) 启动 RedisServer 初始化配置及资源

在 initServer 这个函数内,Redis 做了这么三件重要的事件。

1、创立一个 epoll 对象
2、对配置的监听端口进行 listen
3、把 listen socket 让 epoll 给治理起来


void initServer(void) {

    // 1. 创立 epoll
    server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);

    // 2. 监听端口
    listenToPort(server.port,server.ipfd,&server.ipfd_count)

    // 3. 注册 accept 事件处理器 (只是注册 前面会用到)
    for (j = 0; j < server.ipfd_count; j++) {aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
            acceptTcpHandler,NULL)
    }

}

(1.2) 循环解决申请

// file: src/ae.c 

/**
 * 循环处理事件
 * 
 * @param *eventLoop 
 */ 
void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    // 循环处理事件
    while (!eventLoop->stop) {
        // 处理事件
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|
                                   AE_CALL_BEFORE_SLEEP|
                                   AE_CALL_AFTER_SLEEP);
    }
}
// file: src/ae.c 

/**
 * 处理事件
 * 
 * @param *eventLoop
 * @param flags
 */ 
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    // 省略局部细节...

    // 调用多路复用 API 获取就绪事件
    numevents = aeApiPoll(eventLoop, tvp);

    // 解决写事件
    fe->wfileProc(eventLoop,fd,fe->clientData,mask);
    // 解决读事件
    fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                        
}

其实整个 Redis 的工作过程,就只须要了解分明 main 函数中调用的 initServeraeMain 这两个函数就足够了。

(2) RedisServer 网络申请解决流程

(2.1) 绑定地址并监听套接字 (bind listen)

// file:  src/server.c 

// 监听端口
/**
 * @param port
 * @param *fds
 * @param *count
 */
int listenToPort(int port, int *fds, int *count) {

    // ... 

    for (j = 0; j < server.bindaddr_count || j == 0; j++) {

        // ...  省略 绑定 IPV6 IPV4 的细节  anetTcp6Server  anetTcpServer

        // 绑定
        fds[*count] = anetTcpServer(server.neterr,port,NULL,
                    server.tcp_backlog);

        // ...  

    }
    return C_OK;
}

Redis 是反对开启多个端口的,所以在 listenToPort 中咱们看到是启用一个循环来调用 anetTcpServer。
在 anetTcpServer 中,逐渐会开展调用,直到执行到 bind 和 listen 零碎调用。

// file:  src/anet.c 

/**
 * @param *err
 * @param port
 * @param *bindaddr
 * @param backlog
 */
int anetTcpServer(char *err, int port, char *bindaddr, int backlog)
{return _anetTcpServer(err, port, bindaddr, AF_INET, backlog);
}


/**
 * @param *err
 * @param port
 * @param *bindaddr
 * @param af
 * @param backlog
 */
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog)
{
    int s = -1, rv;
    char _port[6];  /* strlen("65535") */
    struct addrinfo hints, *servinfo, *p;

    // 创立套接字
    s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)

    // 设置端口重用
    anetSetReuseAddr(err,s)

    // 监听
    anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog)
/**
 * 监听
 * 
 * @param *err
 * @param s 对应创立的套接字 fd
 * @param *sa socket 地址信息 (协定 地址)
 * @param len
 * @param backlog 
 */
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog) {
    // 绑定 ip 端口
    bind(s,sa,len)

    // 监听套接字
    listen(s, backlog)

    return ANET_OK;
}

(2.2) 和客户端建设连贯 (accept)

// file: src/networking.c 

/**
 * 接管 tcp 处理器
 * 
 * @param *el
 * @param fd
 * @param *privdata
 * @param mask
 */
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {

        // ... 

        // 接管 tcp 申请
        cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);

        // ... 

        // 接管通用解决
        acceptCommonHandler(connCreateAcceptedSocket(cfd),0,cip);
}

(2.2.1) 接管 tcp 申请 -anetTcpAccept

// file: src/anet.c 

/**
 * 接管 tcp 申请 
 * 
 * @param *err
 * @param s fd
 * @param *ip
 * @param ip_len
 * @param *port 
 */
int anetTcpAccept(char *err, int s, char *ip, size_t ip_len, int *port) {
    int fd;
    struct sockaddr_storage sa; // 套接字地址存储构造体 
    socklen_t salen = sizeof(sa);

    // 接管申请
    fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)
        
    // ... 
    return fd;
}
// file: src/anet.c 

/**
 * @param *err
 * @param s
 * @param *sa
 * @param *len
 */ 
static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *len) {
    int fd;
    while(1) {
        // 接管 socket 数据
        // fd 是 socket 返回的 socket,指向的定义的 SOCKADDR_IN 构造体指针,指针的大小
        fd = accept(s,sa,len);
        // ...
        break;
    }
    return fd;
}

参考资料

Redis 高性能 IO 模型
https://weikeqin.com/2022/01/…

Redis 源码分析与实战 学习笔记 Day8 08 | Redis server 启动后会做哪些操作?
https://time.geekbang.org/col…

退出移动版