Redis设计与实现 第 14 章 服务器

Posted zephxu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis设计与实现 第 14 章 服务器相关的知识,希望对你有一定的参考价值。

第 14 章 服务器

14.1 命令请求的执行过程

例如:

客户端执行以下命令:

127.0.0.1:6379> set key value
OK

客户端和服务器需要执行的操作:

  1. 客户端发送 set key value
  2. 服务器处理收到的请求,并在数据库中设置
  3. 服务器回复 OK 给客户端
  4. 客户端接收 OK 并展示

14.1.1 发送命令请求

客户端从用户得到命令请求后转换为协议格式,再连接服务器的套接字,再将协议格式的命令发送给服务器

过程

14.1.2 读取命令请求

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

  1. 读取套接字的协议格式的命令请求,并保存到客户端的输入缓冲区中
  2. 对输入缓冲区的命令请求进行分析,提取命令参数、参数个数,再保存到客户端状态的 argv、argc 属性
  3. 调用命令执行器执行命令

14.1.3 命令执行器(1):查找命令实现

第一件事根据 argv[0] 参数在命令表中查找命令,并保存到客户端状态的 cmd 属性中

命令表是一个字典,键为字符串对象,值为 redisCommand 结构记录 redis 命令实现信息

redisCommand 结构主要属性:

redisCommand 结构主要属性

属性中的 sflags 属性可以使用的标识符有:

sflags属性

14.1.4 命令执行器(2):执行预操作

执行命令之前还需要一些预操作

  • 检查客户端状态的 cmd 是否指向 NULL,是则找不到命令实现,返回错误

  • 根据 cmd 指向的 redisCommand 结构的 arity 属性,检查命令的个数是否匹配,否则不执行返回错误

  • 检查客户端是否通过了身份验证,没有则只能执行 AUTH 命令,否则则返回错误

  • 如果服务器打开了 maxmemory 功能,会先检查服务器的内存占用情况,并在有需要时进行内存回收,如果回收失败则不再执行后续步骤,返回错误

  • 如果服务器上一次执行 BGSAVE 命令出差,且打开了 stop-writes-on-bgsave-error功能

  • 如果客户端在使用 SUBSCRIBE 命令订阅频道,或者在使用 PSUBSCRIBE 模式,则只会执行 SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE、PUNSUBSCRIBE 四个命令,其他则拒绝

  • 如果服务器在进行数据载入,客户端发送的命令需要带有 l 标识(INFO、SHUTDOWN、PUBLISH)才会被服务器执行

  • 如果服务器执行 Lua 脚本超时而阻塞,则只会执行 SHUTDOWN nosave 和 SCRIPT KILL 命令

  • 如果客户端在执行事务,服务器只会执行客户端的 EXEC、DISCARD、MULTI、WATCH 命令,其他会被放进事务队列

  • 如果服务器打开了监视器功能,服务器则先把执行的命令和参数发给监视器,然后才真正执行命令

以上为单机模式下执行命令的操作

14.1.5 命令执行器(3):调用命令的实现函数

由于 cmd 已经保存了命令实现,命令参数、个数已经保存在 argv、argc 中,只需要执行
client -> cmd -> proc(client)

命令实现函数执行指定操作,并参数相应回复,保存在客户端输出缓冲区 (buf、reply 属性),再为客户端的套接字关联命令回复处理器

14.1.6 命令执行器(4):执行后续工作

  • 如果服务器开启慢查询,慢查询模块需要检查是否为执行完的命令添加一条新的慢查询日志

  • 根据耗费时长,更新 redisCommand 结构的 milliseconds 属性,并将 calls 计数器 + 1

  • 如果 AOF 开启了,则会将命令写入 AOF 缓冲区

  • 如果有其他服务器正在复制当前的服务器,也会将命令传播给所有从服务器

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

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

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

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

14.2 serverCron 函数

默认每隔 100 毫秒运行一次,负责管理服务器资源,并保持服务器自身的良好运作

14.2.1 更新服务器时间缓存

获取系统当前时间需要执行系统调用,为了减少次数,服务器中的 unixtmie 和 mstime 属性被用作当前时间的缓存

unixtime和mstime

  • 服务器只会在打印日志、更新服务器的 LRU 时钟、决定是否执行持久化任务、计算服务器上线时间等对时间精确度要求不高的功能上使用上述两个属性
  • 对于键过期时间、添加慢查询日志这种高精度时间的功能仍旧执行系统调用

14.2.2 更新 LRU 时钟

lruclock 保存了服务器的 LRU 时钟,为时间缓存的一种,默认每 10 秒更新一次

lruclock

每个对象都有一个 lru 属性,保存了对象最后一次被命令访问的时间

lru

数据库键的空转时间:

lruclock - lru

14.2.3 更新服务器每秒执行命令次数

serverCron 中的 trackOpearationPerSecond 函数会以每 100 毫秒频率执行,功能是以抽样方式方式估算记录服务器在最近一秒钟处理的命令请求数量,通过 Info stats 命令的 instanceous_ops_sec 域查看

127.0.0.1:6379> INFO stats
# Stats
total_connections_received:2
total_commands_processed:3
instantaneous_ops_per_sec:0

trackOpearationPerSecond 函数和服务器状态中四个 ops_sec_ 开头的属性有关

ops_sec

trackOpearationPerSecond 函数每次运行会根据 ops_sec_last_sample_time 记录的上一次抽样时间和服务器当前时间,以及 ops_sec_last_sample_ops 记录的上一次抽样的已执行命令数量和服务器当前的已执行数量,计算两次调用之间服务器平均每毫秒处理了多少请求,再计算一秒钟服务器能处理多少请求的估计值,再作为新数组项放进 ops_sec_samples 环形数组里

执行 INFO 命令时服务器调用 getOperationPerSecond 函数,根据 ops_sec_samples 环形数组中抽样结果,计算 instanceous_ops_per_sec 属性的值

getOperationPerSecond

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

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

stat_peak_memory

serverCron 函数执行时服务器则查看当前使用内存数量,与 stat_peak_memory 进行大小对比

127.0.0.1:6379> info memory
# Memory
used_memory:692456
used_memory_human:676.23K

used_memory

used_memory_human

两种不同格式记录

14.2.5 处理 SIGTERM 信号

服务器启动后服务器进程的 SIGTERM 信号会关联 sigtermHandler 函数,负责在服务器接到 SIGTERM 信号时,打开服务器状态的 shutdown_asap 标识

SIGTERM

serverCron 函数则是对服务器状态的 shutdown_asap 属性检查,根据只决定是否关闭服务器

  • 1 :关闭服务器
  • 0: 不做动作

服务器在关闭自身之前会进行 RDB 持久化操作,对 SIGTERM 信号拦截

14.2.6 管理客户端资源

serverCron 函数会调用 clientsCron 函数对一定数量的客户端进行检查:

  • 连接超时,即很长时间没有互动,释放客户端
  • 输入缓冲区超过一定长度,释放客户端当前输入缓冲区,重新创建一个默认大小的输入缓冲区,防止耗费内存

14.2.7 管理数据库资源

serverCron 函数会调用 databasesCron 函数对一部分数据库检查,删除过期键,并在有需要时对字典进行收缩操作

14.2.8 执行被延迟的 BGREWRITEAOF

在执行 BGSAVE 期间,如果客户端向服务器发来 BGREWRITEAOF 命令,则会延迟到 BGSAVE 命令结束之后

服务器的 aof_rewrite_scheduled 标识了是否延迟,1 为延迟

serverCron 函数会检查 BGSAVE 或 BGREWRITEAOF 是否在运行,如果没有且 aof_rewrite_scheduled 为 1,则执行被延迟的 BGREWRITEAOF 命令

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

服务器状态的 rdb_child_pid 属性和 aof_child_pid 属性记录了执行 BGSAVE 和 BGREWRITEAOF 命令子进程的 ID,如果为 -1 表示没有在执行

rdb_child_pid 属性和 aof_child_pid 属性

serverCron 函数执行时会检查这两个属性,如果有一个不为 -1,则程序会执行 wait3 函数,检查子进程是否有信号发来服务器进程:

  • 有信号到达,则新的 RDB 文件生成,或者 AOF 文件重写完成,服务器需要进行新的操作,如新的文件代替旧的文件

  • 没有则持久化未完成,不做动作

如果都为 -1,则服务器没有在持久化,则

  • 查看是否有 BGREWRITEAOF 被延迟,有则进行 BGREWRITEAOF 操作
  • 检查服务器自动保存条件是否满足,是的情况且服务器没有执行其他持久化操作,则进行 BGSAVE 操作
  • 检查 AOF 条件是否满足,是且没有其他持久化操作,则进行 BGREWRITEAOF 操作

判断是否持久化

14.2.10 将 AOF 缓冲区中的内容写入 AOF 文件

14.2.11 关闭异步客户端

关闭输出缓冲区大小超过限制的客户端

14.2.12 增加 cronloops 计数值

服务器状态的 cronloops 记录了 serverCron 函数执行次数,作用为在复制模块实现每执行 N 次操作执行一次指定代码

14.3 初始化服务器

14.3.1 初始化服务器状态结构

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

初始化 server 由 redis.c/initServerConfig 完成,主要工作:

  • 设置运行ID
  • 设置默认运行频率
  • 设置默认配置文件路径
  • 设置运行架构
  • 设置默认端口号
  • 设置默认 RDB 持久化条件和 AOF 持久化条件
  • 初始化服务器的 LRU 时钟
  • 创建命令表

14.3.2 载入配置选项

启动服务器时可以给定配置参数或者指定配置文件来修改服务器的默认配置

在 initServerConfig 函数初始化完成后,就会载入用户给定的参数对 server 变量的属性进行修改

14.3.3 初始化服务器数据结构

initServerConfig 函数只创建了命令表,但还有部分数据结构需要创建

  • server.clients 链表:客户端状态链表,redisClient 结构

  • server.db 数组:服务器所有数据库

  • server.pubsub_channels 字典:保存频道订阅信息

  • server.subpub_patterns 链表:保存模式订阅信息

  • server.lua:保存 Lua 脚本的环境

  • server.slowlog:保存慢查询日志

调用 initServer 函数为以上数据结构分配数据,并在有需要时设置默认值或关联初始化值

到这一步才初始化数据结构是因为服务器需要载入用户指定的配置选项才能对数据结构正确初始化,如果在 initServerConfig 就初始化,而用户配置不同的值,导致重新调整和修改

initServer 函数还进行了:

  • 为服务器设置进程信号器

  • 创建共享对象:“OK”、“ERR” 字符串对象,1 到 10000 整数的字符串对象

  • 打开服务器监听端口,并为监听套接字关联连接应答事件处理器,等待客户端连接

  • 为 serverCron 创建时间事件

  • AOF 打开时打开现有的 AOF 文件;否则创建新的 AOF 文件

  • 初始化服务器后台 I/O 模块

初始完成则在日志中打印 Redis 图标及相关版本信息

14.3.4 还原数据库状态

载入 RDB 文件或 AOF 文件来还原服务器的数据库状态

打开 AOF 功能则使用 AOF 文件,否则 RDB 文件

14.3.5 执行事件循环

以上是关于Redis设计与实现 第 14 章 服务器的主要内容,如果未能解决你的问题,请参考以下文章

Redis | 第6章 事件与客户端《Redis设计与实现》

Redis | 第4章 Redis中的数据库《Redis设计与实现》

Redis | 第5章 Redis 中的持久化技术《Redis设计与实现》

Redis | 第8章 发布订阅与事务《Redis设计与实现》#yyds干货盘点#

Redis | 第9章 Lua 脚本与排序《Redis设计与实现》

Redis | 第9章 Lua 脚本与排序《Redis设计与实现》#yyds干货盘点#