当前位置: 首页 > 后端技术 > PHP

跟着大斌看源码-Redis1-启动服务,程序是做什么的?

时间:2023-03-29 19:38:57 PHP

一直很羡慕那些能看懂Redis源码的童鞋们,一直想自己解读一下,但是迫于大魔王C的压力,解读的日期遥遥无期。相信很多小伙伴也应该或者曾经对源码感兴趣,但是一来觉得自己不会C语言,二来也不知道从何下手。但俗话说,种一棵树最好的时间是十年前,其次是现在。如果你真的想了解Redis的源码,并且有机会看到本系列博文,何不跟随博主一起解读Redis的源码,做个同路人呢?接下来,让我们一起走进Redis的源码世界。决定要读了,接下来就是怎么读了。从github上clone源码,看看src目录,看看天,有104个文件,应该从哪个文件入手呢?一个一个地看文件?不不不,这对我来说没有诱惑,没有诱惑,我怎么能战胜游戏和小说对我的吸引力呢?仔细想想,想不明白。然后突然想起了HTTP协议的经典面试题:从在浏览器中输入URL到显示页面,这个过程发生了什么?将这道面试题改成Redis:输入启动Redis服务的命令,回车,Redis服务启动成功。这个过程中发生了什么?嗯,这个问题成功吸引了我。让我们从源码中找出这个问题的答案。在随后的所有文章中,我们都试图通过提问和回答来深入了解Redis。要了解Redis命令的执行过程,首先要安装Redis服务并搭建调试环境。如果我们能在代码中逐行看到命令的执行过程,那么解读源代码就不会有任何障碍。后续所有文章均基于redis3.2.13版本。一、搭建调试环境1、下载编译文件在linux上,下载源代码文件,编译,使用gdb(cgdb)进行调试。#bashwgethttps://github.com/antirez/redis/archive/3.2.13.tar.gztar-zxvf3.2.13.tar.gzmvredis-3.2.13/opt/cdredis-3.2.13make#编译文件,获取可执行文件redis-server,redis-cli等2.打开debug#bashgdbsrc/redis-server#在redis安装目录下,进入gdb调试环境根据我们平时的调试习惯,找到一个函数进行设置打个断点,然后一步步运行debug。Redis也是一样,我们找到server.c文件,服务器运行的主要功能就在这个文件中。我们在main函数上设置断点:#gdb(gdb)bmainBreakpoint1at0x42ed05:fileserver.c,line3962页面会提示我们在server.c文件的3962行设置断点,也就是main函数我们指定了s的位置。设置好断点后,接下来就是启动服务了://启动服务(gdb)r./redis.conf启动程序:/opt/redis-3.2.13/src/redis-server./redis.conf[使用libthread_db启用线程调试]使用主机libthread_db库“/lib/x86_64-linux-gnu/libthread_db.so.1”。断点1,main(argc=2,argv=0x7fffffffe5a8)atserver.c:39623962intmain(intargc,char**argv){通过页面输出信息,我们会发现程序已经运行到我们设置的断点处。但是我们在运行的地方看不到代码,这样不好。我们看不到源码的调试,也不能接受下面的命令来“召唤”源码:(gdb)layoutsrc出现下图所示的界面:此时,我们已经有了正式开始走上redis源码解读之路。2初始化服务继续往下走,使用n命令执行下一步,然后回车,回车,回车,好像每一行都不明白什么意思。不管了,继续。咦,好像找到了一个可以理解的initServerConfig()。如果没看错的话,这应该是初始化服务器配置,让我们进入这个函数确认一下:(gdb)s回车,走吧。然后我们看到如下界面:提示我们进入server.c的1464行的initServerConfig函数。n命令,继续前进。我们会发现服务器的各种基本参数都是在这个函数中初始化的。这里的参数在server.h/redisServer结构体中有详细说明。回到main函数后,我们继续往前走,找到initServer()的一个函数。该函数是注册驾驶事件并绑定回调函数。继续执行直到aeMain()执行完毕,如下图所示:当程序执行到第4133行时,Redis服务已经成功启动。此时服务器处于休眠状态,使用aeMain()进行事件轮询,等待监听事件的发生。在上面的整个过程中,我们只是跟着程序的运行,大致看了一下执行过程。下面详细解释一下上面介绍的关键步骤:初始化基本配置和初始化服务器数据结构。3初始化详解3.1初始化基本配置初始化服务器的第一步是创建一个`redisServer类型的实例变量server作为服务器的状态,并为结构体中的每个属性设置默认值。voidinitServerConfig(void){intj;//设置服务器运行IDgetRandomHexChars(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函数来说,它主要完成以下几个主要任务:设置服务器的运行ID。设置服务器的默认运行频率。设置服务器的默认配置文件路径。设置服务器的运行架构。设置服务器的默认端口号。initServerConfig函数设置的服务器状态属性基本都是整数、浮点数或者字符串属性。除了命令栏之外,initServerConfig函数没有为服务器状态创建其他数据结构。数据库、慢查询日志、Lua环境、共享对象等数据结构在后面的步骤中创建。初始化基本配置参数后,下一步就是开始加载配置选项。3.2加载配置选项在启动服务器时,用户可以通过给出配置参数或知道配置文件来修改服务器的默认配置。就像我们可以在启动服务的时候指定端口:#bash./src/redis-server--port7379通过给配置参数,修改服务器的运行端口号。除了指定配置参数的方式,我们还可以通过指定配置文件来启动服务:#bash./src/redis-server./redis.conf在通过指定配置文件启动服务的时候,我们实际上是通过配置file形式修改服务器的数据库配置。服务器使用initServerConfig函数初始化服务器变量后,会开始加载用户给定的配置参数和配置文件,并根据用户设置的配置修改服务器变量的相关属性。关于命令行指定配置、配置文件配置、默认配置,这三种配置中:如果有指定配置,则服务端有用户指定的值更新相应的属性。如果未指定值,则使用initServerConfig函数设置的默认值。3.3初始化服务器数据结构在执行initServerConfig函数初始化配置时,程序只创建命令表的数据结构,服务器除了命令表之外还包括其他数据结构,如:server.clients链表。这个链表记录了所有连接到服务器的客户端的状态结构。链表的每个节点都包含RedisClient结构的一个实例。服务器.db数组。该数组包含服务器的所有数据库。server.pubsub_channels字典。频道订阅信息保存在字典中。server.pubsub_patterna链表。模式订阅信息保存在链表中。服务器.lua属性。用于执行Lua脚本。服务器。慢日志属性。用于保存慢日志。上面这些数据结构会在initServer函数中为它们分配内存,并在必要的时候为这些数据结构设置或关联初始化值。之所以在加载用户配置后初始化数据结构,是因为服务器必须先加载用户的配置选项,这样才能根据选项正确初始化数据结构。避免根据用户配置修改数据结构相关属性。因此,我们可以看出服务器对状态的初始化分两步进行:initServerConfig函数对通用属性进行初始化。initServer初始化数据结构。除了初始化数据结构之外,initServer还执行一些非常重要的设置操作,包括:为服务器设置进程信号处理程序。创建共享对象。这些对象包括Redis服务器常用的一些,比如包含“OK”回复的字符串对象、包含“ERR”回复的字符串对象、包含1到10000之间的整数的字符串对象等等。服务器避免重复创建相同的对象并通过重用这些共享对象来节省内存。打开服务器的监听端口,并为监听套接字关联一个响应事件处理器,等待服务器正式运行时接受客户端的连接。为服务器创建时间事件,等待服务器运行时执行serverCron函数。如果启用了AOF持久化,则打开一个现有的AOF文件。如果AOF文件不存在,创建并打开一个新的AOF文件,准备AOF写入。初始化服务器后台IO模块,为IO操作做准备。initServer函数执行后,服务器会使用ASCII字符在日志中打印出常用的Redis图标和Redis版本号信息。4其他操作4.1恢复数据库在服务器状态的服务器变量初始化完成后,服务器需要加载RDB文件或AOF文件(数据持久化文件),根据文件内容恢复服务器的数据库状态记录。在恢复过程中,服务器会判断是否开启了AOF持久化功能:如果开启了AOF持久化功能,服务器会使用AOF文件来恢复数据库状态。如果未启用AOF,则服务器使用RDB文件来恢复数据库状态。当服务器完成恢复数据库状态时,它会在日志中打印出加载文件和恢复数据库状态所花费的时间。8189:M31May13:12:47.971*DBloadedfromdisk:0.000seconds4.2执行事件循环在初始化的最后一步,服务器会打印出如下日志:8189:M31May13:12:47.971*服务器现在准备好接受端口8379上的连接并开始执行服务器的事件循环。至此,服务器的初始化已经完成。5gdb基础使用命令讲解示例gdb文件加载被调试的可执行程序文件gdbsrc/redis-serverrRun缩写,运行被调试的程序。r./redis.confcContinue的缩写。继续执行被调试的程序,直到下一个断点或程序结束cbBreakpoint缩写。设置断点。可以用行号、函数名、执行地址等来指定断点位置bmains/ns相当于“单步跟踪进入”,也就是进入到执行的函数内部。n相当于“单步跟踪”,不进入执行函数。s/np变量名打印缩写。显示指定变量的值。pserver总结了搭建环境的三个步骤:下载、编译、gdb。服务启动包括:初始化基本配置、数据结构、对外提供服务的准备、恢复数据库、执行事件循环等。gdb基本命令:rcbnp。