1.定义
Redis客户端与服务器之间通过TCP协议进行通信。TCP协议是一种流式协议,数据以字节流的形式进行传递,没有固有的”报文”或”报文边界”的概念,如果需要设置边界,需要应用层自行处理。
2.源码
客户端redis.h/redisClient源码如下:
/* With multiplexing we need to take per-client state.
* Clients are taken in a liked list.
*
* 因为 I/O 复用的缘故,需要为每个客户端维持一个状态。
*
* 多个客户端状态被服务器用链表连接起来。
*/
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 reqtype;
// 剩余未读取的命令内容数量
int multibulklen; /* number of multi bulk arguments left to read */
// 命令内容的长度
long bulklen; /* length of bulk argument in multi bulk request */
// 回复链表
list *reply;
// 回复链表中对象的总大小
unsigned long reply_bytes; /* Tot bytes of objects in reply list */
// 已发送字节,处理 short write 用
int sentlen; /* Amount of bytes already sent in the current
buffer or object being sent. */
// 创建客户端的时间
time_t ctime; /* Client creation time */
// 客户端最后一次和服务器互动的时间
time_t lastinteraction; /* time of the last interaction, used for timeout */
// 客户端的输出缓冲区超过软性限制的时间
time_t obuf_soft_limit_reached_time;
// 客户端状态标志
int flags; /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */
// 当 server.requirepass 不为 NULL 时
// 代表认证的状态
// 0 代表未认证, 1 代表已认证
int authenticated; /* when requirepass is non-NULL */
// 复制状态
int replstate; /* replication state if this is a slave */
// 用于保存主服务器传来的 RDB 文件的文件描述符
int repldbfd; /* replication DB file descriptor */
// 读取主服务器传来的 RDB 文件的偏移量
off_t repldboff; /* replication DB file offset */
// 主服务器传来的 RDB 文件的大小
off_t repldbsize; /* replication DB file size */
sds replpreamble; /* replication DB preamble. */
// 主服务器的复制偏移量
long long reploff; /* replication offset if this is our master */
// 从服务器最后一次发送 REPLCONF ACK 时的偏移量
long long repl_ack_off; /* replication ack offset, if this is a slave */
// 从服务器最后一次发送 REPLCONF ACK 的时间
long long repl_ack_time;/* replication ack time, if this is a slave */
// 主服务器的 master run ID
// 保存在客户端,用于执行部分重同步
char replrunid[REDIS_RUN_ID_SIZE+1]; /* master run id if this is a master */
// 从服务器的监听端口号
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
// 事务状态
multiState mstate; /* MULTI/EXEC state */
// 阻塞类型
int btype; /* Type of blocking op if REDIS_BLOCKED. */
// 阻塞状态
blockingState bpop; /* blocking state */
// 最后被写入的全局复制偏移量
long long woff; /* Last write global replication offset. */
// 被监视的键
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
// 这个字典记录了客户端所有订阅的频道
// 键为频道名字,值为 NULL
// 也即是,一个频道的集合
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
// 链表,包含多个 pubsubPattern 结构
// 记录了所有订阅频道的客户端的信息
// 新 pubsubPattern 结构总是被添加到表尾
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
sds peerid; /* Cached peer ID. */
/* Response buffer */
// 回复偏移量
int bufpos;
// 回复缓冲区
char buf[REDIS_REPLY_CHUNK_BYTES];
} redisClient;
3.源码剖析
我在此只剖析redisClient的通用属性,特定功能属性在下篇博文剖析。
(1)fd
根据客户端类型的不同,fd的属性可以是-1(伪客户端)或大于-1(普通客户端)的整数
(2)name
默认情况下,一个连接到服务器的客户端是没有名字的,但可以使用client setname为客户端设置一个名字。
(3)flags
flags记录了客户端的角色及客户端的状态,可以是单个标志,也可以是多个标志。
(4) querybuf
这是客户端的输入缓冲区(sds类型),用于保存客户端发送的命令请求。可以动态缩小或扩大,但不能超过1GB,否则服务器将关闭这个客户端。
(5)argc与argv
参数对象argv:是一个数组,数组中的每一项都是字符串对象,其中argv[0]是要执行的命令,而其它项是传给命令的参数。
参数数量argc:记录argv数组的长度
例:执行set key value命令,argc的值为3,因为set本身也是一个参数。
(6)redisCommand *cmd
当服务器从协议中分析并得出argc与argv属性的值之后,服务器将根据argv[0]的值,在命令表cmd中查找命令所对应redisCommand的命令实现函数。
(7)bufpos和buf[REDIS_REPLY_CHUNK_BYTES]
bufpos:记录了buf数组已使用的字节数量
buf[REDIS_REPLY_CHUNK_BYTES]:大小为REDIS_REPLY_CHUNK_BYTES的字节数组,默认大小16*1024,即16KB。
注:输出缓冲区分为固定大小缓冲区(保存长度较小的回复)和可变大小缓冲区(保存长度大的回复)。
(8)authenticated
记录客户端是否通过了身份验证。若authenticated的值为0,那么客户端未通过身份验证,若authenticated的值为1,表示客户端通过了验证。
4.客户端的创建与关闭
(1)创建客户端
若是普通客户端,使用connect函数连接到服务器时,服务器会调用连接事件处理器,并为客户端创建相应的客户端状态,然后将这个状态添加到服务器状态结构的clients链表的末尾。
(2)关闭客户端
客户端关闭的原因:网络连接关闭、发送了不合适格式的命令请求、执行了Client Kill命令、空转时间超时、输出缓冲区的大小超过控制等
服务器使用硬性限制和软性限制两种模式来限制客户端输出缓冲区的大小
- 硬性限制:若输出缓冲区的大小超过了硬性限制所设置的大小,就立即关闭客户端
- 软性限制:若输出缓冲区的大小超过了软性缓冲区的大小,但没超过硬性限制,则会记录客户端到达软性限制的起始时间,若持续时间服务器设置的时长,则关闭客户端。
(3)伪客户端
- 创建Lua脚本的伪客户端:在服务器初始化时创建,一直持续到服务器关闭。
- 载入AOF文件时使用的伪客户端:在载入时创建,载入完成后关闭。
本人才疏学浅,若有错,请指出,谢谢!
如果你有更好的建议,可以留言我们一起讨论,共同进步!
衷心的感谢您能耐心的读完本篇博文!
参考书籍:《Redis设计与实现(第二版)》—黄健宏