C/S通信模型
Redis服务器是典型的一对多服务器程序,服务器端使用IO多路复用技术实现文件事件处理器,采用单线程单进程方式处理命令请求,并与多个客户端进行网络通信。
1. 客户端
所有和服务器连接的客户端状态信息以链表形式进行组织,并保存在服务器状态中,对应源码redisServer.clients
属性。
typedef struct redisClient {
// 套接字描述符
int fd;
// 当前正在使用的数据库
redisDb *db;
// 当前正在使用的数据库的 id (号码)
int dictid;
// 客户端的名字
robj *name; /* As set by CLIENT SETNAME */
// 查询缓冲区
sds querybuf;
// 查询缓冲区长度峰值
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size */
// 参数数量
int argc;
// 参数对象数组
robj **argv;
// 记录被客户端执行的命令
struct redisCommand *cmd, *lastcmd;
// 客户端状态标志
int flags; /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */
// 当 server.requirepass 不为 NULL 时
// 代表认证的状态
// 0 代表未认证, 1 代表已认证
int authenticated; /* when requirepass is non-NULL */
...
}
1.1. 客户端属性
客户端状态记录在redisClient
结构体中,通用的一些属性包括,套接字描述符、客户端名字、客户端标志(角色)、输入缓冲区(保存客户端发送的命令请求)、命令和命令参数(服务器对querybuf中内容解析,将结果保存在此处)、输出缓冲区(命令回复)、身份验证(记录客户端是否通过了身份验证)等。
1.2. 客户端的创建和关闭
如果客户端是通过网络连接与服务器进行连接的普通客户端,客户端在使用connect
函数连接服务器时,服务器就会调用连接事件处理器,为客户端创建客户端状态,并将该客户端状态添加到redisServer.clients
链表尾。
一个普通客户端可以由多种原因被关闭,(1)客户端进程退出或被杀死(2)客户端向服务器发送了带有不符合协议格式的命令请求(3)客户端成为了CLIENT KILL
命令目标(4)用户为服务器设置了timeout
配置选项(5)客户端发送命令请求大小,超过了输入缓冲区的限制(1GB)(6)发送给客户端的回复超过了输出缓冲区的限制
服务器在载入AOF文件时,会创建用于执行AOF文件包含的Redis命令的伪客户端,并在载入完成后关闭这个伪客户端。
服务器在初始化时,创建负责执行Lua脚本中包含Redis命令的伪客户端,并将这个伪客户端关联在服务器状态结构的lua_client
属性中。lua_client伪客户端在服务器运行的整个生命周期中会一直存在,只有服务器被关闭时,客户端才会被关闭。
2. 服务器
Redis服务器负责与多个客户端建立连接,处理客户端发送的命令请求,在数据库中保存客户端执行命令所产生的数据,通过资源管理来维持服务器自身的运转。Redis服务器是一个事件驱动程序,它基于Reactor模式开发了自己的网络事件处理器,称为文件事件处理器(file event handler)。
一个命令请求从发送到获得回复的过程中,客户端和服务器要完成一系列操作。如SET KEY VALUE
,回复OK期间,执行过程如下:
(1)客户端向服务器发送命令请求SET KEY VALUE
(2)服务器接收并处理,在数据库中进行设置操作,并产生回复命令OK
(3)服务器将命令回复OK发送给客户端
(4)客户端接收服务器返回的命令回复,并在终端显示。
Redis服务器中serverCron函数默认每隔100毫秒执行一次,这个函数负责管理服务器的资源,并保持服务器自身的运转。
2.1. 初始化服务器
一个Redis服务器从启动到能够接受客户端的命令请求,需要经过一系列初始化和设置过程,如初始化服务器状态,接受客户端指定的服务器配置,创建相应的数据结构和网络连接等等。
(1)初始化服务器状态。初始化服务器状态首先要创建一个redisServer
类型的实例变量,并为结构体中各个属性设置默认值。
(2)载入服务器配置。启动服务器时,用户可以通过给定配置参数或者指定配置文件来修改服务器的默认配置,对应initServerConfig
函数。
(3)初始化服务器数据结构。在创建redisServer
实例时,程序只创建了命令表一个数据结构,而除了命令表外,服务器状态还包含其他数据结构,如server.clients
、server.db
、server.lua
等,对应initServer
函数,至此,服务器用ASCII字符在日志中打印Redis 的logo。
(4)还原数据库状态。完成对服务器状态server变量初始化之后,服务器需要载入RDB文件或者AOF文件,并根据文件记录的内容来还原服务器的数据库状态。
(5)执行事件循环。在初始化的最后一步,开始执行服务器的事件循环。至此,服务器初始化工作完成,服务器可以接受客户端连接请求,并处理客户端发来的命令请求了。
2.2. File Event Handler
文件事件处理器使用I/O多路复用(Multiplexing)程序来同时监听多个socket,并为socket关联不同的事件处理器。当被监听的套接字准备好执行accept、read、write、close等操作时,与之对应的文件事件就会产生。
![]() |
---|
Redis服务器架构 |
2.3. I/O多路复用程序
Redis IO多路复用程序所有功能是通过包装select,epoll,evport,kqueue这些函数实现的,对应源码ae_select.c、ae_epoll.c、ae_kqueue.c文件。
Redis为每个I/O多路复用函数库都实现了相同的API,I/O多路复用程序的底层实现是可以互换的。事实上,Redis源码中用#include
宏定义了相应的规则,程序会在编译时自动选择系统中性能最高的I/O多路复用函数作为Redis的底层实现。
3. 参考文献
[1] 黄健宏.Redis设计与实现[M].北京:机械工业出版社