Redis服务器是典型的一对多服务器程序:一个服务器可以与多个客户端建立网络连接。这篇文章将通过源码看看客户端和服务器的底层数据结构和工作过程
在Redis这种一对多的服务模式下,每个客户端可以向服务器发送命令请求,而服务器则接收并处理客户端发送的命令请求,并向客户端返回命令回复。通过使用由I/O多路复用技术实现的文件事件处理器,Redis服务器使用单线程单进程的方式来处理命令请求,并与多个客户端进行网络通信。
客户端
客户端数据结构
客户端底层的数据结构如下:
1 | typedef struct redisClient { |
在客户端的各个属性中:
fd表示套接字描述符,伪客户端的fd属性的值为-1:伪客户端处理的命令请求来源于AOF文件或者Lua脚本,而不是网络,所以这种客户端不需要套接字连接;普通客户端的fd属性的值为大于-1的整数
命令和命令参数是对输入缓冲的命令进行解析以后获得命令和参数。
cmd
是命令的实现函数的数组,命令实现函数的结构如下:
1 | struct redisCommand { |
客户端的创建和关闭
当客户端向服务器发出connect请求的时候,服务器的事件处理器就会对这个事件进行处理,创建相应的客户端状态,并将这个新的客户端状态添加到服务器状态结构clients链表的末尾
1 | /* |
对于客户端的启动程序,其大致的逻辑是:读取本地配置,连接服务器获取服务器的配置,获取本地输入的命令并发送到服务器
一个普通客户端可以因为多种原因而被关闭:
- 如果客户端进程退出或者被杀死,那么客户端与服务器之间的网络连接将被关闭,从而造成客户端被关闭。
- 如果客户端向服务器发送了带有不符合协议格式的命令请求,那么这个客户端也会被服务器关闭。
- 如果客户端成为了CLIENT KLLL命令的目标,那么它也会被关闭。
关闭客户端的底层实现:
1 | /* |
服务器
请求命令执行的过程
从客户端输入一条指令到服务端完成命令的内容并返回要经历以下这些步骤:
- 发送命令请求,Redis服务器的命令请求来自 Redis客户端,当用户在客户端中键人一个命令请求时,客户端会将这个命令请求转换成协议格式,然后通过套接字发送给服务器
- 读取命令的内容,服务器接受到套接字以后会产生一个文件事件,通过对文件事件的处理,判断为命令内容
- 查找命令实现,根据客户端的命令参数argv[0],在服务器的命令表中查找指定的命令,并将找到的命令保存到客户端状态的cmd属性里面
- 执行预备操作,在执行命令前需要进行一些操作:检查给出的命令是否有效(cmd是否为NULL);判断给定的参数是否正确;判断客户端是否通过验证
- 调用命令的实现函数
- 执行后续的工作,包括添加日志,计算时间属性,进行AOF操作等等
- 将命令回复发送给客户端
- 客户端收到并打印命令
serverCron函数
Redis服务器中的 serverCron函数默认每隔100毫秒执行一次,这个函数负责管理服务器的资源,并保持服务器自身的良好运转
因为serverCron的实现代码太过冗长,所以这里就简单说一些serverCron函数都干了哪些事情
- 更新服务器时间缓存
Redis服务器中许多的操作都需要用到当前的系统时间属性unixtime
,serverCron会更新这个时间属性
- 更新LRU时钟
Reids服务器中实现过期键的删除需要计算其空转时间,计算空转时间需要用LRU时钟,serverCron会更新这个时钟保证Redis过期键删除功能的正常使用
- 更新服务器内存峰值记录
Redis中使用了一个属性stat_peak_memory
记录了使用内存的峰值,这个属性需要serverCron进行更新
- 处理SIGTERM信号
在启动服务器时, Redis会为服务器进程的 SIGTERM信号关联处理器 sigtermhandier函数,这个信号处理器负责在服务器接到 SIGTERM信号时,打开服务器状态的 shutdown_asap标识。