本书基于Redis3.0版本编写,虽然与后续版本存在一些差异,但仍详细介绍了Redis服务器的一对多客户端连接机制。书中不仅涵盖了基本的安装配置和命令操作,还深入探讨了数据结构、持久化策略及性能优化等高级主题,适合初学者和进阶用户参考学习。
参考&#xff1a;<>
- 注&#xff1a;这本书是基于Redis3.0版本写的&#xff0c;和后面的版本有点差异
Redis服务器是一对多服务器程序&#xff1a;一个服务器可以和多个客户端建立网络连接&#xff0c;每个客户端可以向服务器发送命令请求&#xff0c;而服务器接收并处理客户端发送的命令请求&#xff0c;并向客户端返回命令回复。
使用由I/O多路复用技术实现的文件事件处理器&#xff0c;Redis服务器可以使用单线程单进程的方式来处理命令请求&#xff0c;并与多个客户端进行网络通信。
与服务器连接客户端&#xff0c;Redis服务器使用redisClient结构&#xff08;客户端状态&#xff09;保存客户端当前的状态信息、执行相关功能时需要用到的数据结构。主要包括以下&#xff1a;
- 客户端的套接字描述符
- 客户端名称
- 客户端标志值
- 指向客户端正在使用的数据库指针、数据库序号
- 客户端当前要执行的命令、命令参数、参数个数和指向命令实现函数的指针
- 客户端的输入和输出缓冲区
- 客户端执行发布订阅用到的数据结构
- 客户端身份验证标志
- 客户端的创建时间、最后一次与服务器通信的时间、客户端的输出缓冲区大小超出软性限制&#xff08;soft limit&#xff09;的时间
- …
客户端保存在redisServer结构的clients属性中&#xff0c;该属性是一个链表&#xff0c;保存了所有与服务器连接客户端的状态结构&#xff0c;对客户端执行操作或查找指定客户端&#xff0c;都是通过遍历clients属性完成&#xff1a;
struct redisServer {list *clients;
}
一、客户端属性
1.1 套接字描述符
客户端状态的fd属性记录了客户端正在使用的套接字描述符&#xff1a;
type struct redisClient {int fd;
} redisClient;
客户端类型不同&#xff0c;fd取值可以是-1、大于-1的整数&#xff1a;
- 伪客户端(fake client)&#xff1a;fd属性值为-1。伪客户端处理的命令请求来源于AOF文件或Lua脚本&#xff0c;不是网络&#xff0c;所以这种客户端不需要套接字连接。Redis服务器会在两个地方用到伪客户端&#xff1a;
- 1.载入AOF文件并还原数据库状态
- 2.用于执行LUa脚本中包含的Redis命令
- 普通客户端&#xff1a;fd属性大于-1的整数。普通客户端使用套接字与服务器进行通信&#xff0c;Redis服务器会用fd属性记录客户端套接字的描述符。
执行client list
命令可以列出目前所有连接到服务器的普通客户端&#xff1a;
fd属性显示了服务器连接客户端所使用的套接字描述符
redis> client list
id&#61;3 addr&#61;127.0.0.1:53094 fd&#61;9 name&#61; age&#61;914864 idle&#61;0 flags&#61;N db&#61;0 sub&#61;0 psub&#61;0 multi&#61;-1 qbuf&#61;26 qbuf-free&#61;32742 obl&#61;0 oll&#61;0 omem&#61;0 events&#61;r cmd&#61;client
1.2 标志
客户端的标志属性flags记录了客户端的角色&#xff08;role&#xff09;、客户端目前所处的状态&#xff1a;
type struct redisClient {int fd;int flags;
} redisClient;
flags属性的值可以是单个标志&#xff0c;也可以是多个标志的二进制或&#xff0c;如
- flags &#61;
- flags &#61; | | …
每个标志使用一个常量表示&#xff0c;一部分记录了客户端的角色、一部分记录了客户端目前所处的状态。
- 客户端角色&#xff1a;
- REDIS_MASTER、REDIS_SLAVE&#xff1a;表示客户端代表的是一个主服务器、从服务器。在主从服务器进行复制操作时&#xff0c; 主服务器会成为从服务器的客户端&#xff0c;从服务器成为抓服务器的客户端。
- REDIS_PRE_PSYNC&#xff1a;表示客户端代表是一个版本低于Redis2.8的从服务器&#xff0c;主服务器不能使用PSYNC命令与这个从服务器进行同步。这个标志只能在REDIS_SLAVE标志打开状态使用。
- REDIS_LUA_CLINET&#xff1a;表示客户端用于处理Lua脚本里包含的Redis命令的伪客户端。
- 客户端所处的状态&#xff1a;
- REDIS_MONITOR&#xff1a;表示客户端正在执行MONITOR命令。
- REDIS_UNIX_SOCKET&#xff1a;表示服务器使用UNIX套接字来连接客户端。
- …
1.3 输入缓冲区
客户端状态的输入缓冲区用于保存客户端发送的命令请求&#xff1a;
type struct redisClient {int fd;int flags;sds querybuf;
} redisClient;
注&#xff1a;输入缓冲区的大小根据输入内容动态地缩小或扩大&#xff0c;但最大不能超过1GB&#xff0c;否则服务器将会关闭这个客户端。
示例&#xff1a;
如果客户端向服务器发送了set key value
命令请求&#xff0c;那么客户端状态的querybuf属性是一个包含以下内容的SDS值&#xff1a;
*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
如下图所示&#xff1a;
1.4 命令与命令参数
在服务器将客户端发送的命令请求保存到客户端的querybuf
属性后&#xff0c;服务器会对命令请求的内容进行分析&#xff0c;然后将得出的命令参数以及参数个数分别保存到客户端状态的argv
属性和argc
属性&#xff1a;
type struct redisClient {int fd;int flags;sds querybuf;robj **argv;int argc;
} redisClient;
- argv属性&#xff1a;一个数组&#xff0c;数组中每一项是字符串&#xff0c;argv[0]是要执行的命令&#xff0c;其他项是传给命令的参数。
- argc属性&#xff1a;记录argv数组的长度
示例&#xff1a;
如1.3的querybuf属性值&#xff0c;服务器将分析、创建下图的argv属性和argc属性&#xff1a;
注&#xff1a;SET命令本身也是一个参数。
1.5 命令的实现函数
服务器从协议内容中分析得出argv属性和argc属性的值后&#xff0c;服务器根据argv[0]的值&#xff0c;在命令表中查找命令所对应的命令实现函数。
命令表是一个字典&#xff0c;字典的键是SDS结构&#xff0c;保存了命令的名字&#xff0c;字典的值是命令对应的redisCommand结构。该结构保存了命令的实现函数、命令的标志、命令应该给定的参数个数、命令的总执行次数和总耗时等统计信息。命令表如下图所示&#xff1a;
当程序在命令表中找到argv[0]所对应的redisCommand结构时&#xff0c;它会将客户端状态的cmd指针指向这个结构&#xff1a;
type struct redisClient {int fd;int flags;sds querybuf;robj **argv;int argc;struct redisCommand *cmd;
} redisClient;
之后服务器使用cmd属性指向的redisCommand结构、argv、argc属性的值调用相关命令实现函数&#xff0c;执行客户端指定的命令。
示例&#xff1a;
当argv[0]为"SET"时&#xff0c;查找命令表并将客户端状态的cmd指针指向redisCommand结构的过程如下图所示&#xff1a;
注&#xff1a;命令表的查找操作不区分大小写。
1.6 输出缓冲区
执行命令所得的命令回复会被保存在客户端状态的输出缓冲区里&#xff0c;每个客户端都有两个输出缓冲区可用&#xff1a;
- 固定大小缓冲区&#xff1a;保存那些长度比较小的回复&#xff0c;如OK、简短的字符串值、整数值、错误回复等。
- 可变大小缓冲区&#xff1a;保存那些长度较大的回复&#xff0c;如非常长的字符串值、很多项组成的列表、包含很多元素的集合等。
客户端的固定大小缓冲区由buf和bufpos两个属性组成&#xff1a;
- buf&#xff1a;数组长度为REDIS_REPLY_CHUNK_BYTES&#xff0c;默认值是16*1024&#xff0c;也就是16KB
- bufpos&#xff1a;记录了数组目前已经使用的字节数量
type struct redisClient {int fd;int flags;sds querybuf;robj **argv;int argc;struct redisCommand *cmd;char buf[REDIS_REPLY_CHUNK_BYTES];int bufpos;
} redisClient;
示例&#xff1a;
一个固定大小缓冲区保存&#43;OK\r\n
&#xff0c;如下图所示&#xff1a;
当buf数组空间用完或回复太大无法放进buf数组里&#xff0c;服务器就会使用可变大小的缓冲区。
可变大小的缓冲区由reply链表和一个或多个字符串对象组成&#xff1a;
type struct redisClient {int fd;int flags;sds querybuf;robj **argv;int argc;struct redisCommand *cmd;char buf[REDIS_REPLY_CHUNK_BYTES];int bufpos;list *reply;
} redisClient;
示例&#xff1a;
一个包含三个字符串对象的reply链表如下图所示&#xff1a;
为了避免发送给客户端回复过大&#xff0c;占用过多服务器资源&#xff0c;服务器会时刻检查客户端的输出缓冲区大小&#xff0c;并在缓冲区的大小超出范围时&#xff0c;执行相应的限制操作。
服务器使用两种模式限制客户端输出缓冲区的大小&#xff1a;
- 硬性限制&#xff08;hard limit&#xff09;&#xff1a;如果输出缓冲区大小超过了硬性限制所设置的大小&#xff0c;服务器立即关闭客户端
- 软性限制&#xff08;soft limit&#xff09;&#xff1a;
- 如果缓冲区大小超过软性限制所设置的大小&#xff0c;但没超过硬性限制&#xff0c;那么服务器将使用客户端状态结构的
obuf_soft_limit_reached_time
属性记录下客户端到达软性限制的起始时间&#xff1b;之后服务器会继续监视客户端&#xff0c;如果输出缓冲区的大小一致超过软性限制&#xff0c;且持续时间超过服务器设置的时长&#xff0c;那么服务器将关闭客户端&#xff1b; - 如果输出缓冲区的大小在指定时间之内不再超过软性限制&#xff0c;那么客户端不会给关闭&#xff0c;且
obuf_soft_limit_reached_time
属性值被清零。
使用 client-output-buffer-limit
选项可以为普通客户端、从服务器客户端、执行发布订阅的客户端分别设置不同的软性限制和硬性限制。
三个设置示例&#xff1a;
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
1.7 身份验证
客户端状态的authenticated
属性用于记录客户端是否通过了身份验证&#xff1a;
type struct redisClient {int fd;int flags;sds querybuf;robj **argv;int argc;struct redisCommand *cmd;char buf[REDIS_REPLY_CHUNK_BYTES];int bufpos;list *reply;int authenticated;
} redisClient;
- authenticated&#xff1a;
- 0&#xff1a;表示客户端未通过身份验证。此时除了AUTH命令&#xff0c;其他命令都会被服务器拒绝执行&#xff08;服务器需要开启身份验证功能&#xff0c;参考配置文件里的
requirepass
选项&#xff09; - 1&#xff1a;表示客户端通过了身份验证
1.8 时间
客户端中与时间有关的几个属性&#xff1a;ctime、lastinteraction、obuf_soft_limit_reached_time。
type struct redisClient {int fd;int flags;sds querybuf;robj **argv;int argc;struct redisCommand *cmd;char buf[REDIS_REPLY_CHUNK_BYTES];int bufpos;list *reply;int authenticated;time_t ctime;time_t lastinteraction;time_t obuf_soft_limit_reached_time;
} redisClient;
- ctime&#xff1a;记录了客户端创建的时间&#xff0c;用来记算客户端与服务器连接的时间&#xff0c;
client list
命令中的age选项记录了这个时间。 - lastinteraction&#xff1a;记录了客户端与服务器最后一次交互的时间。如客户端向服务端发送命令请求、服务器向客户端发送命令回复。
- 该属性还可以用来计算客户端空转&#xff08;idle&#xff09;时间&#xff0c;即距离客户端与服务器最后一次进行交互过去的时间。
client list
命令中的idle选项记录了这个时间。
- obuf_soft_limit_reached_time&#xff1a;记录了输出缓冲区第一次到达软性限制&#xff08;soft limit&#xff09;的时间。
二、客户端的创建与关闭
2.1 普通客户端
2.1.1 创建普通客户端
如果客户端通过网络连接与服务器进行连接的&#xff0c;当客户端使用connect函数连接到服务器时&#xff0c;服务器就会调用连接事件处理器为客户端创建相应的客户端状态&#xff0c;并将这个新的客户端状态添加到服务器状态结构clients链表的末尾。
示例&#xff1a;
如已有客户端c1、c2&#xff0c;新的客户端c3连接到服务器后&#xff0c;就会将新创建的c3客户端状态添加到clients链表末尾&#xff1a;
2.1.2 关闭普通客户端
一个普通客户端会因为多种原因被关闭&#xff1a;
- 客户端进程退出或被杀死&#xff0c;客户端与服务器之间的网络连接将被关闭
- 客户端向服务器发送了带有不符合协议格式的命令请求
- 客户端成为了CLIENT KILL目标
- 用户为服务器设置了timeout配置选项&#xff0c;当客户端的空转时间超过timeout选项设置的值。一些例外情况&#xff0c;
- 客户端是主服务器&#xff08;打开了REDIS_MASTER标志&#xff09;&#xff0c;从服务器&#xff08;打开了REDIS_SLAVE标志&#xff09;&#xff0c;正在被BLOOP等命令阻塞&#xff08;打开了REDIS_BLOCKED标志&#xff09;&#xff0c;或者正在执行发布订阅等命令&#xff0c;即使超过了timeout设置的值也不会被关闭
- 客户端发送的命令请求大小超过输入缓冲区的限制&#xff08;默认1GB&#xff09;
- 要发送给客户端的命令回复大小超过了输出缓冲区的限制大小
2.2 Lua脚本伪的客户端
服务器在初始化时创建负责执行Lua脚本中包含Redis命令的伪客户端&#xff0c;并将这个伪客户端关联在服务器状态结构的lua_client属性中&#xff1a;
struct redisServer {redisClient *lua_client;
} redisClient;
注&#xff1a;lua_client伪客户端在服务器运行整个生命周期会一直存在&#xff0c;只有服务器关闭&#xff0c;这个客户端才会被关闭。
2.3 AOF文件的伪客户端
服务器在载入AOF文件时会创建用于执行AOF文件包含的Redis命令的伪客户端&#xff0c;在载入完成之后&#xff0c;关闭这个伪客户端。