Redis 是一个客户端服务端的程序,服务端提供数据存储等等服务,客户端连接服务端并通过向服务端发送命令,读取或写入数据,简单来说,客户端就是某种工具,我们通过它与 Redis 服务端进行通讯并完成数据操作。
客户端并不是 Redis 的核心,Redis 的核心是它的服务端程序,服务端程序才是完成数据存、取,持久化等等我们使用频繁的各种操作的执行者。但也不是说客户端就没什么作用,客户端在整个 Redis 服务体系中也是非常重要的一环。本篇先来看看 Redis 客户端的一些特性以及实现原理。
一、客户端的基本属性
redis 中为客户端抽象的数据结构是,server.h/client 结构,我这里是 redis-4.0.x 版本,不同版本或许稍有不同,每一个 redis 客户端成功的连接上服务端之后,服务端就会创建一个 client 结构实例,并以链表的形式链接所有连接成功的客户端。
这个结构最主要作用就是存储当前客户端的大量属性,套接字、名字、标志,状态等等信息,这些信息非常的重要,当服务端为客户端服务时,很多的信息例如当前要执行的命令、参数都会从这里获取。我们一个一个来了解。
1、客户端名称
默认情况下,所有连接成功的客户端都是没有名字的,这一点你可以通过向服务发送 client list 命令验证,它会返回当前服务端成功建立的客户端以及他们的基本信息。例如:
可以看到,name 字段默认是空,如果你想让你的客户端辨识度更高,你可以向服务端发送 client setname 为你的客户端命名,这里我就不做演示了,客户端名称这个信息保存在 client 结构中的 name 字段里。
typedef struct client {
.........
robj *name; /* As set by CLIENT SETNAME. */
.........
} client;
2、标志
标志用于描述当前 redis 客户端的一些状态或者角色,对应的到数据结构中就是一个整型字段。
typedef struct client {
.........
int flags; /* Client flags: CLIENT_* macros. */
.........
} client;
Redis 中定义了很多的客户端标志,
一个整型的 flags 字段,可以通过二进制或(|) 的方式同时存储过个状态,比如:
flags = 0000 0110 = CLIENT_MASTER | CLIENT_MONITOR
当然了,上面那个 flages 的值只是举了个例子,描述了当前客户端是一个主节点的 server(当进行主从节点复制的时候,主节点会作为客户端连接从节点发送 RDB 文件给客户端),又正在执行 MONITOR 命令。前者描述了客户端角色,后者描述客户端状态。
总而言之,redis 客户端 flags 字段可以描述当前客户端的角色,也可以记录当前客户端各种状态信息,是服务端了解客户端信息的一个非常重要的字段。
3、输入/输出缓冲区
redis 服务端收到客户端发来的命令请求需要很多步骤来处理和调用相关命令的实现,并最终将数据返回给客户端,那么输入缓冲区其实就是一小块内存,用于存储客户端发送过来的命令,包括参数,这块内存空间默认不能超过 1GB,否则 redis 服务端就会强制关闭与该客户端的连接。
typedef struct client {
.........
sds querybuf; /* Buffer we use to accumulate client queries. */
.........
} client;
querybuf 就是客户端缓冲区,它是一个 SDS 类型的字段,那么说明这是一个可以动态扩充输入缓冲区。
当然我们也可以通过 client list 看看当前客户端的的 querybuf 分配和使用情况。
其中 qbuf 和 qbuf-free 用于描述客户端输入缓冲区状态。我这里的这个没有写入过大的命令,所以这里的 querybuf 只分配了 32768 个字节。
ps:尽量不要使用过大的 KEY,这样会导致客户端 querybuf 占用过多内存,这样会导致 redis 服务端程序占用过高内存,如果超过 maxmemory 限制,会触发 KEY 的 LRU 淘汰或程序异常。
除此之外,redis 客户端还有一个输出缓冲区,用于缓存服务端响应的回复。
输出缓冲区有两种,一种是固定大小的,用于存储服务端简单的响应,例如:OK,错误信息等。还有一种是非固定长度的缓冲区,它的长度是可动态扩展的,用于存储一些较长的响应内容。
typedef struct client {
.........
/* Response buffer */
int bufpos;
char buf[PROTO_REPLY_CHUNK_BYTES];
.........
} client;
PROTO_REPLY_CHUNK_BYTES 等于 16*1024,也就是默认固定输出缓冲区只有 16K,bufpos 记录当前固定缓冲区已经使用的字节数。
typedef struct client {
.........
list *reply; /* List of reply objects to send to the client. */
.........
} client;
动态缓冲区用链表实现,可以为我们返回较大的 key,例如一些 set、list 集合等等。我们可以通过 client list 命令查看输出缓冲区的使用情况。
obl 表示固定缓冲区长度,oll 代表动态缓冲区长度,omem 表示固定缓冲区和动态缓冲区总共占用了多少字节。
ps:输出缓冲区可以通过配置 client-output-buffer-limit 限制最大内存上限,同样如果滥用,一样会导致 redis 服务器内存飙升,建议尽量配置小一点的输出缓存区大小。
二、客户端三种类型
redis 客户端主要分为三种,普通客户端、发布订阅客户端、slave 客户端。普通客户端我们不用多说,也是我们用的最多的客户端。
redis 客户端可以订阅任意数量的频道,如果你订阅了某个服务器频道,那么你的客户端就也是一个发布订阅客户端,当频道中有新消息,服务器会向你推送,关于 redis 的发布订阅功能,我们后续会详细介绍。
当 redis 集群中部署了主从节点的时候,所有的从节点服务器又称为 slave 客户端,他们会向 master 定期拉取最新数据,详细的内容后续介绍。
下一篇我们分析 redis 的服务端程序实现,以及神秘的 serverCron 定时函数实现。