服务器

本文详细阐述了Redis服务器处理命令请求的过程,包括客户端发送命令、服务器接收与执行、命令查找与执行、预备操作及后续工作。同时,介绍了`serverCron`函数的职责,如更新时间、管理资源、执行持久化操作等,以及服务器初始化的步骤,如配置加载、数据结构初始化和数据库状态还原。
摘要由CSDN通过智能技术生成

1、命令请求执行过程

  • 客户端向服务器发送命令请求
  • 服务器接收并处理客户端发来的命令请求,在数据库中进行操作,并产生命令回复
  • 服务器将命令回复发送给客户端
  • 客户端接收服务器返回的命令回复,并将这个回复打印给用户观看

1.1 发送命令请求

redis服务器的命令请求来自redis客户端,当用户在客户端中键入一个命令请求时,客户端会将这个命令请求转换成协议格式,然后通过连接到服务器的套接字,将协议格式的命令请求发送给服务器

1.2 读取命令请求

当客户端与服务器之间的连接套接字因为客户端的写入而变得可读时,服务器将调用命令请求处理器来执行以下操作

  • 读取套接字中协议格式的命令请求,并将其保存到客户端状态的输入缓冲区里面
  • 对输入缓冲区中的命令请求进行分析,提取出命令请求中包含的命令参数,以及命令参数的个数,然后分别将参数和参数个数保存到客户端状态的argv属性和argc属性里面
  • 调用命令执行器,执行客户端指定的命令

1.3 命令执行器:查找命令实现

命令执行器要做的第一件事就是根据客户端状态的argv[0]参数,在命令表中查找参数所指定的命令,并将找到的命令保存到客户端状态的cmd属性里面。

命令表是一个字典,字典的键是一个个命令名字,而字典的值则是一个个redisCommand结构,每个redisCommand结构记录了一个redis命令的实现信息。

属性名类型

作用

namechar*

命令的名字

procredisCommandProc*函数指针,指向命令的实现函数。redisCommandProc类型的定义为typedef void redisCommandProc(Client* c);
arityint命令参数的个数,用于检查命令请求的格式是否正确。如果这个值为负数-N,那么表示参数的数量大于等于N。注意命令的名字本身也是一个参数。
sflagschar*字符串形式的标识值,这个值记录了命令的属性,比如这个命令是写命令还是读命令,这个命令是否允许在载入数据时使用,这个命令是否允许在Lua脚本中使用等等
flagsuint64_t对sflags标识进行分析得出的二进制标识,由程序自动生成。服务器对命令标识进行检查时使用的都是flags属性而不是sflags属性,因为对二进制标识的检查可以方便地通过&,^,~ 等操作来完成
callslong long服务器总共执行了多少次这个命令
millisecondslong long服务器执行这个命令所耗费的总时长

 

 sflags属性可以使用的标识值

标识

意义

write这是一个写入命令,可能会修改数据库
read-only这是一个只读命令,不会修改数据库
use-memory这个命令可能会占用大量内存,执行之前需要先检查服务器的内存使用情况,如果内存紧缺的话就禁止执行这个命令
admin这是一个管理命令
pub-sub这是一个发布与订阅功能方面的命令
no-script这个命令不可以在Lua脚本中使用
random这是一个随机命令,对于相同的数据集和相同的参数,命令返回的结果可能不同
to-sort当在Lua脚本中使用这个命令时,对这个命令的输出结果进行一次排序,使得命令的结果有序
ok-loading这个命令可以在服务器载入数据的过程中使用
ok-stale这是一个允许从服务器在带有过期数据时使用的命令
no-monitor这个命令在监视器模式下不会自动被传播
no-slowlog这个命令不会自动传播到慢日志中
cluster-asking执行隐式的asking,如果在集群模式下,该槽标记为importing时,命令会被接受
fast只要内核调度有足够时间,命令不会延迟其执行,时间复杂度为O(1)或者O(logn)
 no-auth不需要身份验证

 

1.4 命令执行器:执行预备操作

  • 检查客户端状态的cmd指针是否指向NULL,如果是的话,那么说明用户输入的命令名字找不到相应的命令实现,服务器不再执行后续步骤,并向客户端返回一个错误
  • 根据客户端cmd属性指向的redisCommand结构的arity属性,检查命令请求所给定的参数个数是否正确,当参数个数不正确时,不再执行后续步骤,直接向客户端返回一个错误
  • 检查客户端是否已经通过了身份验证,未通过身份验证的客户端只能执行auth、hello命令,如果未通过身份验证的客户端试图执行auth、hello命令之外的其他命令,那么服务器将向客户端返回一个错误。
  • 检查当前用户的访问权限列表,如果当前用户的flags有标识位USER_FLAG_ALLCOMMANDS并且命令不是authCommand,则检查用户命令对应比特位,如果没有设置,则检查子命令。如果用户的flags有标识位USER_FLAG_ALLKEYS并且命令的getkeys_proc不为空或者命令的firstkey不为0,则检查命令对应的keys
  • 集群模式下的重定向;当命令的发送者不是master(c->flags & CLEINT_MASTER != 0)并且命令没有key参数不执行重定向(c->cmd-getkeys_proc !=NULL || c->cmd->firstkey !=0 || c->cmd->proc == execCommand)
  • 如果服务器打开了maxmemory功能,那么在执行命令之前,先检查服务器的内存占用情况,并在有需要时进行内存回收,从而使得接下来的命令可以顺利执行。如果内存回收失败,那么不再执行后续步骤,向客户端返回一个错误。
  • 如果服务器上一次执行bgsave命令或者执行flushappendonlyfile失败,并且服务器打开了stop-writes-on-bgsave-error功能,而且服务器即将要执行的命令是一个写命令,那么服务器将拒绝执行这个命令,并向客户端返回一个错误
  • 如果当前服务器是master,并且设置了min-slaves-to-write选项,并且正常的slaves个数小于min-slaves-to-write个数,而且服务器即将要执行的命令是一个写命令,服务器将拒绝执行这个命令,并向客户端返回一个错误
  • 如果当前服务器不是master并且是只读模式,而当前命令是写命令,则拒绝执行这个命令,向客户端返回一个错误
  • 如果客户端当前正在用SUBSCRIBE命令订阅频道,并且resp版本为2时,服务器只会执行客户端发来的subscribe,psubscribe,unsubscribe,punsubscribe,ping五个命令,其它命令都会被拒绝
  • 如果当前服务器是slave,并且slave-serve-stale-data为no时,而当前命令标识为非stale命令,则拒绝执行
  • 如果服务器正在执行数据载入,那么客户端发送的命令标志必须带有ok-loading才会被服务器执行,其他命令都会被服务器拒绝
  • 如果服务器因为执行Lua脚本而超时并进入阻塞状态,那么服务器只会执行客户端发来的shutdown nosave和script kill命令,其他命令都会被服务器拒绝
  • 如果客户端正在执行事务,那么服务器只会执行客户端发来的exec,discard,multi,watch四个命令,其他命令都会被放进事务队列中
  • 如果服务器打开了监视器功能,那么服务器会将要执行的命令和参数等信息发送给监视器。当完成了以上预备操作之后,服务器就可以开始真正执行命令了。

1.5 命令执行器:调用命令的实现函数

服务器执行命令是通过c->cmd->proc(c)

被调用的命令实现函数会执行指定的操作,并产生相应的命令回复,这些回复会被保存在客户端状态的输出缓冲区里面(buf属性和reply属性),之后实现函数还会为客户端的套接字关联命令回复处理器,这个处理器负责将命令回复返回给客户端。

1.6 命令执行器:执行后续工作

  • 如果服务器开启了慢查询日志功能,那么慢查询日志模块会检查是否需要为刚刚执行完的命令请求添加一条新的慢查询日志。
  • 根据刚刚执行命令所耗费的时长,更新被执行命令的redisCommand结构的milliseconds属性,并将命令的redisCommand结构的calls计数器的值增一。
  • 如果服务器开启了aof持久化功能,那么aof持久化模块会将刚刚执行的命令请求写入到aof缓冲区里面。
  • 如果有其他从服务器正在复制当前这个服务器,那么服务器会将刚刚执行的命令传播给所有从服务器。

1.7 将命令回复发送给客户端

命令实现函数会将命令回复保存到客户端的输出缓冲区里面,并为客户端的套接字关联命令回复处理器,当客户端套接字变为可写状态时,服务器就会执行命令回复处理器,将保存在客户端输出缓冲区中的命令回复发送给客户端。

1.8 客户端接收并打印命令回复

当客户端接收到协议格式的命令回复之后,它会将这些回复转换成人类可读的格式,并打印给用户观看。

2、serverCron函数

redis服务器中的serverCron函数默认每隔100ms执行一次,这个函数负责管理服务器的资源,并保持服务器自身的良好运转。将severCron以时间事件注册时是1ms,在处理该事件时,将事件的时间添加1000/server.hz

2.1 更新服务器时间缓存

更新redisServer的ustime(us),mstime(ms), unixtime(s)三个属性

因为serverCron函数默认会以每100毫秒一次的频率更新unixtime属性和mstime属性,ustime属性,所以这两个属性记录的时间的精确度并不高。

服务器只会在打印日志,更新服务器的lru时钟,决定是否执行持久化任务,计算服务器上线时间这类对时间精确度要求不高的功能上,对于 为键设置过期时间,添加慢查询日志这种需要高精度时间的功能来说,服务器还是会再次执行系统调用 ,从而获得最准确的系统当前时间。

2.2 更新测量指标

serverCron函数中的trackInstantaneousMetric函数会以每100ms一次的频率执行,这个函数的功能是以抽样计算的方式,估算并记录服务器在最近一秒钟处理的命令请求及网络流量数量

每次运行,都会根据last_sample_time记录的上一次抽样时间及服务器的当前时间,以及last_sample_count记录的上一次抽样的记录数和服务器当前的记录数,计算出1s内能处理多个命令请求及网络流量,这个估计值会放到samples环形数组里面。

2.3 更新lru时钟

服务器状态中的lruclock属性保存了服务器的lru时钟,这个属性和上面介绍的unixtime,mstime,ustime属性一样,都是服务器时间缓存的一种。每个redis对象都会有一个lru属性,这个lru属性保存了对象最后一次被命令访问的时间。当服务器要计算一个数据库键的空转时间,就用服务器的lruclock属性记录的时间减去对象的lru属性记录的时间,得出的计算结果就是这个对象的空转时间。

2.4 更新服务器内存峰值记录

服务器状态 中的stat_peak_memory属性记录了服务器的内存峰值大小。

每次serverCron函数执行时,都会查看服务器当前使用的内存数量,并与stat_peak_memory保存的数值进行比较,如果当前使用的内存数量比stat_peak_memory属性记录的值要大,就将当前使用的内存数量记录到stat_peak_memory属性里面。info memory命令会返回这个数据

2.5 更新malloc的内存使用情况

以每100ms一次的频率执行来更新服务器状态中的cron_malloc_stats属性

2.6 处理sigterm信号

在初始化服务器时,会为服务器进程的SIGTERM,SIGINT信号关联处理器sigShutdownHandler函数,这个信号处理器负责在服务器接到SIGTERM,SIGNT信号时,打开服务器状态的shutdown_asap标识。

每次serverCron函数运行时,会对服务器状态的shutdown_asap属性进行松本,并根据属性的值来决定是否关闭服务器。

2.7 管理客户端资源

serverCron函数每次执行都会调用clientCron函数,clientCron函数会对一定数据的客户端检查

  • 如果客户端与服务器之间的连接已经超时,释放这个客户端
  • 如果客户端在上一次执行命令请求之后,输入缓冲区的大小超过了一定的长度,会释放客户端当前的输入缓冲区,并重新创建一个默认大小的输入缓冲区,从而防止客户端的输入缓冲区耗费过多的内存

2.8 管理数据库资源

serverCron函数每次执行都会调用databaseCron函数,这个函数会对服务器中的一部分数据库进行检查,删除其中的过期键,并在需要时,对字典进行收缩操作

2.9 执行被延迟的bgrewriteaof

在服务器执行bgsave命令的期间,如果客户端向服务器发来bgrewriteaof命令,那么服务器会将bgrewriteaof命令的执行时间延迟到bgsave命令执行完毕之后。服务器的aof_rewrite_scheduled标识记录了服务器是否延迟了bgrewriteaof命令。每次serverCron函数执行时,函数都会检查bgsave命令或者bgrewriteaof命令是否在执行,如果这两个命令都没在执行,并且aof_rewrite_scheduled属性的值为1,那么服务器就会执行之前被推延的bgrewriteaof命令。

2.10 检查持久化操作的运行状态

服务器状态使用rdb_child_pid属性和aof_child_pid属性记录执行bgsave命令和bgrewriteaof命令的子进程的id,这两个属性也可以用于检查bgsave命令或者bgrewriteaof命令是否正在执行。

每次serverCron函数执行时,会检查rdb_child_pid和aof_child_pid两个属性值,只要其中一个属性的值不为-1,就会执行一次wait3函数,检查子进程是否有信号发来服务器进程

  • 如果有信号到达,那么表示新的rdb文件已经生成完毕(对于bgsave命令来说),或者aof文件已经重写完毕(对于bgrewriteaof命令来说),服务器需要进行相应命令的后续操作,比如用重写后的aof文件替换现有的aof文件
  • 如果没有信号到达,那么表示持久化操作未完成,程序不做操作。

如果rdb_child_pid和aof_child_pid两个属性的值都为-1,那么表示服务器没有在进行持久化操作,在这种情况下,程序执行以下检查

  • 检查服务器的自动保存条件是否已经满足,如果条件满足,并且服务器没有在执行其它持久化操作,那么服务器开始一次新的bgsave操作
  • 检查服务器的设置的aof重写条件是否满足,如果条件满足,并且服务器没有在执行其它持久化操作,那么服务器将开始执行一次新的bgrewriteaof操作

2.11 将aof缓冲区的内容写入aof

如果服务器开启了aof持久化功能,并且aof缓冲区里面还有待写入的数据,那么serverCron函数会调用相应的函数,将aof缓冲区的内容写入到aof文件里面。

2.12 增加cronloops计数器

服务器状态的cronloops属性记录了serverCron函数执行的次数。

cronloops属性目前在服务器中的唯一作用,就是在run_with_period中使用,限制调用 频率

3、初始化服务器

3.1 初始化服务器状态结构

初始化服务器的第一步就是创建一个struct redisServer类型的实例变量server作为服务器的状态,并为结构中的各个属性设置默认值。

初始化server变量的工作是由server.c/initServerConfig函数来完成

  • 设置服务器的运行id
  • 设置服务器的默认运行频率
  • 设置服务器的默认配置文件路径 
  • 设置服务器的运行架构
  • 设置服务器的默认端口号
  • 设置服务器的默认rdb持久化条件和aof持久化条件 
  • 设置服务器的lru时钟
  • 创建命令表

3.2 载入配置选项

在启动服务器时,用户可以通过给定配置参数或者指定配置文件来修改服务器的默认配置。

服务器在用initServerConfig函数初始化完server变量后,就会开始载入用户指定的配置参数和配置文件,并根据用户设置的配置,对server变量相关属性的值进行修改,通过config.c/loadServerConfig来加载配置

3.3 初始化服务器数据结构

通过server.c/initServer来初始化

  • server.clients链表,这个链表记录了所有与服务器相连的客户端的状态结构,链表的每个节点都包含了一个client结构实例
  • server.db数组,数组中包含了服务器的所有数据库
  • server.pubsub_channles字典,用于保存频道订阅信息
  • server.pubsub_patterns链表,用于保存模式订阅信息
  • server.lua用于执行Lua脚本的Lua环境
  • server.showlog用于保存慢查询日志

除了初始化数据结构之外,initServer还进行了一些非常重要的设置操作

  • 为服务器设置进程信号处理器
  • 创建共享对象,这些对象包含redis服务器经常用到的一些值。
  • 打开服务器的监听端口,并为监听套接辽关联连接应答事件处理器,等待服务器正式运行时接受客户端的连接
  • 为serverCron函数创建时间事件,等待服务器正式运行时执行serverCron函数
  • 如果aof持久化功能已经打开,那么打开现有的aof文件,如果aof文件不存在,那么创建并打开一个新的aof文件,为aof写入作准备
  • 初始化服务器的后台i/o模块,为后序的i/o操作做好准备。

3.4 还原数据库状态

在完成了对服务器状态server变量的初始化之后,服务器需要载入rdb文件或者aof文件,并根据文件记录的内容来还原服务器的数据库状态。

根据服务器是否启用了aof持久化功能,服务器载入数据时所使用的目标文件会有所不同

  • 如果服务器启用了aof持久化功能,那么服务器使用aof文件还原数据库状态
  • 如果服务器没有启用aof持久化功能,那么服务器使用rdb文件还原数据库状态

3.5 执行事件循环

调用aeMain来执行事件循环

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kgduu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值