Redis设计与实现-客户端

141 阅读7分钟

Redis客户端

客户端定义

Redis服务器是典型的一对多服务器程序,通过使用由I/O多路复用技术实现文件事件处理器,Redis服务器使用单进程单线程的方式来处理命令请求,并与多个客户端进行网络通信。

对于每个与服务端进行连接的客户端,服务器都是为这些客户端建立对应的redis.h/redisClient结构来保存客户端当前的状态信息,以及相关功能需要用到的数据结构。

Redis服务器状态结构的clients属性是一个链表,保存了与服务器连接的客户端的状态结构。数据结构如下:

struct redisServer{
	list *clients //一个保存所有客户端状态的链表
}

假设一个服务端与三个客户端对接,他的数据结构是这样的:

image.png

客户端属性

套接字描述符、名字、标志

数据结构标识:

	typedef struct redisClient{
		int fd;//套接字描述符
		robj *name;//名字
		int flags; //标识
	}

套接字描述符

伪客户端的套接字描述符字段值为-1,伪客户端的处理命令来源于AOF文件或者Lua脚本,而不是网络。普通客户端的套接字标识符为大于-1的整数

名字

默认情况下,连接到服务端的客户端是没有名字的,但是可以通过设置name字段让客户端的身份更明确。

标志

客户端的标志属性记录了客户端的角色信息

flags属性可以是单个标志,也可以是多个标志的二进制或。

命令

命令参数与执行程序对应数据结构

	typedef struct redisClient{
		robj **argv //字符串数组,用来保存相应的命令内容
		int argc //字符串数组的大小

		struct redisCommand *cmd //命令所对应的redisCommand结构
	}

服务器将客户端发送的命令请求存储到客户端状态的query_buf属性之后,服务器会对命令的内容进行分析,将分析的结果保存到 argv和argc(长度)属性上,具体的如果是像set key value这样的命令他的存储如下:

image.png

服务器进行命令的解析时,会从argv[0]获取值并与命令数据字典dict(命令表)进行匹配,找到对应的命令信息,存储到cmd属性。这个属性包含给定的参数个数、命令的总执行次数,命令的总耗时等统计信息。服务端通过调用cmd这个参数进行命令的执行。

缓冲区

输入缓冲区与输出缓冲区的结构表示:

  typedef struct redisClient{
  	sds querybuf; //输入缓冲区

  	//固定大小输出缓冲区
  	char buf[REDIS_REPLY_CHUNK_BYTES]
  	int bufpos

  	list *reply; //可变大小输出缓冲区
  }

输入缓冲区是保存客户端输入的命令信息,所以用sds的结构进行存储,它的大小会根据内容动态的缩小或者扩大,但是最大大小如果超过1GB,服务端就会关闭这个客户端。

每个客户端都会有两个输出缓冲区,一个缓冲区大小是固定的,一个缓冲区大小是可变的。

固定大小缓冲区,通过buf数组和bufpos,两个属性组合,buf数组的大小是固定的,bufpos则记录了buf数组目前已经使用的字节数量。固定大小缓冲区主要用来保存长度较小的回复。

当固定大小缓冲区空间使用殆尽或者回复消息过大,则会开始启用可变长大小的输出缓冲区,它的数据结构是通过链表来连接一个或者多个的字符串对象,具体结构如下:

image.png

身份验证

身份验证的数据结构是:

	typedef struct redisClient{
		int authenticated;//身份验证
	}

       如果该字段的值为0表示客户端未通过身份验证,如果该值为1表示客户端通过身份验证。

时间

ctime: 记录了创建客户端的时间,这个时间可以用来计算客户端和服务端已经连接了多少秒

lastinteraction:记录客户端和服务端最后一次进行互动的时间,可以用来计算客户端空转时长

obuf_soft_limit_reached_time:记录输出缓冲区第一次到达软性限制的时间。

客户端创建与关闭

普通客户端

通过网络与服务器连接的普通客户端,在客户端使用connect函数连接到服务器的时候,服务器就会调用连接事件处理器为客户端创建相应的客户端状态,将它添加到clients链表的末尾。

客户端被服务端关闭有很多种情况。

服务器会采用两种模式来限制客户端缓冲区大小:

  • 硬性限制:如果缓冲区大小超出硬性限制的大小,服务端会主动关闭客户端

  • 软性限制:如果客户端没有超出硬性限制,但是在配置的软性限制时长中,持续超出软性限制大小,则服务端会自动关闭客户端,否则不关闭

Lua脚本的伪客户端

服务器会在初始化时,创建负责执行Lua脚本的中包含Redis命令的伪客户端,将这个伪客户端关联到服务器状态结构中的lua_client属性中。

struct redisServer{
	redisClient *lua_client;
}

lua_client伪客户端在服务器运行的整个生命周期都会一直存在,只有服务器被关闭的时候,这个客户端才会关闭。

AOF的伪客户端

服务器在载入AOF文件的时候,会创建用于执行AOF文件的Redis命令伪客户端,并在载入完成之后,关闭这个伪客户端。

总结

  • 服务器状态结构使用 clients 链表连接起多个客户端状态, 新添加的客户端状态会被放到链表的末尾。
  • 客户端状态的 flags 属性使用不同标志来表示客户端的角色, 以及客户端当前所处的状态。
  • 输入缓冲区记录了客户端发送的命令请求, 这个缓冲区的大小不能超过 1 GB 。
  • 命令的参数和参数个数会被记录在客户端状态的 argv 和 argc 属性里面, 而 cmd 属性则记录了客户端要执行命令的实现函数。
  • 客户端有固定大小缓冲区和可变大小缓冲区两种缓冲区可用, 其中固定大小缓冲区的最大大小为 16 KB , 而可变大小缓冲区的最大大小不能超过服务器设置的硬性限制值。
  • 输出缓冲区限制值有两种, 如果输出缓冲区的大小超过了服务器设置的硬性限制, 那么客户端会被立即关闭; 除此之外, 如果客户端在一定时间内, 一直超过服务器设置的软性限制, 那么客户端也会被关闭。
  • 当一个客户端通过网络连接连上服务器时, 服务器会为这个客户端创建相应的客户端状态。 网络连接关闭、 发送了不合协议格式的命令请求、 成为 CLIENT_KILL 命令的目标、 空转时间超时、 输出缓冲区的大小超出限制, 以上这些原因都会造成客户端被关闭。
  • 处理 Lua 脚本的伪客户端在服务器初始化时创建, 这个客户端会一直存在, 直到服务器关闭。
  • 载入 AOF 文件时使用的伪客户端在载入工作开始时动态创建, 载入工作完毕之后关闭。