Redis 核心原理与实践 --Redis 启动过程源码分析
Redis 服务器负责接收处理用户请求,为用户提供服务。Redis 服务器的启动命令格式如下:
configfile 参数指定配置文件。options 参数指定启动配置项,它可以覆盖配置文件中的配置项,如
该命令启动 Redis 服务,并指定了配置文件/path/to/redis.conf,给出了两个启动配置项:port、protected-mode。
本文通过阅读 Redis 源码,分析 Redis 启动过程,内容摘自新书《Redis 核心原理与实践》。本文涉及 Redis 的很多概念,如事件循环器、ACL、Module、LUA、慢日志等,这些功能在作者新书《Redis 核心原理与实践》做了详尽分析,感兴趣的读者可以参考本书。
服务器定义
提示:本章代码如无特殊说明,均在 server.h、server.c 中。
Redis 中定义了 server.h/redisServer 结构体,存储 Redis 服务器信息,包括服务器配置项和运行时数据(如网络连接信息、数据库 redisDb、命令表、客户端信息、从服务器信息、统计信息等数据)。
redisServer 中的属性很多,这里不一一列举,等到分析具体功能时再说明相关的 server 属性。server.h 中定义了一个 redisServer 全局变量:
本书说到的 server 变量,如无特殊说明,都是指该 redisServer 全局变量。例如,第 1 部分说过 server.list_max_ziplist_size 等属性,正是指该变量的属性。可以使用 INFO 命令获取服务器的信息,该命令主要返回以下信息:
server:有关 Redis 服务器的常规信息。
clients:客户端连接信息。
memory:内存消耗相关信息。
persistence:RDB 和 AOF 持久化信息。
stats:常规统计信息。
replication:主/副本复制信息。
cpu:CPU 消耗信息。
commandstats:Redis 命令统计信息。
cluster:Redis Cluster 集群信息。
modules:Modules 模块信息。
keyspace:数据库相关的统计信息。
errorstats:Redis 错误统计信息。
INFO 命令响应内容中除了 memory 和 cpu 等统计数据,其他数据大部分都保存在 redisServer 中。
main 函数
server.c/main 函数负责启动 Redis 服务:
【1】检查该 Redis 服务器是否以 sentinel 模式启动。
【2】initServerConfig 函数将 redisServer 中记录配置项的属性初始化为默认值。ACLInit 函数初始化 ACL 机制,moduleInitModulesSystem 函数初始化 Module 机制。
【3】记录 Redis 程序可执行路径及启动参数,以便后续重启服务器。
【4】如果以 Sentinel 模式启动,则初始化 Sentinel 机制。
【5】如果启动程序是 redis-check-rdb 或 redis-check-aof,则执行 redis_check_rdb_main 或 redis_check_aof_main 函数,它们尝试检验并修复 RDB、AOF 文件后便退出程序。Redis 编译完成后,会生成 5 个可执行程序:
redis-server:Redis 执行程序。
redis-sentinel:Redis Sentinel 执行程序。
redis-cli:Redis 客户端程序。
redis-benchmark:Redis 性能压测工具。
redis-check-aof、redis-check-rdb:用于检验和修复 RDB、AOF 持久化文件的工具。
继续分析 main 函数:
【6】对-v、--version、--help、-h、--test-memory 等命令进行优先处理。strcmp 函数比较两个字符串 str1、str2,若 str1=str2,则返回零;若 str1str2,则返回正数。
【7】如果启动命令的第二个参数不是以“--”开始的,则是配置文件参数,将配置文件路径转化为绝对路径,存入 server.configfile 中。
【8】读取启动命令中的启动配置项,并将它们拼接到一个字符串中。
【9】以 Sentinel 模式启动,必须指定配置文件,否则直接报错退出。
【10】config.c/resetServerSaveParams 函数重置 server.saveparams 属性(该属性存放 RDB SAVE 配置)。
config.c/loadServerConfig 函数从配置文件中加载所有配置项,并使用启动命令配置项覆盖配置文件中的配置项。
提示:config.c 中的 configs 数组定义了大多数配置选项与 server 属性的对应关系:
配置项 rdbchecksum 对应 server.rdb_checksum 属性,默认值为 1(即 bool 值 yes),其他配置项以此类推。如果读者需要查找配置项对应的 server 属性和默认值,则可以从中查找。
下面继续分析 main 函数:
【11】server.supervised 属性指定是否以 upstart 服务或 systemd 服务启动 Redis。
如果配置了 server.daemonize 且没有配置 server.supervised,则以守护进程的方式启动 Redis。
【12】打印启动日志。
【13】initServer 函数初始化 Redis 运行时数据,createPidFile 函数创建 pid 文件。
【14】如果非 Sentinel 模式启动,则完成以下操作:
(1)moduleLoadFromQueue 函数加载配置文件指定的 Module 模块;
(2)ACLLoadUsersAtStartup 函数加载 ACL 用户控制列表;
(3)InitServerLast 函数负责创建后台线程、I/O 线程,该步骤需在 Module 模块加载后再执行;
(4)loadDataFromDisk 函数从磁盘中加载 AOF 或 RDB 文件。
(5)如果以 Cluster 模式启动,那么还需要验证加载的数据是否正确。
【15】如果以 Sentinel 模式启动,则调用 sentinelIsRunning 函数启动 Sentinel 机制。
【16】尽可能将 Redis 主线程绑定到 server.server_cpulist 配置的 CPU 列表上,Redis 4 开始使用多线程,该操作可以减少不必要的线程切换,提高性能。
【17】启动事件循环器。事件循环器是 Redis 中的重要组件。在 Redis 运行期间,由事件循环器提供服务。【18】执行到这里,说明 Redis 服务已停止,aeDeleteEventLoop 函数清除事件循环器中的事件,最后退出程序。
Redis 初始化过程
下面看一下 initServer 函数,它负责初始化 Redis 运行时数据:
【1】设置 UNIX 信号处理函数,使 Redis 服务器收到 SIGINT 信号后退出程序。
【2】设置线程随时响应 CANCEL 信号,终止线程,以便停止程序。
【3】如果开启了 Unix 系统日志,则调用 openlog 函数与 Unix 系统日志建立输出连接,以便输出系统日志。
【4】初始化 server 中负责存储运行时数据的相关属性。
【5】createSharedObjects 函数创建共享数据集,这些数据可在各场景中共享使用,如小数字 0~9999、常用字符串+OK\r\n(命令处理成功响应字符串)、+PONG\r\n(ping 命令响应字符串)。adjustOpenFilesLimit 函数尝试修改环境变量,提高系统允许打开的文件描述符上限,避免由于大量客户端连接(Socket 文件描述符)导致错误。
【6】创建事件循环器。UNIX 编程:信号也称为软中断,信号是 UNIX 提供的一种处理异步事件的方法,程序通过设置回调函数告诉系统内核,在信号产生后要做什么操作。系统中很多场景会产生信号,例如:
用户按下某些终端键,使终端产生信号。例如,用户在终端按下了中断键(一般为 Ctrl+C 组合键),会发送 SIGINT 信号通知程序停止运行。
系统中发生了某些特定事件,例如,当 alarm 函数设置的定时器超时,内核发送 SIGALRM 信号,或者一个进程终止时,内核发送 SIGCLD 信号给其父进程。
某些硬件异常,例如,除数为 0、无效的内存引用。
程序中使用函数发送信号,例如,调用 kill 函数将任意信号发送给另一个进程。
感兴趣的读者可以自行深入了解 UNIX 编程相关内容。
接着分析 initServer 函数:
【7】如果配置了 server.port,则开启 TCP Socket 服务,接收用户请求。
如果配置了 server.tls_ port,则开启 TLS Socket 服务,Redis 6.0 开始支持 TLS 连接。
如果配置了 server.unixsocket,则开启 UNIX Socket 服务。
如果上面 3 个选项都没有配置,则报错退出。
【8】初始化数据库 server.db,用于存储数据。
【9】evictionPoolAlloc 函数初始化 LRU/LFU 样本池,用于实现 LRU/LFU 近似算法。
继续初始化 server 中存储运行时数据的相关属性:
【10】创建一个时间事件,执行函数为 serverCron,负责处理 Redis 中的定时任务,如清理过期数据、生成 RDB 文件等。
【11】分别为 TCP Socket、TSL Socks、UNIX Socket 注册监听 AE_READABLE 类型的文件事件,事件处理函数分别为 acceptTcpHandler、acceptTLSHandler、acceptUnixHandler,这些函数负责接收 Socket 中的新连接,本书后续会详细分析 acceptTcpHandler 函数。
【12】注册事件循环器的钩子函数,事件循环器在每次阻塞前后都会调用钩子函数。
【13】如果开启了 AOF,则预先打开 AOF 文件。
【14】如果 Redis 运行在 32 位操作系统上,由于 32 位操作系统内存空间限制为 4GB,所以将 Redis 使用内存限制为 3GB,避免 Redis 服务器因内存不足而崩溃。
【15】如果以 Cluster 模式启动,则调用 clusterInit 函数初始化 Cluster 机制。
replicationScriptCacheInit 函数初始化 server.repl_scriptcache_dict 属性。
scriptingInit 函数初始化 LUA 机制。
slowlogInit 函数初始化慢日志机制。
latencyMonitorInit 函数初始化延迟监控机制。
总结:
redisServer 结构体存储服务端配置项、运行时数据。
server.c/main 是 Redis 启动方法,负责加载配置,初始化数据库,启动网络服务,创建并启动事件循环器。
文章最后,介绍一下新书《Redis 核心原理与实践》,本书通过深入分析 Redis 6.0 源码,总结了 Redis 核心功能的设计与实现。通过阅读本书,读者可以深入理解 Redis 内部机制及最新特性,并学习到 Redis 相关的数据结构与算法、Unix 编程、存储系统设计,分布式系统架构等一系列知识。
经过该书编辑同意,我会继续在个人技术公众号(binecy)发布书中部分章节内容,作为书的预览内容,欢迎大家查阅,谢谢。
语雀平台预览:《Redis核心原理与实践》
版权声明: 本文为 InfoQ 作者【binecy】的原创文章。
原文链接:【http://xie.infoq.cn/article/751e23bb966aca71ba789b41c】。文章转载请联系作者。
评论