Redis 是一个客户端服务端的程序,服务端提供数据存储等等服务,客户端连接服务端并通过向服务端发送命令,读取或写入数据,简单来说,客户端就是某种工具,我们通过它与 Redis 服务端进行通讯并完成数据操作。
客户端并不是 Redis 的核心,Redis 的核心是它的服务端程序,服务端程序才是完成数据存、取,持久化等等我们使用频繁的各种操作的执行者。但也不是说客户端就没什么作用,客户端在整个 Redis 服务体系中也是非常重要的一环。
Redis服务器是一个典型的一对多服务器程序:一个服务器可以与多个客户端建立网络连接,每个客户端可以向服务器发送命令请求,而服务器则接受并处理客户端发送的命令请求,并向客户端返回命令回复。通过使用由I/O多路复用技术实现的文件事件处理器,Redis服务器使用单线程单进程的方式来处理命令请求,并与多个客户端进行网络通信。
服务器状态结构的clients属性是一个链表,这个链表保存了所有与服务器连接的客户端的状态结构,对客户端执行批量操作,或者查找某个指定的客户端,都可以通过遍历clients链表来完成:
struct redisServer{ //,., // 一个链表,保存了所有客户端状态 list * clients; }
redis 中为客户端抽象的数据结构是,server.h/client 结构,我这里是 redis-4.0.x 版本,不同版本或许稍有不同,每一个 redis 客户端成功的连接上服务端之后,服务端就会创建一个 client 结构实例,并以链表的形式链接所有连接成功的客户端。
这个结构最主要作用就是存储当前客户端的大量属性,套接字、名字、标志,状态等等信息,这些信息非常的重要,当服务端为客户端服务时,很多的信息例如当前要执行的命令、参数都会从这里获取。我们一个一个来了解。
默认情况下,所有连接成功的客户端都是没有名字的,这一点你可以通过向服务发送 client list 命令验证,它会返回当前服务端成功建立的客户端以及他们的基本信息。例如:
可以看到,name 字段默认是空,如果你想让你的客户端辨识度更高,你可以向服务端发送 client setname 为你的客户端命名,这里我就不做演示了,客户端名称这个信息保存在 client 结构中的 name 字段里。
typedef struct client { ......... robj *name; /* As set by CLIENT SETNAME. */ ......... } client;
2、套接字
客户端套接字由客户端状态的 fd 属性记录
当 fd 属性值为-1 时,表示这个客户端是伪客户端。伪客户端的请求命令不是来源于网络的,而是来源于 Lua 脚本或 AOF 文件(后续详细介绍)的,所以伪客户端不需要套接字连接,它也没有套接字描述符。当我们执行的 Lua 脚本中含有 Redis 命令,或者使用 AOF 文件来还原数据库状态时,就会用到伪客户端。
当 fd 属性值是大于-1 的整数时,表示这个客户端是普通客户端。普通客户端采用相关套接字来实现与服务器的通信,因此服务器会利用 fd 属性来记录客户端套接字的描述符。
标志用于描述当前 redis 客户端的一些状态或者角色,对应的到数据结构中就是一个整型字段。
typedef struct client { ......... int flags; /* Client flags: CLIENT_* macros. */ ......... } client;
Redis 中定义了很多的客户端标志,客户端的标志属性 flags 用来记录客户端的角色(Role)及客户端目前所处的状态。
flags 属性的取值可以是单个标志,也可以是多个二进制或的组合标志,具体如下。
单个标志:flags=<flag>
组合标志:flags=<flag1>|<flag2>|<flag3>|…
标志使用常量来表示。Redis 所具有的所有标志都定义在 redis.h 文件中。
记录客户端角色的标志有如下几个:
记录客户端当前状态的标志有如下几个:
REDIS_DIRTY_CAS 和 REDIS_DIRTY_EXEC 标志的出现都表示 Redis 事务的安全性已被破坏。只要这两个标志中的任何一个被打开,EXEC 命令都会执行失败。而只有在客户端打开了 REDIS_MULTI 标志的情况下,才能使用这两个标志。
当然了,上面那个 flages 的值只是举了个例子,描述了当前客户端是一个主节点的 server(当进行主从节点复制的时候,主节点会作为客户端连接从节点发送 RDB 文件给客户端),又正在执行 MONITOR 命令。前者描述了客户端角色,后者描述客户端状态。
总而言之,redis 客户端 flags 字段可以描述当前客户端的角色,也可以记录当前客户端各种状态信息,是服务端了解客户端信息的一个非常重要的字段。
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 定时函数实现。
https://www.cnblogs.com/undefined22/p/12580818.html