Redis(四):Redis服务器的初始化

1. Redis服务器初始化流程

2. 初始化服务器默认参数(initServerConfig)

3. 加载配置文件(loadServerConfig)

4. 初始化服务器数据结构(initServer)

5. 还原数据库状态(loadDataFromDisk)

6. 执行事件主循环(aeMain)


1. Redis服务器初始化流程

    redis服务器的初始化流程如下图所示,这里不包含哨兵模式和集群模式的流程,哨兵和集群模式的流程将在后面的博客中介绍。

            

2. 初始化服务器默认参数initServerConfig

    第一步是创建一个struct redisServer类型的实例变量server作为服务器的状态,并为结构中的各个属性设置默认值。初始化服务器的状态主要完成的主要工作包括:

(1)设置服务器运行ID(server.runid)为一个根据时间戳生成的一个随机数

(2)设置服务器的serverCron函数的默认运行频率(server.hz)为10次每秒,即100ms调用一次

(3)设置服务器的默认配置文件路径(server.configfile)为空

(4)设置服务器的运行架构,32位或者64位(server.arch_bits),一般为64位

(5)设置服务器的默认端口号(server.port)为6379

(6)设置服务器listen函数的backlog(server.tcp_backlog)为511

(7)设置服务器的默认数据库个数(server.dbnum)为16

(8)设置服务器的日志级别(server.verbosity)为REDIS_NOTICE

(9)设置服务器键的最大空转时长(server.maxidletime)为0

(10)设置服务器TCP的keepalive为0

(11)设置服务器的过期键策略(server.active_expire_enabled)为使能状态

(12)设置客户端最大查询缓冲区大小(server.client_max_querybuf_len)为1GB

(13)设置RDB的保存条件(server.saveparams)为NULL

(14)设置服务器是否使能系统日志(server.syslog_enabled)为0

(15)设置服务器是否作为守护进程运行(server.daemonize)为0

(16)设置AOF持久化策略(server.aof_state)为关闭

(17)设置服务器默认的AOF持久化条件(server.aof_rewrite_perc、server.aof_rewrite_min_size、server.aof_rewrite_base_size)

(18)设置服务器的最大客户端数量(server.maxclients)为1000

(19)设置服务器的最大内存限制(server.maxmemory)为0,即对内存无限制使用

(20)设置服务器的LRU淘汰策略(server.maxmemory_policy)为REDIS_MAXMEMORY_NO_EVICTION

(21)设置服务器的LRU淘汰策略的样本数(server.maxmemory_samples)为5

(22)初始化服务器的LRU时钟(server.lruclock)

(23)设置RDB持久化的保存条件(server.saveparams)

(24)设置和主从复制相关的状态

(25)设置PSYNC命令所使用的backlog

(26)设置客户端输出缓冲区限制(server.client_obuf_limits[])

(27)初始化服务器的命令表字典(server.commands),并且调用函数populateCommandTable把命令表中redisCommandTable的所有命令添加到命令表server.commands字典中,键为命令的名字,值为redisCommand的一个结构体

/*
 * Redis 命令
 */
struct redisCommand {

    // 命令名字
    char *name;

    // 实现函数
    redisCommandProc *proc;

    // 参数个数
    int arity;

    // 字符串表示的 FLAG
    char *sflags; /* Flags as string representation, one char per flag. */

    // 实际 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. */
    // 从命令中判断命令的键参数。在 Redis 集群转向时使用。
    redisGetKeysProc *getkeys_proc;

    /* What keys should be loaded in background when calling this command? */
    // 指定哪些参数是 key
    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 */

    // 统计信息
    // microseconds 记录了命令执行耗费的总毫微秒数
    // calls 是命令被执行的总次数
    long long microseconds, calls;
};

(28)初始化慢查询日志(server.slowlog_log_slower_than、server.slowlog_max_len)

(29)初始化调试选项

3. 加载配置文件loadServerConfig

   在初始化server变量之后,解析服务器启动时从命令行传入的参数,如果服务器启动时指定了配置文件,则这里就会开始载入用户给定的配置参数和配置文件redis.conf,并根据用户设定的配置,对server变量相关属性值进行更新。    

4. 初始化服务器数据结构initServer

    initServerConfig函数初始化时,程序只创建了命令表一个数据结构,除了这个命令表外,服务器状态还包括其它数据结构需要设置,因此initServer函数的工作如下:

(1)设置信号处理函数:忽略SIGHUP和SIGPIPE两种信号,设置信号SIGTERM的信号处理函数为sigtermHandler,即进程在收到该信号时打印信息,终止服务器

(2)服务器的当前客户端(server.current_client)设置为NULL

(3)服务器的客户端链表(server.clients)初始化,这个链表记录了所有服务器相连的客户端状态结构

(4)服务器的待异步关闭的客户端链表(server.clients_to_close)初始化,这个链表记录了所有待异步关闭的客户端,一般在serverCron函数中关闭

(5)服务器的从服务器链表(server.slaves)初始化,这个链表保存了所有从服务器

(6)服务器的监视器链表(server.monitors)初始化,这个链表保存了所有监视器,即哨兵服务器

(7)服务器的未阻塞的客户端链表(server.unblocked_clients)初始化,这个链表保存了下一个循环前未阻塞的客户端

(8)服务器的等待回复的客户端链表(server.clients_waiting_acks)初始化,这个链表保存了等待回复的客户端列表,即阻塞在“WAIT”命令上的客户端,“WAIT”命令表示主从服务器同步复制,客户端的命令中如果带有“WAIT”,用户指定至少多少个replication成功以及超时时间。

(9)服务器的客户端中是否有等待slave回复的客户端(server.get_ack_from_slaves)初始化为0,如果为1,表示有客户端正在等待从服务器slave的回复。

(10)调用函数createSharedObjects()创建共享对象,通过复用来减少内存碎片:

struct sharedObjectsStruct {
    robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *cnegone, *pong, *space,
    *colon, *nullbulk, *nullmultibulk, *queued,
    *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
    *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
    *masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
    *busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
    *unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop,
    *lpush, *emptyscan, *minstring, *maxstring,
    *select[REDIS_SHARED_SELECT_CMDS],
    *integers[REDIS_SHARED_INTEGERS],
    *mbulkhdr[REDIS_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
    *bulkhdr[REDIS_SHARED_BULKHDR_LEN];  /* "$<value>\r\n" */
};

(11)调用函数adjustOpenFilesLimit()根据服务器最大客户端数量(server.maxclients)调整一个进程最大可以打开的文件个数

(12)调用函数aeCreateEventLoop()初始化服务器的事件循环结构体(server.el):

/* File event structure
 *
 * 文件事件结构
 */
typedef struct aeFileEvent {

    /* 监听事件类型掩码,值可以是 AE_READABLE 或 AE_WRITABLE ,或者 AE_READABLE | AE_WRITABLE */ 
    int mask; 

    aeFileProc *rfileProc;  // 读事件处理器

    aeFileProc *wfileProc;  // 写事件处理器
    
    void *clientData;  // 多路复用库的私有数据

} aeFileEvent;

/* Time event structure
 *
 * 时间事件结构
 */
typedef struct aeTimeEvent {
    long long id; // 时间事件的唯一标识符

    // 事件的到达时间
    long when_sec; /* seconds */
    long when_ms;  /* milliseconds */

    // 事件处理函数
    aeTimeProc *timeProc;

    aeEventFinalizerProc *finalizerProc;  // 事件释放函数

    void *clientData; // 多路复用库的私有数据

    struct aeTimeEvent *next; // 指向下个时间事件结构,形成链表

} aeTimeEvent;

/* A fired event
 *
 * 已就绪事件
 */
typedef struct aeFiredEvent {
    
    int fd; // 已就绪文件描述符

    /* 事件类型掩码,值可以是 AE_READABLE 或 AE_WRITABLE,或者是两者的或 */
    int mask;

} aeFiredEvent;

/* State of an event based program 
 *
 * 事件处理器的状态
 */
typedef struct aeEventLoop {

    int maxfd;   // 目前已注册的最大描述符

    int setsize; // 目前已追踪的最大描述符
    
    long long timeEventNextId; // 用于生成时间事件 id

    time_t lastTime;    // 最后一次执行时间事件的时间

    aeFileEvent *events; // 已注册的文件事件

    aeFiredEvent *fired; // 已就绪的文件事件
    
    aeTimeEvent *timeEventHead; // 时间事件

    int stop;  // 事件处理器的开关
    
    void *apidata; // 多路复用库的私有数据
    
    aeBeforeSleepProc *beforesleep; // 在处理事件前要执行的函数

} aeEventLoop;

    函数中需要对结构体进行初始化,包括:

  •  初始化文件事件结构体数组:eventLoop->events
  •  初始化已就绪文件事件结构体数组:eventLoop->fired
  •  设置已追踪的最大描述符大小为server.maxclients + REDIS_EVENTLOOP_FDSET_INCR
  •  初始化时间时间结构eventLoop->timeEventHead、eventLoop->timeEventNextId等
  •  调用函数aeApiCreate()创建epoll句柄,并初始化eventLoop->apidata
  •  调用函数 listenToPort()创建套接字,并且打开监听端口

(13)根据数据库的数量(server.dbnum)给服务器的数据库数组(server.db)分配内存,这个数组包含了服务器的所有数据库,并且初始化数组中每个元素的所有字典项:

typedef struct redisDb {

    dict *dict;                // 数据库键空间,保存着数据库中的所有键值对

    dict *expires;             // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
    
    dict *blocking_keys;        // 正处于阻塞状态的键
    
    dict *ready_keys;          // 可以解除阻塞的键
    
    dict *watched_keys;         // 正在被 WATCH 命令监视的键

    struct evictionPoolEntry *eviction_pool;    /* Eviction pool of keys */

    int id;                  // 数据库号码
    
    long long avg_ttl;        // 数据库的键的平均 TTL ,统计信息

} redisDb;

(14)初始化server.pubsub_channels和server.pubsub_patterns:用于保存频道订阅信息和模式订阅信息

(15)初始化server.lua:保存用于执行Lua脚本的Lua环境

(16)初始化server.slowlog:用于保存慢查询日志

(17)调用函数aeCreateTimeEvent()注册时间事件,将函数serverCron()注册给时间事件处理器,并且设定1ms后执行这个函数,并且将注册的时间时间放在事件处理器的时间事件链表的表头节点,这个函数后面的博客会介绍

(18)调用函数aeCreateFileEvent()为监听套接字关联连接应答处理器,这个函数做的事儿为:

  • 将监听的套接字加入到epoll的句柄中,并且将事件类型mask设置为AE_READABLE;
  • 取出套接字在事件处理器中对应的文件事件aeFileEvent,并且初始化文件事件的事件类型mask设置为AE_READABLE,文件事件的读事件处理器和写事件处理器设置为acceptTcpHandler(),这个函数后面的博客中会介绍;

(19)如果AOF持久化功能打开,那么打开现有的AOF文件,如果AOF文件不存在,那么创建并打开一个新的AOF文件,为AOF写入做好准备

(20)如果是32位的架构,则设置服务器的最大内存(server.maxmemory)为3GB,内存淘汰策略为REDIS_MAXMEMORY_NO_EVICTION,如果是64位架构,则没这个限制

(21)初始化Lua脚本系统:scriptingInit()

(22)初始化慢查询功能:slowlogInit()

(23)初始化服务器的后台异步I/O模块,为将来的I/O操作做好准备:bioInit(),这里的BIO模块其实是创建一个线程池,线程数量大小为2(REDIS_BIO_NUM_OPS),每个线程会处理一种类型的后台处理任务,分别是关闭文件任务调用fdatasync,将AOF文件缓冲区的内容写入到磁盘 。线程池中需要使用到两个互斥量,分别用于对两个任务队列进行加锁,需要使用到两个条件变量。

5. 还原数据库状态loadDataFromDisk

    完成对server变量的初始化之后,服务器需要载入RDB文件或者AOF文件,并根据文件记录的内容来还原服务器的数据库状态。

(1)如果开启了AOF持久化功能,那么会优先使用AOF文件来恢复数据库,调用函数为:loadAppendOnlyFile(),这个函数将在后面介绍。

(2)如果没有开启AOF持久化功能,就会使用RDB文件来恢复数据库,调用函数为:rdbLoad(),这个函数将在后面介绍。

6. 执行事件主循环(aeMain

/*
 * 事件处理器的主循环
 */
void aeMain(aeEventLoop *eventLoop) {

    eventLoop->stop = 0;

    while (!eventLoop->stop) {

        // 如果有需要在事件处理前执行的函数,那么运行它
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);

        // 开始处理事件
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

    在一个循环中对事件进行处理,下一节中介绍。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值