Redis启动全流程

394 阅读6分钟

1.初始化服务器基础配置

(1)缓存系统当前时间

(2)生成runid

(3)生成replicaid,清理replicaid2

(4)设置默认的saveparam(满足参数条件会触发rdb)

(5)将默认支持的所有command加入到commandTable

(6)使用config配置项的默认值去初始化server属性

2.初始化acl模块(acl模块是用于权限校验的,检测某个用户是否有执行某个command的权限),并创建一个默认用户,这个用户具备所有权限。

3.初始化module

(1)除了基本的属性初始化外,会开启module_blocked_pipe

4.如果当前节点作为哨兵启动,初始化相关属性(作为哨兵节点,本服务器仅支持执行哨兵相关的command,其他command会从server.commands中被移除)

5.尝试从启动参数中找到配置文件。并通过文件数据设置server的一些属性

6.初始化服务器

(1)对一些基础属性做初始化

(2)初始化常量池,主要创建一些字面量,避免反复创建使用/释放内存

(3)调整允许打开的句柄数量,主要是增加socket句柄数,方便接收更多的连接

(4)根据最大句柄数创建eventloop

(5)根据bind_addr/port等信息,绑定本地端口,准备监听外部连接

(6)初始化database,redis内部有多个database,每个database通过一个key对象关联redisObject.(redisObject有多种数据类型,会暴露不同的api。用户在使用redis时主要就是操作这些redisObject)

(7)初始化evictionPoolLRU,该对象会将redisObject按照过期时间排序,在发生内存淘汰时使用

(8)将serverCron函数注册到eventloop上,redis服务器会周期性的执行一些函数,比如找到需要被淘汰的redisObject,找到待处理的client数据等等。也因此redis本认为单线程运行。serverCron将是下一步解读redis的入口。

(9)将acceptTcpHandler设置为eventloop的连接处理器,这样当有用户client或者redis_client连接到当前节点时就会用这个函数来处理。

(10)注册一个读取事件,当接收到数据时唤醒module_blocked_pipe(这个pipe在module的初始化过程中被打开)
(11)在eventloop的主循环中,在sleep前后执行2个函数(beforesleep,aftersleep)

(12)当开启aof模式时。打开aof文件

(13)如果设置了cluster_enable,初始化集群

[1]加载集群配置文件,设置集群属性。

[2]如果集群文件不存在,默认自身为master节点。并生成配置文件

[3]监听针对集群开放的端口

[4]将函数clusterAcceptHandler注册为集群连接处理器

[5]初始化key的路由对象slots_to_keys,在集群中每个master节点都会分配一些slot,而某个redisObject存储在哪个master上是通过hash算法将key映射到某个slot上实现的。在集群中包含多个master.

[6]重置手动故障转移相关的参数

(14)初始化replicationScriptCache,在集群中主从节点的数据拷贝是通过replica.c实现的

7.在服务器初始化完毕后判断本节点是哨兵节点还是正常的数据节点(master,slave)

8.假设本节点是数据节点

(1)加载之前在配置文件中读取到的所有需要从外部获取的module。(无关主流程)

(2)加载之前在配置文件中读取到的acl配置文件,并初始化用户信息

(3)执行initServerLast函数,主要是初始化一些额外的线程,用于提高效率。

[1]首先是初始化bio线程,又被称为background IO线程。专门负责执行一些后台任务。后台线程数量固定为3个,每个线程有自己的任务队列,以及线程锁,条件对象。扩充每个线程的栈空间.

[2]启动bio线程,主要逻辑就是扫描任务队列中的任务,并执行。对于外部线程来说,只要将任务添加到任务队列中就可以。目前看到的3种bio任务分别是关闭文件句柄,释放内存,文件刷盘。这些耗时且优先级不是特别高的任务就是在主线程中通过转移到bio任务队列进行异步化批量处理。

[3]执行initThreadedIO(),初始化在网络模块使用的线程组。主线程通过获取这些线程的锁将他们阻塞在自己的循环中。当主线程认为需要通过这些额外的线程分担io任务时,就会通过调用unlock,让这些io线程可以继续执行。 (4)从磁盘中恢复数据,这里指的是加载aof/rdb文件。

[1]当加载的是aof文件时,会将aof设置成禁用状态。

[2]创建一个fake客户端对象用于执行aof文件中存储的command。这样可以复用执行command的逻辑。fakeClient->db = 0

[3]发出一个正在加载aof文件的事件(目前在整个redis代码中没有找到添加监听器的逻辑,所以先忽略事件功能)

[4]检查该aof文件中是否包含rdb数据,假设此时内部不包含rdb数据,因为读取rdb数据的逻辑与直接根据rdb文件进行数据恢复一致.在数据恢复的过程中,每当恢复部分数据就会执行网络模块囤积的一部分事件,以及更新数据恢复进度,并发出进度事件。

[5]按照aof的格式解析文件会得到一系列要执行的command,以及相关的参数。之后执行这些command就可以恢复关机前的数据

[6]aof重做完毕后,释放fakeClient,发出恢复完成的事件,更新aof此时已经刷盘的偏移量

[7]当发现本次加载的aof文件中包含rdb文件,先将文件包装成rio,rio抽象出了一个模板,内部可以是socket,buffer,file,通过调用对外暴露的read/write函数,可以读取或者写入数据.

[8]通过调用rdbLoadRio读取rdb文件的数据。进行数据恢复。

[9]当发现aof被关闭时,尝试读取rdb并进行数据恢复,同样会转发到rdbLoadRio。

9.假设本节点是哨兵节点

(1)创建bio,network额外线程.

(2)读取哨兵文件配置

(3)对本哨兵此时监控的所有master节点(数据主从节点中的主节点)发送一个事件流。这里采用的是订阅消费者模式,与module中的监听器不同,需要其他节点提前发送订阅命令。事件类型为+monitor代表此时本master增加了一个监控对象

10.执行aeMain方法,也就是执行事件循环,以及2个钩子函数beforeSleep/afterSleep。启动流程结束。

总结:主流程只是一个入口,想要继续深入的话,之后的入口有这么几个。

第一,redis如何处理接收到的用户客户端连接,又是如何进行收发数据的。

第二,redis如何处理收到的来自集群的其他节点连接,之后又会进行什么样的数据交互。又会涉及到哪些模块。集群j架构是如何设计的。

第三,redis执行命令的全流程是怎样。

第四,aof/rdb的写入时机,又是如何进行。

第五,在事件循环中做了哪些事