一. 客户端与服务端交互
本篇简单介绍下服务器, 服务器运行涉及的内部原理知识很多, 主要了解 Redis 服务器内部要做哪些事情, 需要开发人员去干预的比较少. Redis 服务器负责与多个客户端建立网络连接, 处理客户端发送的命令请求, 在数据库中保存客户端执行命令所产生的数据, 并通过资源管理来维持服务器自身的运转. 本节先说客户端与服务器交互原理: 服务器与客户端进行了什么交互, 服务器中的各个不同组件又是如何协作的. 在详细了解客户端与服务器在执行命令请求时所做的各种工作之前, 先慨括看下命令请求的执行步骤过程:
(1) 客户端向服务器发送命令请求, 比如 set key value .
(2) 服务器接收并处理客户端发来的命令请求, 在数据库中进行设置操作, 并产生命令回复 OK.
(3) 服务器将命令回复 OK 发送给客户端.
(4) 客户端接收服务器返回的命令回复 OK, 并打印给用户看.
1.1 客户端发送命令请求
Redis 服务器的命令请求来自 Redis 客户端, 当用户在客户端中输入一个命令请求时, 客户端会将这个命令请求转换成协议格式, 然后通过连接到服务器的套接字, 将协议格式的命令请求发送给服务器.
1.2 服务端读取命令
当客户端与服务器之间的连接套接字因为客户端的写入而变得可读时, 服务器将调用命令请求处理器来执行以下操作:
(1) 读取套接字中协议格式的命令请求, 并将其保存到客户端状态的输入缓冲区里面.
(2) 对输入缓冲区中的命令请求进行分析, 提取出命令请求中包含的命令参数, 以及命令参数的个数. 分别保存到服务端记录客户端状态的 argv 和 argc 属性中.
(3) 调用命令执行器, 执行客户端指定的命令.
1.3 命令执行器
(1) 查找命令实现
命令执行器要做的第一件事就是根据客户端状态的 argv[0]参数, 在命令表 (command table) 中查找参数所指定的命令, 并将找到的命令保存到客户端状态的 cmd 属性里面. 命令表是一个字典, 字典的键是一个个命令的名字, 比如常见的如: get , set ,del 等命令, 字典的值则是一个个 redisCommand 结构, 每个结构记录了一个命令的实现信息. redisCommand 结构的主要属性就不在此了解.
(2) 执行预备操作
服务器已经将执行命令所需的命令实现函数 (客户端状态的 cmd 属性), 参数(客户端状态的 argv 属性), 参数个数(客户端状态的 argc 属性) 都收集全了, 但在真正执行命令之前, 程序还要进行一些预备操作, 确保命令可以正确, 顺利地被执行. 简单说包括: 1. 检查 cmd 属性中命令是否正确. 2. 参数以及参数个数是否正确. 3. 是否通过身份验证. 4. 如果打开了 maxmemory 功能, 那么在执行命令之前, 先检查服务器内存占用情况, 需要时进行内存回收, 以接下来的命令可以顺利执行. 5. 如果服务器正在进行数据载入, 那么客户端发送的命令会被服务器拒绝. 6. 如果客户端当前正使用 subscribe 命令订阅频道, 或者用 psubscribe 命令订阅模式, 那么其它命令都会被服务器拒绝, 8. 如果客户端正在执行事务, 那么服务器只会执行客户端发来的 exec,discard,multi,watch 命令, 其它命令都会被放进事务队列中. 9 如果服务器开启了监视器功能, 那么服务器会将要执行的命令和参数信息发送给监视器. 10. 如果服务器因为执行 Lua 脚本而超时并进入阻塞状态, 那么其他命令会被服务器拒绝. 注意: 如果服务器是在复制或者集群模式下, 预备操作会更多. 完成了以上预备操作之后, 服务器才会执行命令.
(3) 执行命令实现函数操作
服务器已经将要执行命令的实现保存到了客户端状态的 cmd 属性里, 并将命令的参数和参数个数分别保存到了客户端状态的 argv 属性和 argv 属性中, 当服务器决要执行命令时, 内部只要执行以下语句就可以了:
- //clinet 是指向客户端状态的指针
- client->cmd->proc(client);
当执行命令操作后, 会产生相应的命令回复, 比如 ok, 这些回复会被保存在客户端状态的输出缓冲区里面(redisClient 结构的 buf 属性和 reply 属性), 之后还会为客户端的套接字关联命令回复处理器, 这个处理器命令回复返回给客户端.
(4)执行后续工作
当执行命令实现函数之后, 服务器还需要执行后续工作: 1. 如果服务器开启了慢查询日志功能, 那么慢查询日志模块会添加一条新的慢查询日志. 2. 根据执行命令所耗时的时长, 更新被执行命令的 redisCommand 结构的 millisecondes 属性, 并将命令的 redisCommand 结构的 calls 计数器值增一. 3. 如果开启了 AOF 功能, 刚执行的命令请求写入到 AOF 缓冲区中. 4. 如果有从服务器正在复制, 那么该命令会传播给所有从服务器.
当以上操作都执行完了后, 服务器就可以继续从文件事件处理器中取出并处理下一个命令请求了.
二. serverCron 函数
在上节中介绍了客户端与服务端交互过程, 这节了解 serverCron 函数执行操作, 并说明这些操作对于服务器维持正常运行有何帮助. Redis 服务器中的 serverCron 函数默认每隔 100 毫秒执行一次, 负责管理服务器的资源, 并保存执行器自身的良好运转.
2.1 更新服务器时间缓存
Redis 服务器中有不少功能需要获取系统当前时间, 每次获取系统的当前时间都需要执行一次系统调用, 为了减少系统调用的执行次数, 服务器状态 redisServer 结构的 unixtime 属性和 mstime 属性被用作当前时间的缓存. 默认每隔 100 毫秒一次频率更新 unixtime 属性和 mstime 属性, 所以这两个属性记录的时间的精确度并不高. 一般用在服务器打印日志, 更新服务器的 LRU 时钟, 决定是否执行持久化任务, 计算服务器上线的时间 (uptime) 等这类对时间精度度要求不高的功能上. 对于要求精确度高的时间, 会再次执行系统调用获取, 一般用在为键设置过期时间, 添加慢日志等功能上.
2.2 更新 LRU 时钟
LRU 全称是 Least Recently Used, 即近期最少使用算法. 用于内存数据清除方面, 在第 15 篇中有介绍. 服务器状态 redisServer 结构的 lruclock 属性保存了服务器的 LRU 时钟. 默认每隔 10 秒更新一次时钟缓存. 通过该算法计算一个数据库键的空转时间.
- 127.0.0.1:6379> set msg "hello"
- OK
- 127.0.0.1:6379> object idletime msg
- (integer) 8
- 127.0.0.1:6379> object idletime msg
- (integer) 14
在 Redis4.0 版本中, 感觉这个空转时钟很精确, 不像默认 10 秒一次更新 lurclock 属性的值.
来源: https://www.cnblogs.com/MrHSR/p/10070089.html