【Redis】redis介绍-启动过程

在reidis的系列文章中,这是第二篇。在之前有篇文章介绍了redis的使用,主要是讲解了在redis中通过redis提供的特性如何使用如何pub/sub模式,基于python给出了简单的代码示例和介绍。在这篇文章中,对redis的启动过程给出一个梳理,对redis的启动过程有一个宏观上的概念。

redis从本质上讲是一个单进程的服务,通过一个进程对外提供请求。但是在整个redis体系中存在多个进程,一些核心操作是通过进程的方式执行的。比如核心的接受客户端命令执行相应操作是一个单独的进程在执行;一些log的写入是一个进程等。这篇文章不对这个多进程的执行方式详细展开,主要是对redis中启动过程中都执行了什么操作进行一个梳理。

redis的启动入口是在redis.c中,在redis.c中可以看到main函数,这就是redis的启动的入口。所有代码分析都是从这里展开的。redis在启动过程中其实可以归结为几大操作,初始化配置文件,初始化redis支持的命令、初始化服务器。其中核心操作是在初始化服务器里面,包括socket监听、各种监听事件的建立等。

初始化配置

当我们输入redis启动命令,redis首先会设置语言类型、初始化以后hash所要使用的种子,然后通过调用initServerConfig初始化配置redisServer,把所有属性都初始化为默认值
对于redis这样一个复杂的服务器来说,其可支持的配置也是很多。因此对于配置的初始化,redis在实现中单独拿了出来,通过一个方法调用,把配置都初始化出来,初始化成默认值,然后在具体的使用中,再对具体的值进行修改。

初始化命令

在初始化配置中,还会对redis所支持的命令进行初始化操作。对reidis的命令的初始化放在初始化配置中是因为redis的命令可以在配置中进行重命名操作,所以这也是初始化配置的一部分,因此两者在一个方法里完成了。在redis.c中,定义了一个巨大的数组redisCommandTable,数组的元素是结构体redisCommand。这个结构体封装了redis的命令的属性。
struct redisCommand {
    char *name;
    redisCommandProc *proc;
    int arity;
    char *sflags; /* Flags as string representation, one char per flag. */
    int flags;    /* The actual flags, obtained from the 'sflags' field. */
    /* Use a function to determine keys arguments in a command line.
     * Used for Redis Cluster redirect. */
    redisGetKeysProc *getkeys_proc;
    /* What keys should be loaded in background when calling this command? */
    int firstkey; /* The first argument that's a key (0 = no keys) */
    int lastkey;  /* The last argument that's a key */
    int keystep;  /* The step between first and last key */
    long long microseconds, calls;
};

在大数组redisCommandTable里,为每个命令都指定该命令的相关属性。通过读取配置然后参考大数组 redisCommandTable对每个命令在执行相应的修改操作。在最后命令的初始化结束之后,所有命令以一个dict的形式保存 redisServer的结构体的commonds指针对象中。

接下来就回到了main方法,然后判定
如果启动了 sentinel选项,那么 先对sentinel进行初始化启动操作。然后根据对启动命令中的命令进行处理操作,把所有给的命令行暂时保存在字符数组options中,在下面的处理中会把所有这些都追加到配置文件中。

无论对当前命令处理还是对下面的配置文件处理,在reids中都是通过sds实现,它
是redis实现的一个对字符串进行封装操作的数据结构。每次set一个value为字符串的时候,redis会为该字符串多分配一倍的内存大小。采用这种策略是避免对该字符串重新赋值的时候再进行内存的申请和释放。
启动命令行中的参数处理完之后就是开始读取配置文件,把上面的options中的参数并一起保存在配置文件中。在redis中对配置文件的处理是在config.c中操作,所有的配置文件的参数是通过redisServer结构体保存的。

初始化服务器

上面几个操作可以理解成启动redis的准备工作,把配置、环境等准备好之后,真正的redis启动才可以开始。
初始化服务器主要是通过调用initServer(),在这里只列出启动redis的核心操作,initServer所执行的操作主要如下
1:创建事件监听库eventLoop,这是redis自己实现的一个轻量级事件库ae;
2: 通过listenToPort,根据配置文件给的需要监听的ip和端口号,开始创建socket、监听具体 端口号,准备接受链接请求。如果配置文件中没给出具体要监听的ip,那么redis就默认接受所有来源的访问。
这里创建的soket,在接下来的创建具体的文件fd事件的时候,会使用到这里socket;
if (server.bindaddr_count == 0) server.bindaddr[0] = NULL;
    for (j = 0; j < server.bindaddr_count || j == 0; j++) {
         if (server.bindaddr[j] == NULL) {
            /* Bind * for both IPv6 and IPv4, we enter here only if
             * server.bindaddr_count == 0. */
            fds[*count] = anetTcp6Server(server.neterr,port,NULL,
                server.tcp_backlog);
            if (fds[*count] != ANET_ERR) {
                anetNonBlock(NULL,fds[*count]);
                (*count)++;
            }
            fds[*count] = anetTcpServer(server.neterr,port,NULL,
                server.tcp_backlog);
            if (fds[*count] != ANET_ERR) {
                anetNonBlock(NULL,fds[*count]);
                (*count)++;
            }
        }else
            do bind every ip gived in conf
      }
 }

3:根据配置文件里配置的数据库个数,开始创建数据库;
/* Create the Redis databases, and initialize other internal state. */
    for (j = 0; j < server.dbnum; j++) {
        server.db[j].dict = dictCreate(&dbDictType,NULL);
        server.db[j].expires = dictCreate(&keyptrDictType,NULL);
        server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
        server.db[j].ready_keys = dictCreate(&setDictType,NULL);
        server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
        server.db[j].id = j;
        server.db[j].avg_ttl = 0;
 }

redis是支持多个数据库同时存在的,在程序中可以使用不同的数据库。通过select命令来选择程序中具体使用哪一个数据库。

4:初始化aof和rdb相关标志,这两个操作主要是为了对redis的数据进行备份和恢复的操作;
5:创建服务器的计划任务事件,事件的执行方法为serverCron;
serverCron中主要如下几个动作,按照定时的时长依次执行以下动作。执行clientsCron、databaseCron、AOF,rdbSaveBackground
6:创建文件事件,绑定到上面第2步创建成功的socke id。事件发生的时候的执行方法为acceptTcpHandler, 在该方法中会调用相应的方法createClient,在createClient方法中又会创建一个文件事件,事件触发的执行方法为readQueryFromClient;
 /* Create an event handler for accepting new connections in TCP and Unix
     * domain sockets. */
    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.");
            }
    }

redis的网络通信是使用anet实现,对底层的socket的建立、监听进行了封装与定义。对于ipv4的方法封装anetTcpServer,在底层中也是依次调用socket、bind、listen等方法,对于accept是通过创建事件进行处理。
所有的事件,包括时间轮询事件、文件fd事件,都被redis封装成几个精巧的事件库ae。时间库的启动在redis.c的main函数末尾调用aeMain启动。
7:检查当前机器架构;
如果是检查当前架构是32位,而且最大可使用内存没有配置的话,那么redis就强制在配置中声明当前最大可使用内存为3GB。这是因为在32位中,指针最大可指示的范围就是4GB。对于64位则没有此选项。
在启动redis的时候可以在配置里声明只监听来自那个IP的请求,如果不设置的话那么就接受所有IP的请求。

initServer执行完成,redis基本就算启动成功了。接下来会设置进程的名称,这个是为了方便一个机器上启动多个进程进行管理;检查是否启动sentinel相应进行不同操作;然后启动事件监听库,开始监听上面所有的time事件和file事件。

上述几个核心操作执行结束,redis就真正的启动起来,等待请求的到来。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值