前言

相关介绍主要围绕着 redis 服务的启动相关 

redis-server 的启动, 是执行 server.c 的 int main(int argc, char **argv), 我们这里看的是 standalone 的情况下的服务的启动流程 

本文的相关代码 拷贝自 redis-6.2.0  

代码来自于 https://redis.io/ 

初始化服务配置

初始化 随机种子, 初始化 hashseed, 初始化服务器的配置[server.xxx 系列], 记录命令行参数 

加载配置

如果是 -v, 输出 redis-server 的版本相关信息 

如果是 -h, 输出 usage, 这里省略了 

第一个参数为 当前执行的程序的路径 

默认约束下 第二个参数如果不以 "-" 开头, 则为配置文件 

如果第二个 或者 最后一个参数为 "-", 则表示 redis 需要从 stdin 中获取管理员手动输入的配置信息 

其余的为命令行 options, "--" 开头的表示 key, 紧接着的为 key 对应的 value, 需要成对出现 

接下来 loadServerConfig 则为读取 redis.conf 文件里面的配置, 从 stdin 输入的配置, 命令行中传入的 options, 封装在一起之后, 进行参数的解析 并 apply 到 server.xxx 上面, 以及一些个别 option 的特殊处理 

初始化服务器 

打印我们从 cmd 里面看到的日志信息, 日志信息 大致如下 

/Users/jerry/ClionProjects/redis-6.2.0/src/redis-server /Users/jerry/ClionProjects/redis-6.2.0/redis.conf --port 6666
1381:C 20 Mar 2021 11:31:55.145 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1381:C 20 Mar 2021 11:31:55.146 # Redis version=6.2.0, bits=64, commit=d10d120b, modified=1, pid=1381, just started
1381:C 20 Mar 2021 11:31:55.146 # Configuration loaded
1381:M 20 Mar 2021 11:42:33.987 * monotonic clock: POSIX clock_gettime
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.2.0 (d10d120b/1) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6666
 |    `-._   `._    /     _.-'    |     PID: 1381
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

initServer 里面初始化服务器, 初始化服务器, 记录 pid, threadId 等等, 初始化相关数据结构[clients, slaves 等等], 创建全局共享变量, 监听服务端口, 初始化 server.db[i], 初始化服务器业务相关上下文变量, 统计信息, 创建服务器定时任务(处理后台异步检测的业务), 绑定 tcp/tls/unixSocketHandler, 注册 before/afterSleepHandler, 初始化脚本相关资源, 初始化慢日志相关资源 

如果 pid 文件不存在, 创建 pid 文件并写入数据 

打印 redis-server 的 ascii logo 

加载持久化的数据

loadDataFromDisk 从 rdb/aof 中加载持久化的数据到 redis-server 中 

然后输出 "Ready to accept connections"

1611:M 20 Mar 2021 16:25:41.068 # Server initialized
1611:M 20 Mar 2021 16:25:44.388 * Loading RDB produced by version 6.2.0
1611:M 20 Mar 2021 16:25:44.388 * RDB age 82914 seconds
1611:M 20 Mar 2021 16:25:44.388 * RDB memory usage when created 0.98 Mb
1611:M 20 Mar 2021 16:25:44.391 * DB loaded from disk: 0.006 seconds
1611:M 20 Mar 2021 16:25:59.558 * Ready to accept connections

事件监听处理

后面就是等待客户端的连接, 处理客户端的请求了, 核心业务 

aeProcessEvents 循环处理事件 

redis-server 监听事件的流程中, 外层对应于 aeMain, 里面死循环调用 aeProcessEvents, 传递的 flags 为 27 [0b0001 1011], 逻辑意义为 AE_FILE_EVENTS | AE_TIME_EVENTS | AE_CALL_BEFORE_SLEEP | AE_CALL_AFTER_SLEEP

如果没有需要关心的时间(timeEvent, fileEvent), 直接 return 

我们这里是满足于 AE_TIME_EVENTS, !AE_DONT_WAIT, 因此会走轮询事件, 处理事件的相关处理 

        如果需要调用 beforeSleep, 调用 beforeSleep 

        基于 kqueue 轮询时间, 有没有超时时间取决于 AE_DONT_WAIT 

        如果需要调用 afterSleep, 调用 afterSleep 

        循环处理当前 loop 需要处理的 numevents 个事件

                约束为如果 rfileProc 和 wfileProc 相同, 并且 AE_READABLE | AE_WRITEABLE, 使用 rfileProc/wfileProc 处理 event 

                如果 rfileProc 和 wfileProc 不相同, 则取决于 AE_BARRIER, 如果没有此标记 则逻辑执行顺序为先执行 read, 再执行 write, 否则逻辑执行顺序为 先执行 write 再执行 read[是否执行 read/write 取决于标记 AE_READABLE, AE_WRITEABLE]

        统计处理的事件的数量 

如果需要处理 timeEvent, 处理 timeEvent 

我们这里的 redis-cli 和 redis-server 创建连接, redis-cli 和 redis-server 的命令交互都是基于 fileEvent 

这里的具体时间轮询可能是基于 evport, epoll, kqueue, select 等等, 我这里 mac 是基于 kqueue 

redis-server & redis-cli 连接

如下日志为, 我启动了一个 redis-server, 进程号为 2029, 监听端口 6379, 有两个 socket 监听端口 6379[server.ipfd有两个有效元素] 

第一个 "lsof -i:6379" 为我只有一个 redis-cli 连接 redis-server 的情况, 可以看出第一个 redis-cli 进程为 3065, 通过端口 55203 和 redis-server 进行交互 

        redis-server 这边的 socket 对应的 fd 为 8, redis-cli 这边对应的 fd 为 3, 二者之间数据通过 socket 交互 

第二个 "lsof -i:6379" 为我增加了一个 redis-cli 连接 redis-server 的情况, 可以看出第一个 redis-cli 进程为 3092 , 通过端口 55310 和 redis-server 进行交互 

        redis-server 这边的 socket 对应的 fd 为 9, redis-cli 这边对应的 fd 为 3, 二者之间数据通过 socket 交互 

master:~ jerry$ lsof -i:6379
COMMAND    PID  USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
redis-ser 2029 jerry    6u  IPv4 0x57f6176a3d05c08b      0t0  TCP *:6379 (LISTEN)
redis-ser 2029 jerry    7u  IPv6 0x57f6176a4868a84b      0t0  TCP *:6379 (LISTEN)
redis-ser 2029 jerry    8u  IPv4 0x57f6176a4bfcad0b      0t0  TCP localhost:6379->localhost:55203 (ESTABLISHED)
redis-cli 3065 jerry    3u  IPv4 0x57f6176a4a19a38b      0t0  TCP localhost:55203->localhost:6379 (ESTABLISHED)
master:~ jerry$ lsof -i:6379
COMMAND    PID  USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
redis-ser 2029 jerry    6u  IPv4 0x57f6176a3d05c08b      0t0  TCP *:6379 (LISTEN)
redis-ser 2029 jerry    7u  IPv6 0x57f6176a4868a84b      0t0  TCP *:6379 (LISTEN)
redis-ser 2029 jerry    8u  IPv4 0x57f6176a4bfcad0b      0t0  TCP localhost:6379->localhost:55203 (ESTABLISHED)
redis-ser 2029 jerry    9u  IPv4 0x57f6176a4a17808b      0t0  TCP localhost:6379->localhost:55310 (ESTABLISHED)
redis-cli 3065 jerry    3u  IPv4 0x57f6176a4a19a38b      0t0  TCP localhost:55203->localhost:6379 (ESTABLISHED)
redis-cli 3092 jerry    3u  IPv4 0x57f6176a4a179d0b      0t0  TCP localhost:55310->localhost:6379 (ESTABLISHED)
master:~ jerry$ 

redis-server 监听 6379 的 socket 

服务端这边注册 处理连接请求的 handler, 使用 acceptTcpHandler 来处理客户端的 bind 请求 

并且这里会注册到 eventLoop 里面, 并初始化对应的 kevent[以 fd 作为唯一标志] 

处理 bind 请求的 fd 来自于上面 bind 端口 6379 的时候, 基于 linux socket api 申请的 fd 

redis-server 与 redis-cli 业务交互的 socket 

我们来看一下服务端这边处理客户端的 bind 请求 

通过 accept api 监听到客户端的 bind 请求, 然后 基于 clientFd 创建 connection[fd, state], 走后面的 acceptCommonHandlerr 的处理  

acceptCommonHandlerr 里面就是注册客户端, 并注册事件到 eventLoop, kevent 

acceptCommonHandler

校验 connection 状态正常, 确保为 ACCEPTING 

确保客户端数量没后超过服务端能够处理的上限[默认为10000] 

创建 client, 并注册到 server, 并注册事件到 eventLoop, kevent 

更新状态为 CONNECTED, 并使用 clientAcceptHandler 来处理 connection[ con->state->accept(acceptHandler) ] 

clientAcceptHandler 里面主要是一个 protectMode 情况下对于 客户端的一个校验[只允许 本地回环ip 登录]

对于当前客户端的请求注册事件到 eventLoop, kevent 

我们这里主要关注的是, 配置了 rfileProc/wfileProc 为 connSocketEventHandler, 当前 redis-cli 和 redis-server 交互的 handler 就由 connSocketEventHandler 来进行处理了 

redis-server 对于 redis-cli 业务的处理 

可以看到这里 aeProcessEvents 接下来调用的 rfileProc/wfileProc 为 connSocketEventHandler, 也就是我们的配置的 客户端业务处理函数 

从这里可以看出会使用 readQueryFromClient 来继续向下处理 

readQueryFromClient 里面从 connection 获取客户端传递过来的数据, 并进行相关校验 

接下来会调用 processInputBuffer 来继续向下处理 

processInputBuffer 里面解析 客户端传递过来的数据, 解析为 argc, argv 

接下来会调用 processCommandAndResetClient 来继续向下处理 

processCommandAndResetClient 里面解析 处理 command 的业务, 以及业务处理之后做相关清理工作 

接下来会调用 processCommand 来继续向下处理 

processCommand 里面做业务处理之前的必要的校验, 包含了 命令是否存在, 参数数量校验, 是否认证通过, 是否有访问权限, 是否 oom 等等  

我们这里命令不处于 multi, exec 块中, 接下来会调用 call 来继续向下处理[基于 command 处理具体的业务, 以及相关统计] 

call 里面做业务处理业务, 记录slowlog, 记录统计信息, 将命令传播到 aof 或者 slave 

c->cmd 对应的就是具体处理业务的函数, 比如我们这里 "get" 对应的就是 t_string.getCommand 

"get" 命令对应的 handler 为 getCommand 来处理具体的业务 

完 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐