Mr Dk.'s BlogMr Dk.'s Blog
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • ⛸️ Redis Implementation
    • Part 1 - 数据结构与对象

      • Chapter 2 - 简单动态字符串
      • Chapter 3 - 链表
      • Chapter 4 - 字典
      • Chapter 5 - 跳跃表
      • Chapter 6 - 整数集合
      • Chapter 7 - 压缩列表
      • Chapter 8 - 对象
    • Part 2 - 单机数据库的实现

      • Chapter 9 - 数据库
      • Chapter 10 - RDB 持久化
      • Chapter 11 - AOF 持久化
      • Chapter 12 - 事件
      • Chapter 13 - 客户端
      • Chapter 14 - 服务器
    • Part 3 - 多机数据库的实现

      • Chapter 15 - 复制
      • Chapter 16 - Sentinel
      • Chapter 17 - 集群
    • Part 4 - 独立功能的实现

      • Chapter 18 - 发布与订阅
      • Chapter 19 - 事务
      • Chapter 21 - 排序
      • Chapter 22 - 二进制位数组
      • Chapter 23 - 慢查询日志

Chapter 14 - 服务器

Created by : Mr Dk.

2020 / 06 / 07 14:02

Nanjing, Jiangsu, China


命令请求的执行过程

客户端发送命令请求

客户端会将用户输入的命令转换为协议,然后发送给服务器。

服务器读取命令请求

  1. 服务器从 socket 中读取协议内容,并保存到输入缓冲区中
  2. 对输入缓冲区中的命令进行协议解析,将参数和参数个数保存到 argv 和 argc 中
  3. 调用相应的命令执行器

命令执行器

命令执行器首先根据 argv[0] 参数,在命令表中查找参数指定的命令,并保存到客户端状态的 cmd 中。

redisCommand 结构体的主要属性:

  • char *name 命令名
  • redisCommnadProc *proc 函数指针,指向命令的实现函数
  • int arity 命令参数的个数
  • char *sflags 命令属性
  • int flags 对上述属性分析出的二进制 flag
  • long long calls 服务器执行命令的次数
  • long long milliseconds 服务器执行命令耗费的总时长

在执行命令前,程序还需要进行一些预备操作:

  • 检查 cmd 指针是否为 NULL (说明找不到命令的实现)
  • 检查参数个数 arity 和 argc 是否一致
  • 检查客户端是否经过了身份验证
  • 检查服务器的内存占用情况,必要情况下,对内存进行回收
  • 检查服务器是否正在进行数据载入、阻塞、事务等过程,一些命令不允许被执行

接下来,调用命令的实现函数:

client->cmd->proc(client);

命令的回复将会被保存在 client 结构的输出缓冲区中。

执行完命令实现函数后,服务器还要执行一些后续的动作:

  • 添加日志
  • 统计命令的执行次数与时长
  • AOF 持久化
  • 如果有从服务器,则刚执行的命令将传播给所有从服务器

之后,服务器将命令回复以协议的形式发送给客户端,客户端对协议进行解析后,向用户展示结果。


serverCron 函数

这个函数默认每 100ms 执行一次,主要是对服务器的资源进行管理,保持服务器自身的良好运转。

更新服务器的时间缓存

为了减少为获取时间而进行系统调用,服务器状态中缓存了时间,精度不高。

更新 LRU 时钟

服务器状态中时间缓存的一种,服务器的 LRU 时钟可用于计算对象或客户端的空闲时间。

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

服务器状态中保存了上一次抽样时,服务器的时间以及已执行的命令数量。下一次抽样时,根据两次抽样的时间差、命令数量差,取得这期间每毫秒执行的命令数量,并缓存到一个环形数组中。

struct redisServer {
    // ...
    long long ops_sec_last_sample_time; // 上一次抽样的时间
    long long ops_sec_last_sample_ops; // 上一次抽样时服务器已执行命令的数量
    long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES]; // 抽样结果环形数组
    int ops_sec_index; // 数组当前索引
    // ...
}

更新服务器的内存峰值

如果当前服务器内存使用量大于内存峰值,则更新这个属性。

处理 SIGTERM 信号

Redis 启动后会为 SIGTERM 信号注册一个处理函数,在收到该信号时,打开服务器状态的 shutdown_asap 标志。在 serverCron 函数中,会对这个标志进行检查,决定是否关闭服务器。在关闭服务器之前,服务器会先进行持久化操作。

管理客户端资源

  • 客户端与服务器之间的连接已超时 (没有互动),则释放客户端
  • 如果客户端的输入缓冲区超过一定的长度,则释放并重新分配一个默认大小的输入缓冲区

管理数据库资源

对服务器中的 一部分 数据库进行检查,删除其中的过期 key。

执行被延迟的 BGWRITEAOF

如果这个标志为 1,且 BGSAVE 和 BGREWRITEAOF 命令都没有在执行,那么执行这个命令。

struct redisServer {
    // ...
    int aof_rewrite_scheduled;
    // ...
}

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

检查自动保存或 AOF 重写的条件是否满足,如果满足且服务器没有正在进行持久化操作,则引发新的持久化操作。

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

关闭异步客户端

增加 cronloop 计数器的值

在服务器状态中记录了 serverCron 函数的执行次数。


服务器初始化

初始化服务器状态结构

初始化服务器的第一步 - 创建一个 struct redisServer 作为服务器的状态:

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

载入配置选项

由用户指定的覆盖默认配置的选项,对 struct redisServer 中已经初始化的默认值进行修改。

初始化服务器数据结构

在之前的初始化过程中,程序只创建了 命令表,其它的表不能提前创建,否则将无法应用用户指定的配置。在这一步中,用户自定义的配置已经在 struct redisServer 中了,此时可以进行数据结构的初始化:

  • 初始化 client 链表
  • 初始化 db 数组 (所有的数据库)
  • 初始化保存频道订阅信息的字典、链表
  • 初始化执行 Lua 脚本的 Lua 环境
  • 初始化保存慢查询日志的属性

实际上的主要工作就是用 struct redisServer 中的指针去开辟数据结构的内存,并初始化。

另外,做好与网络或磁盘 I/O 相关的一切准备:

  • 为服务器设置信号处理器 (比如 SIGTERM)
  • 创建共享的对象 (类比 Java 中的 Integer Cache)
  • 打开服务器的监听端口,为 socket 关联 connect 处理器
  • 为 serverCron 函数创建时间事件
  • 如果 AOF 持久化功能开启,那么打开现有的 AOF 文件;否则新建并打开一个 AOF 文件
  • 初始化服务器后台的 I/O 模块

还原数据库状态

载入 AOF 或 RDB 文件来还原数据库的状态,并在控制台上打印还原数据库耗费的时长。

执行时间循环

进入到服务器的事件循环中,初始化结束。接下来,服务器可以接受客户端的连接请求并执行命令了。

Edit this page on GitHub
Prev
Chapter 13 - 客户端