redis学习笔记2--单机数据库的实现

一、数据库

(一)服务器中的数据库
所有数据库都保存在服务器状态redis.h/redisServer结构的db数组(每个项都是redisDb结构,每个redisDb结构都代表一个数据库)中,在初始化服务器时,程序会根据服务器状态的dbnum属性来决定应该创建多少个数据库。dbnum属性的值由服务器配置的database选项决定,默认的值是16。

(二)切换数据库 (SELECT)
每个redis客户端有自己的目标数据库,可以通过SELECT命令来切换当前数据库。
客户端没有返回当前数据库号的命令,在执行危险命令之前先显式地切换到指定的数据库,然后和执行此命令。

(三)数据库键空间
redisDb结构中的dict字典保存了数据库中所有的键值对,称之为键空间。

添加键、删除键、更新键、对键取值、清空数据库(FLUSHDB)、随机返回数据库的某个键(RANDOMKEY)、返回数据库键数量(DBSIZE)、EXISTS、RENAME、KEYS等等,都是通过对键空间进行操作来实现的。

读写键空间时的维护操作:更新键空间的命中次数和不命中次数(INFO stats命令的keyspace_hits属性和keyspace_missess属性中查看);更新LRU闲置时间;删除过期键;客户端使用WATCH命令监视,修改之后的键会被标记为dirty,从而让事务程序注意到这个键已经被修改过;服务器修改一个键之后,都会对dirty键计数器加1,这个计数器会触发服务器的持久化以及复制操作;如果服务器开启了数据库通知功能,那么在对键进行修改之后,服务器会 按照配置发送相应的数据库通知。

(四)设置键的生存时间或过期时间
EXPIRE PEXPIRE EXPIREAT PEXPIREAT TTL PTTL。
redisDb的expires字典保存数据库中所有键的过期时间,其中键保存的是键空间某个键的指针,而值则是一个long long类型的整数,这个整数保存了键所指向的数据库键的过期时间—一个毫秒精度的UNIX时间戳。

过期键的判定:is_expired(key)。

(五)过期键的删除策略
定时删除(创建定时器,对内存友好,对cpu最不友好)、惰性删除(对CPU最友好,对内存最不友好)、定期删除(整合折中)。

(六)redis采用的策略
惰性删除(db.c/expireIfNeeded)和定期删除(redis.c/activeExpireCycle)。

(七)AOF、RDB和复制功能对过期键的处理
1、生成RDB文件:SAVE或者BGSAVE,程序会进行过期键的检测,不会影响RDB文件的生成。
2、载入RDb文件:服务器以主服务器模式运行(过期键会被过滤),服务器以从服务器运行时(所有键都加入数据库,但是主从服务器同步数据的时候会把从服务器数据库全部清除,因此也没有影响)。
3、AOF文件写入:程序向AOF文件追加DEL命令,显式地记录该键已被删除。
4、AOF重写:BGREWRITEAOF命令,程序检查。
5、复制:当服务器运行复制模式下时,从服务器的过期键删除动作由主服务器控制。主服务器负责删除过期键并同步,从服务器像处理未过期的键一样来处理过期键。

(八)数据库通知(redis.conf/notify-keyspace-events)
redis命令对数据库进行修改之后,服务器会根据配置向客户端发送数据库通知。

以 keyspace 为前缀的频道被称为键空间通知(key-space notification), 而以 keyevent 为前缀的频道则被称为键事件通知(key-event notification)。
1、发送通知:notify.c/notifyKeyspaceEvent函数实现的。
2、发送通知的实现:

二、RDB持久化

redis是内存数据库,如果不保存到磁盘的话,服务器进程一旦退出数据库状态就会消失。RDB持久功能就是将redis的内存状态保存到磁盘里面,避免数据意外丢失。redis持久化既可以手动执行,也可以根据服务器的配置选项定期执行。通过SAVE(阻塞)和BGSAVE(子进程处理)。

SAVE、BGSAVE和BGREWRITEAOF:BGSAVE在执行时,拒绝SAVE;BGSAVE在执行时,拒绝BGSAVE;BGSAVE执行时,BGREWRITEAOF被延后执行;BGREWRITEAOF执行时,BGSAVE会被拒绝。

通过配置服务器的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令,可以设置多个选项,任何一个被满足就执行BGSAVE。服务器通过save选项,设置服务器状态redisServer结构的saveparams属性,saveparams 是一个数组,每个元素是一个saveparam结构,里面保存时间和修改次数两个参数。

redisServer结构还维持了一个dirty(修改次数)计数器和一个lastsave(上次保存时间的unix时间戳)。检查条件是否满足的周期性操作函数serverCron。

RDB文件的结构:
REDIS+db_version+databases+EOF+check_sum;(大小表示常量,小写表示变量和数据)

databases:SELECTDB(1字节常量,标志接下来要读入的是数据库的号码)+db_number(数据库号码)+key_value_pairs(所有键值对数据);

key_value_pairs:不带过期时间的键值对(TYPE(对象类型或者底层编码)+key(键对象)+value(值对象));带过期时间的键值对(EXPIRETIME_MS(标志接下来要读入的是一个以毫秒为单位的过期时间)+ms(以毫秒为单位的unix时间戳,也就是键值对的过期时间 )+TYPE+key+value)。

value编码:根据TYPE类型的不同,value的结构和长度也会有所不同。

字符串对象: TYPE值为REDIS_RDB_TYPE_STRING时,Value的编码分为INT(ENCODING+integer)和RAW(压缩(REDIS_RDB_ENC_LZF+compressed_len+origin_len+compressed_string);不压缩(len+string))(RDB文件压缩功能需要开启,参考redis.conf文件的edbcompression选项,压缩采用LZF算法)。

列表对象: TYPE值为REDIS_RDB_TYPE_LIST时,Value保存的是一个REDIS_ENCODING_LINKEDLIST编码的列表对象,(list_length+len1+string1+len2+string2….)。

集合对象: TYPE值为REDIS_RDB_TYPE_SET时,value保存的是一个REDIS_ENCODING_HT编码的集合对象,(set_size+len1+string1+len2+string2….)。

哈希表对象: TYPE值为REDIS_RDB_TYPE_HASH时,value保存的是一个REDIS_ENCODING_HT编码的集合对象,(hash_size+key1+value1+key2+value2….).

有序集合: TYPE值为REDIS_RDB_TYPE_ZSET时,value保存的是一个REDIS_ENCODING_SKIPLIST编码的有序集合对象,(sorted_set_size+member1+score1+member2+score2….)。

IntSet编码的集合和zipLIST编码的列表、哈希表或者有序集合: 都是将其先转换成为字符串存储。

分析RDB文件: od -cx dump.rdb(-c表示ascll码,-x表示16进制,默认是8进制);redis自身带有RDB文件检查工具redis-check-dump。

(三)AOF持久化(append only file)

AOF持久化是通过保存Redis服务器所执行的命令开记录数据库的状态的。
写入是wirte函数写入到缓冲区;操作系统还提供了fsync和fdatasync两个同步函数,强制让操作系统立即将缓冲区的数据写入到硬盘。

1、AOF持久化的实现:分为命令追加(append)、文件写入、文件同步(sync)三个步骤。

①命令追加:当AOF持久化功能处于打开状态时,服务器执行完一个命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾(redisServer中有个sds结构类型的aof_buf)。
②AOF文件的写入与同步: redis的服务器进程是一个时间循环(loop),文件事件负责和客户端通信,时间事件执行定时操作,而在每次结束一个事件循环之前,会调用flushAppendOnlyFile函数,考虑是否把aof_buf的内容写入和保存到AOF文件当中,具体函数行为有服务器配置redis.conf/appendfsync选项的值来决定(always将所有内容写入并同步、everysec写入所有内容并在上一次同步超过1秒钟就进行同步(同步操作有一个线程专门负责)、no(写入内容不同步,何时同步由操作系统决定))。

2、AOF文件的载入与数据还原
创建一个不带网络连接的伪客户端,从AOF文件中分析并读取一条命令,使用伪客户端执行被读出的写命令,一直重复执行2和3步骤,直到AOF的所有写命令被处理完毕。

3、AOF重写(重写AOF文件体积要小很多,不会包含任何浪费空间的冗余命令)

①AOF文件重写的实现(aof_rewrite):直接从数据库读取对象,然后用新的命令来保存对应的对象的操作命令;当重写程序在处理有多个元素的键时,会先检查键的数量,如果超过了redis.h/REDIS_AOF_REWITE_ITEMS_PER_CMD常量的值,那么会用多条命令来存储,目前此常量的值是64。

②AOF后台重写:

避免重写导致服务器阻塞,AOF重写操作放到子进程去执行;为了避免重写的过程中数据库有新的修改导致AOF文件和当前数据库状态不一致,redis服务器设置了一个AOF重写缓冲区,子进程完成重写之后,父进程将所有重写缓冲区的内容写入到新的AOF文件中,并原子地覆盖现有的AOF文件。

(四)事件

redis服务器是一个事件驱动程序,需要处理两类事件:文件事件(通过套接字与其他服务器或者客户端通信产生)和时间事件(服务器的一些定时操作)。

1、文件事件:
基于reactor模式开发了自己的网络处理器,这个处理器被称为文件事件处理器(file event handler)。

组成:套接字(产生文件事件)、I/O多路复用程序(将产生事件的套接字都放到一个队列里面,然后有序、同步、每次一个套接字向分派器传送套接字)、文件事件分派器(根据套接字产生事件的类型调用相应的事件处理器)、事件处理器(一个个的函数执行相应的操作)。

I/O多路复用程序的实现: select、epoll、evport、kqueue,利用宏定义了相应的规则,编译时会自动选择系统中性能最高的I/O多路复用函数库来作为redisI/O多路复用程序的底层实现。

事件的类型: 客户端(write、close、新的可应答套接字accept、connect)产生AE_READALE事件;客户端执行read操作时,产生AE_WRITABLE事件。同时产生时,先处理read后处理write。

API:
ae.c/aeCreateFileEvent:(函数接受一个套接字描述符、一个事件类型、一个事件处理器作为参数,并将给定套接字的给定事件加入到I/O多路复用程序的监听范围内,并对事件和事件处理器进行关联);
ae.c/aeDeleteFileEvent:(少一个事件处理器参数,与上一个函数相反);ae.c/aeGetFileEvent:(接受一个套接字描述符参数,返回套接字正在被监听的事件类型);
ae.c/aeWait:(函数接受一个套接字描述符、一个事件类型、一个毫秒数为参数,在给定的时间内阻塞并等待套接字的给定类型事件的发生,当时间成功产生或者等待超时之后函数返回);
ae.c/aeApiPoll:(接受一个sys/time.h/struct timeval结构为参数,并在指定的时间内 ,阻塞并等待所有被aeCreateFileEvent函数设置为监听状态的套接字产生的文件事件,当有至少一个事件产生或者超时函数返回);
ae.c/aeProcessEvents:(文件事件分派器,调用aeApiPoll函数来等待事件产生,然后遍历已产生的事件,并调用相应的事件处理器来处理这些事件);
ae.c/aeGetApiName:(返回I/O多路复用程序底层所使用I/O多路复用函数库的名称)。

文件事件的处理器: 连接应答处理器、命令请求处理器、命令回复处理器和复制处理器。

连接应答处理器:(networking.c/acceptTcpHandler函数,具体实现是sys/socket.h/accept 函数的包装)
命令请求处理器:(networking.c/readQueryFromClient函数,具体实现是unistd.h/read函数的包装)。
命令回复处理器:(networking.c/sendReplyToClient函数,具体实现是unistd.h/write函数的包装)。

一次完整的客户端与服务器连接事件实例:客户端发送连接请求—服务器应答连接请求并将read事件与命令请求处理器关联—客户端发送命令请求—服务器引发命令请求处理器—客户端读取命令回复—触发服务器命令回复处理器

2、时间事件:
分为定时事件(让一段程序在指定的时间 之后执行一次)和周期性事件(让一段程序每隔一段指定时间就执行一次)。

三个属性组成:id(全局唯一ID)、when(时间事件到达的unix时间戳)、timeproc(时间事件处理器,返回值为ae.h/AE_NOMORE时表示此事件为定时事件,否则表示周期性事件)

API:ae.c/aeCreateTimeEvent(创建一个新的时间事件到服务器)、ae.c/aeDeleteTimeEvent(从服务器删除ID对应的时间事件)、ae.c/aeSearchNearestTimer(返回距离当前时间最近时间事件的ID)、ae.c/aeProcessTimeEvent(遍历到达的时间事件)。

时间事件应用实例:serverCron函数
服务器定期对自身的资源和状态进行检查和调整,从而确保服务器可以长期稳定地运行,这些都是由redis.h/serverCron函数来执行,主要工作包括:
更新服务器的各类统计信息,比如时间、内存占用、数据库占用等情况;
清理数据库的过期键;
关闭和清理连接失效的客户端;
尝试进行AOF和RDB持久化操作;
如果服务器是主服务器,那么对从服务器进行定期同步;
如果处于集群模式,那么进行定期同步和连接测试。
serverCron函数每隔一段时间执行一次,2.6版本是100ms一次,2.8版本可以通过redis.conf的hz选项进行修改。

3、事件的调度与执行:
事件的调度和和执行是由ae.c/aeProcessEvents函数负责。
服务器的运行过程:获取当前到达时间最近的 时间事件,计算达到时间,没到达的话就阻塞并等待文件事件的发生(最大阻塞时间由传入的timeval结构决定),处理所有已产生的文件事件,处理已到达的时间事件,然后再循环到第一步,直到服务器退出。由于文件事件的处理在前,所以时间事件的处理有可能比设定的到达时间稍晚一点。

(五)客户端

redisSever结构里面有clients属性,是一个redisClient的链表。

1、客户端属性:通用属性和特定功能相关属性,都存储在redisClient结构体中
套接字描述符(fd): CLIENT list查看当前所有客户端信息;伪客户端的fd为-1,普通客户端的值为大于-1的整数。
名字(name): CLIENT setname name可以设置客户端的名字;CLIENT getname可以获取客户端的名字。
标志(flags):记录了客户端的角色,以及客户端目前的所处状态;所有标志都定义在redis.h文件里面。(PUBSUB和Script load命令的特殊性:前一个命令会向所有订阅者发送消息并改变客户端状态,因此有副作用;后一个命令修改了服务器的状态;因此需要服务器使用REDIS_FORCE_AOF标志强制把这个命令写入AOF。)
输入缓冲区(querybuf): 保存客户端的命令请求,最大不超过1G,否则关闭这个客户端。
命令与命令参数(argv(参数)和argc(参数个数)): 服务器将客户端发送命令请求保存在客户端状态的querybuf之后,服务器将对命令请求进行分析,并将得出的命令参数和命令参数的个数保存在这两个属性里面。argv是一个数组,argv[0]是要执行的命令,而之后是其他项传给命令的参数;argc属性记录argv数组的长度。

命令的实现函数: 服务器根据argv[0]的值查找命令表(字典)中命令对应的命令实现函数。字典的键是SDS,值是对应的redisCommand结构(这个结构保存了命令的实现函数、命令的标志、命令应该给定的参数个数、命令的总执行次数和总消耗时长等统计信息),找到redisCommand结构之后服务器会将客户端状态的cmd指针指向这个结构。命令不区分大小写!

输出缓冲区:
执行命令所得到的命令回复会被保存在客户端状态的输出缓冲区里面,每个客户端都有两个输出缓冲区:一个大小是固定的(redisClient结构下面的buf和bufpos属性组成),buf是一个大小为REDIS_REPLY_CHUNK_BYTES(目前默认是16KB)字节的字节数组,bufpos属性则记录了buf数组目前使用的字节数量;另一个缓冲区的大小是可变的,由reply链表来连接字符串对象。

身份验证: authenticated属性用于记录客户端是否通过了身份验证,为0表示为通过身份验证,为1表示通过了身份验证。服务器启动身份验证才有效,具体参考配置文件requirepass选项的相关说明。

时间: ctime(客户端与服务器连接了多少秒)、lastinteraction(客户端和服务器最后一次互动的时间,可以用来计算客户端的空转时间,CLIENT list的idle属性就是从这计算)、obuf_soft_limit_reached_time(记录输出缓冲区第一到达软性限制的时间)。

2、客户端的创建和关闭
普通客户端:创建客户端相应的客户端状态,并将客户端状态添加到服务器状态结构clients链表的末尾;关闭客户端有很多种原因:客户端进程退出;客户端发送不符合协议格式的命令请求;客户端成为CLIENT KILL命令的目标;服务器设置了timeout配置选项(空转时间超过此值时客户端将给关闭),但是主从服务器作为客户端被命令阻塞时是不会被关闭的;输入缓冲区超限制;输出缓冲区超限制(服务器使用两种模式来限制客户端输出缓冲区的大小;硬性限制—超出硬性限制直接关闭客户端和软性限制—持续时间超过设定时长时关闭客户端)。(client_output_buffer_limit选项可以为普通客户端、从服务器客户端和发布与订阅功能的客户端分别设置不同的软性限制和硬性限制,具体参考redis.conf的client_output_buffer_limit选项)

LUA脚本的伪客户端:
服务器在初始化时创建负责执行lua脚本中包含的redis命令的伪客户端,并将这个伪客户端关联在服务器状态结构的lua_client(redisClient结构指针)属性中,这个客户端会在服务器生命周期中一直维持,只有服务器关闭之后这个客户端才会被关闭。

AOF伪客户端:
服务器在载入AOF文件时,会创建用于执行AOF文件包含的redis命令的伪客户端并在载入完成之后关闭这个客户端。

(六)服务器

服务器处理命令过程,serverCron函数所进行的详细操作,服务器在启动和接受客户端命令请求之间所做的准备工作。

1、命令请求的执行过程
客户端发送命令请求(将命令转换成协议格式,套接字将协议格式的命令请求发送个服务器);
服务器读取命令请求(将套接字的协议格式保存到输入缓冲区;对输入缓冲区的命令请求进行分析,提取命令参数和命令参数个数保存到argv和argc;条用命令执行器执行客户端命令);
命令执行器(1):查找命令(查找参数所指定的命令并将找到的命令保存在cmd属性里面),命令表示字典,字典的键是命令的名字,字典的值是redisCommand结构(记录了很多属性:name、proc、arity、sfalgs(命令的属性,例如是读还是写、是否在载入数据时使用、是否允许在lua脚本中使用等)、flags、calls、milliseconds);
命令执行器(2):执行预备操作(在真正执行命令以前,程序还需要进行一些预备操作,包括:cmd指针是否为空,cmd的arity对应的参数个数是否正确,客户端是否通过了身份验证,maxmemory功能打开的话要检查服务器内存的占用情况,BGSAVE命令出错并且服务器开启了stop-writes-on-bgsave-error功能拒绝写命令的执行等)
命令执行器(3):调用命令的实现函数,client->cmd->proc(client)或者等于执行setCommand(client),并将调用产生的回复结果保存到客户端状态的buf属性里面。
命令执行器(4):执行后续工作(是否需要添加慢日志;更新redisCommand的millsseconds属性,并将calls加1;AOF开启的话,持久化模块会把刚刚执行的命令写入到AOF缓冲区;如果有其他服务器在复制这个服务器,那么服务器会把刚刚执行的命令传播给所有从服务器)。
将命令回复给客户端:
客户端接收并打印回复命令:

2、serverCron函数(默认100ms执行一次)

**更新服务器时间缓存:**redisServer当中有两个时间的缓存unixtime(保存当前的unix时间戳)和mstime(毫秒级精度的当前unix时间戳)。默认100ms更新一次,因此精度不高,对于精度要求高的通过执行系统调用。

**更新LRU时钟:**redisServer/lruclock:22(LRU时钟,每10s更新一次)、redisObject/lru:22(对象最后一次被命令访问的时间),通过info server可以查看。

**更新服务器每秒执行命令的次数:**trackOperationsPerSecond函数以100ms一次的频率执行,以抽样计算的方式估算并记录服务器在最近一秒钟 处理的命令请求的数量(这个值可以通过info stats命令的instantneous_ops_per_sec域查看)。这个函数与redisServer的四个属性有关(ops_sec_last_sample_time上一次进行抽样的时间;ops_sec_last_sample_ops上次抽样时服务器已执行的命令的数量;ops_sec_last_samples[REDIS_OPS_SEC_SAMPLES]大小默认为16的环形数组,每个项都记录了一次抽样的结果;ops_sec_idx环形数组的索引值,每次抽样增1,等于16时置零)。当客户端执行info命令的时候,服务器就会调用getOperationPerSecond函数。

更新服务器内存峰值记录:服务器状态的stat_peak_memory属性记录了服务器内存峰值的大小,每次serverCron函数执行的时候都会将当前的内存使用情况和这值进行比较,并把较大值存在此值中,INFO memory命令的used_memory_peak和used_memory_peak_human的两个域记录了服务器的内存峰值。

**处理SIGTERM信号:**sigtermHandler函数,这个信号处理器负责在服务器街道sigterm信号时,打开服务器状态的shutdown_asap标识,每次serverCron函数运行时会对标识进行检查,并根据属性的值决定是否关闭服务器(1关闭,0不做动作)。关闭服务器之前会进行RDB持久化操作。

**管理客户端资源:**serverCron函数每次执行都会调用clientCron函数,clientCron函数会对一定数量的客户端进行两个检查:客户端和服务器之间的连接超时(很长时间没有互动),那么程序释放这个客户端;如果客户端在上一次执行命令请求之后输入缓冲区超过了一定的长度,那么程序会释放当前的缓冲区,并重新创建一个默认大小的输入缓冲区从而防止客户端输入缓冲区耗费了过多的内存。

**管理数据库资源:**serverCron函数每次执行都会调用databasesCron函数,这个函数会对服务器中的一部分数据库进行检查,删除其中的过期键,并在有需要的时候对字典进行收缩操作,具体参考第九章。

执行被延迟的REWRITEAOF: BGSAVE执行的时候,REWRITEAOF命令会被延迟,服务器状态的aof_rewrite_scheduled标识(1标识被延迟了)记录了服务器是否延迟了REWRITEAOF命令。

检查持久化操作的运行状态: 服务器状态用rdb_child_pid和aof_child_pid属性记录了执行BGSAVE命令和REWRITEAOF命令的子进程的ID,这两个属性可以用于检查这两个命令是否正在执行(没有执行的话值为-1),serverCron函数每次执行都会检查,只要有一个不为-1,程序会执行一次wait3函数,检查子进程是否有信号发给服务进程,如果有信号到达就只想后续操作(比如新的RDB文件替换现有的RDB文件或者重写的AOF文件替换现有的aof文件等),没有信号到达则表示持久化操作未完成,不做任何动作;如果两个属性值都为-1,那么服务器没有执行持久化操作,程序会执行三个检查:查看是否有REWRITEAOF命令被延迟了,有就开启一次新的REWRITEAOF操作;检查服务器的自动保存条件是否达到,如果条件满足并且没有其他的持久化操作,那么服务器开启一次新的BGSAVE操作;检查服务器的AOF重写条件是否满足,如果条件满足并在服务器没有其他持久化操作的情况下,服务器开启一次新的REWRITEAOF操作。

将AOF缓冲区的内容写入到AOF文件: 如果服务器开启了AOF持久化功能,并且AOF缓冲区有待写入的数据,那么serverCron函数会调用相应的程序将AOF缓冲区中的内容写入到AOF文件里面。

关闭异步客户端: 在这一步服务器会关闭那些输出缓冲区大小超过了限制的客户端。

增加cronloops计数器的值: 此属性记录了serverCron函数的执行次数。这个属性的唯一作用就是在复制模块中实现”每执行serverCron函数N次就执行一次指定代码”的功能。

3、初始化服务器

一个redis服务器从启动到能够接收客户端的命令请求之前,需要经过一系列的初始化和设置过程,比如初始化服务器状态,接受用户指定的服务器配置,创建相应的数据结构和网络连接等。

初始化服务器状态结构: 初始化工作由redis.c/initServerConfig函数完成,设置服务器的运行ID、默认运行频率、默认配置文件路径、运行架构、默认端口号、默认的RDB和AOF持久化条件、初始化LRU时钟、创建命令表。

载入配置选项: 根据用户指定的配置选项对server状态进行更新。

初始化服务器数据结构: 前面的initServerConfig函数初始化server状态时,程序只创建了命令表一个数据结构,服务器状态还包括其他数据结构:clients链表、db数组、pubsub_channels字典(保存频道订阅信息)、pubsub_patterns链表(用于保存模式订阅信息)、lua(用于执行lua脚本)、slowlog(保存慢查询日志)。服务器调用initServer函数为以上数据结构分配内存,并在有需要的时候为这些数据结构设置或者关联初始化值。除了初始化数据结构之外,initServer还进行一些非常重要的设置操作:为服务器设置进程信号处理器、创建共享对象、打开服务器的监听端口并为监听套接字关联连接应答事件处理器来等待客户端的连接请求、为serverCron函数创建时间事件用来等待服务器正式运行时执行serverCron函数、如果AOF持久化功能已经打开则打开现有的AOF文件(没有AOF文件则创建并打开一个新的AOF文件为写入AOF做好准备)、初始化服务器的后台I/O模块(bio,为将来的I/O操作做好准备)。initServer函数执行完毕之后,服务器将用ASCLL字符在日志中打印出redis的图标,以及redis的版本号信息。

还原数据库状态: 如果服务器启用了AOF持久化功能,那么服务器使用AOF文件来还原数据库状态;反之,使用RDB文件来还原。数据库状态还原之后,服务器将在日志中打印出载入文件并还原数据库状态所耗费的时长。

执行事件循环: 打印日志:the server is now ready to accept …… 然后开始执行服务器的事件循环(loop),至此,服务器初始化工作全部完成,可以接受客户端的连接请求并处理客户端发来的命令请求了。

你可能感兴趣的:(学习笔记)