一直很羡慕那些能读 Redis 源码的童鞋,也一直想本身解读一遍,但迫于 C 大魔王的压力,解读日期遥遥无期。linux
相信不少小伙伴应该也都对或曾对源码感兴趣,但一来以为本身不会 C 语言,二来也不知从何入手,结果就和博主同样,一拖再拖。git
但正所谓,种一棵树的最好时间是十年前,其次就是如今。若是你真的想了解 Redis 源码,又有缘看到了这系列博文,何不跟着博主一块儿解读 Redis 源码,作个同行人呢?接下来,就让咱们一块儿走入 Redis 的源码世界吧。github
决定要读了,下一步就是如何读。从 github 上克隆下来源码,一看 src 目录,望天,104 个文件,我该从哪一个文件开始呢?一个个文件看?不行不行,这样对我毫无诱惑力,没有诱惑力,怎么能打败游戏、小说对个人吸引呢?苦苦思考,不得其解。而后忽然想起来 HTTP 协议的那个经典面试题:从浏览器输入网址,到页面展现,这个过程发生了什么?面试
把这个面试题换成 Redis:输入开启 Redis 服务的命令,回车,到成功启动 Redis 服务,这个过程发生了什么?redis
很好,这个问题成功吸引到我了。就让咱们从源码中找出这个问题的答案吧。后续的全部文章咱们都尝试经过提出问题,解答问题的步骤,来深刻了解 Redis。数据库
要了解 Redis 命令的执行过程,首先要安装 Redis 服务,搭建 debug 环境。若是咱们能一行行的看到命令在代码中的执行过程,解读源码也就没任何阻碍了。数组
后续全部文章均基于 redis3.2.13 版本。浏览器
一、下载编译文件
在 linux 上,下载源码文件,编译,使用 gdb(cgdb) 进行 debug。bash
# bash wget https://github.com/antirez/redis/archive/3.2.13.tar.gz tar -zxvf 3.2.13.tar.gz mv redis-3.2.13 /opt/ cd redis-3.2.13 make # 编译文件,获得可执行文件 redis-server、redis-cli 等
二、开启 debug服务器
# bash gdb src/redis-server # 在 redis 安装目录,进入 gdb 调试环境
按咱们平时调试的习惯,找到一个函数设置断点,而后一步步运行调试。对于 Redis 也同样,咱们找到 server.c 文件,服务器运行的 main 函数就在此文件中。咱们对 main 函数设置断点:
# gdb (gdb) b main Breakpoint 1 at 0x42ed05: file server.c, line 3962.
页面会提示咱们在 server.c 文件的 3962 行设置了断点,也就是咱们指定的 main 函数的位置。
设置好断点,下一步就是启动服务:
// 启动服务 (gdb) r ./redis.conf Starting program: /opt/redis-3.2.13/src/redis-server ./redis.conf [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, main (argc=2, argv=0x7fffffffe5a8) at server.c:3962 3962 int main(int argc, char **argv) {
经过页面输出信息,咱们会发现程序已经运行到咱们设置的断点了。可是咱们看不到运行处的代码,这可不行,看不到源码的调试,无法接受使用如下命令”召唤“源码:
(gdb) layout src
出现下图所示的界面:
到了这一步,咱们已经正式开始踏上 Redis 源码解读之路了。
继续往下走,使用 n
命令,执行下一步,而后不断回车、回车、回车,好像每一行都看不懂什么意思。无论了,继续走。咦,好像发现个能看懂的 initServerConfig()
。没看错的话,这个应该是初始化服务器配置的,让咱们进到这个函数里确认下:
(gdb) s
回车,走你。而后咱们就看到了下面这个界面:
提示咱们进入了 server.c 1464 行的 initServerConfig
函数中。 n
命令,继续走。咱们会发如今这个函数里对服务器的各类基础参数进行初始化。这里的参数详见 server.h/redisServer 结构体。
回到 main 函数后,咱们继续前进,还会发现一个 initServer()
的函数。这个函数是进行驱动事件的注册,以及绑定回调函数等。
继续走,直到执行 aeMain()
,以下图:
程序执行到 4133 行时,Redis 服务已成功开启了。此时服务器处于休眠状态,并使用 aeMain()
进行事件轮询,等待监听事件的发生。
上述整个过程,咱们只是跟着程序的运行,大概看了一遍执行流程。下面,咱们来详细解读上面叙述的关键步骤:初始化基础配置和初始化服务器数据结构。
初始化服务器的第一步就是建立一个 `redisServer
类型的实例变量 server 做为服务器的状态,并为结构中的各个属性设置默认值。
void initServerConfig(void) { int j; // 设置服务器运行 ID getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE); // 为运行 ID 加上结尾字符 server.runid[CONFIG_RUN_ID_SIZE] = '\0'; // 设置服务器默认运行架构 server.arch_bits = (sizeof(long) == 8) ? 64 : 32; // 设置服务器默认配置文件路径 server.configfile = NULL; // 设置服务器默认运行频率 server.hz = CONFIG_DEFAULT_HZ; // 设置服务器默认端口 server.port = CONFIG_DEFAULT_SERVER_PORT; // ... }
对于 initServerConfig
函数来讲,它主要完成如下主要工做:
initServerConfig 函数设置的服务器状态属性基本上都是一些整数、浮点数或者字符串属性。除了命令吧以外,initServerConfig 函数没有建立服务器状态的其它数据结构。像数据库、慢查询日志、Lua 环境、共享对象等这些数据结构是在以后的步骤中建立的。
当初始化基础配置参数后,下一步就要开始载入配置选项。
在启动服务器时,用户能够经过给定配置参数或者知道配置文件来修改服务器的默认配置。就像咱们能够在启动服务时指定端口:
# bash ./src/redis-server --port 7379
经过给定配置参数的方式,修改了服务器的运行端口号。
除了给定配置参数的方式,咱们能够经过指定配置文件的形式启动服务:
# bash ./src/redis-server ./redis.conf
经过指定配置文件的形式启动服务时,咱们实际上就是经过配置文件的形式修改了服务器的数据库配置。
服务器在用 initServerConfig
函数初始完 server
变量后,就会开始载入用户给定的配置参数和配置文件,并根据用户设定的配置,对 server
变量相关属性进行修改。
关于命令行指定配置、配置文件配置、默认配置,这三种配置中:
initServerConfig
函数设置的默认值。在执行 initServerConfig
函数初始化配置时,程序只建立了命令表一个数据结构,而服务器除了命令表还包括其余数据结构,好比:
RedisClient
结构实例。上述这些数据结构会在 initServer
函数为其分配内存,并在有须要时为这些数据结构设置或关联初始化值。
之因此在载入用户配置以后才初始化数据结构,就是由于服务器要先载入用户的配置选项,才能根据选项正确的对数据结构进行初始化。避免再根据用户配置修改数据结构相关属性。
因此,咱们能够看出,服务器对状态的初始化分为两步进行:
initServerConfig
函数是初始化通常属性。initServer
初始化数据结构。除了初始化数据结构以外,initServer
还进行了一些很是重要的设置操做,包括:
当 initServer
函数执行完毕以后,服务器将用 ASCII 字符在日志中打印出咱们常见到的 Redis 图标,以及 Redis 的版本号信息等。
在完成了对服务器状态 server
变量的初始化以后,服务器须要载入 RDB 文件或者 AOF 文件(数据持久化保存文件),并根据文件记录的内容来还原服务器的数据库状态。
还原过程当中,服务器会判断是否启用了 AOF 持久化功能:
当服务器完成数据库状态还原工做以后,会在日志中打印出载入文件和还原数据库状态所耗费的时长。
8189:M 31 May 13:12:47.971 * DB loaded from disk: 0.000 seconds
在初始化的最后一步,服务器将打印出如下日志:
8189:M 31 May 13:12:47.971 * The server is now ready to accept connections on port 8379
并开始执行服务器的事件循环。
至此,服务器的初始化工做所有完成。
命令 | 解释 | 示例 |
---|---|---|
gdb file | 加载被调试的可执行程序文件 | gdb src/redis-server |
r | Run 的缩写,运行被调试的程序。 | r ./redis.conf |
c | Continue 的缩写。继续执行被调试程序,直至下一个断点或程序结束 | c |
b | Breakpoint 缩写。设置断点。可使用 行号、函数名称、执行地址等方式指定断点位置 | b main |
s/n | s 至关于“单步跟踪并进入”,也就是说进入到执行的函数内部。n 至关于“单步跟踪”,不进入到执行函数内部 | s/n |
p 变量名称 | Print 缩写。显示指定变量的值。 | p server |