文章目录
- 前言
- 一、客户端结构体简介
- 1.套接字描述符
- 2.名字
- 3.标志
- 4.输入缓冲区
- 5.命令和命令参数
- 6.命令的实现函数
- 7.输出缓冲区
- 8.身份认证
- 9.时间
- 10.完整结构体
- 二、客户端创建与关闭
- 1.普通客户端创建
- 2.普通客户端关闭
- 3.lua脚本的伪客户端
- 4.aof文件的伪客户端
- 总结
前言
Redis服务端是典型的一对多程序,可以为多个客户端提供服务,Redis服务端结构体中的clients链表中保存了所有的客户端信息,如下所示:
代码语言:javascript复制struct redisServer {
// 一个链表,保存了所有客户端状态结构
list *clients; /* List of active clients */
};
本文对Redis的客户端相关内容进行了简要介绍。
一、客户端结构体简介
1.套接字描述符
客户端的fd属性记录了客户端的套接字描述符,如下所示:
代码语言:javascript复制/* With multiplexing we need to take per-client state.
* Clients are taken in a liked list.
*
* 因为 I/O 复用的缘故,需要为每个客户端维持一个状态。
*
* 多个客户端状态被服务器用链表连接起来。
*/
typedef struct redisClient {
// 套接字描述符
int fd;
} redisClient;
fd除了可以说客户端的已连接套接字外,还可以是-1,此时表明是一个伪客户端。伪客户端表明命令来自AOF文件或者lua脚本。
2.名字
默认情况下连接到服务器的客户端是没有名字的,可以通过client setname命令可以为客户端设置一个名字,设置的名字保存在redisclient的name成员中,如下所示:
代码语言:javascript复制typedef struct redisClient {
// 客户端的名字
robj *name; /* As set by CLIENT SETNAME */
} redisClient;
3.标志
redisClient 的flags成员记录了客户端的角色和状态,如下所示:
代码语言:javascript复制typedef struct redisClient {
// 客户端状态标志
int flags; /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */
} redisClient;
flags属性的可能取值如下所示:
4.输入缓冲区
redisClient中的输入缓冲区保存客户端发送的命令请求,最大不能超过1GB,如下所示:
代码语言:javascript复制typedef struct redisClient {
// 查询缓冲区
sds querybuf;
} redisClient;
5.命令和命令参数
命令保存到querybuf后,服务端会对命令进行解析,将命令参数和参数个数保存到argv成员和argc成员中,如下所示:
代码语言:javascript复制typedef struct redisClient {
// 参数数量
int argc;
// 参数对象数组
robj **argv;
} redisClient;
6.命令的实现函数
服务端将参数及个数放入argv和argc成员以后,argv[0]即为命令名称,服务端会根据命令表查找命令的实现函数,命令表如下所示:
代码语言:javascript复制/*
* Redis 命令
*/
struct redisCommand {
// 命令名字
char *name;
// 实现函数
redisCommandProc *proc;
// 参数个数
int arity;
// 字符串表示的 FLAG
char *sflags; /* Flags as string representation, one char per flag. */
// 实际 FLAG
int flags; /* The actual flags, obtained from the 'sflags' field. */
/* Use a function to determine keys arguments in a command line.
* Used for Redis Cluster redirect. */
// 从命令中判断命令的键参数。在 Redis 集群转向时使用。
redisGetKeysProc *getkeys_proc;
/* What keys should be loaded in background when calling this command? */
// 指定哪些参数是 key
int firstkey; /* The first argument that's a key (0 = no keys) */
int lastkey; /* The last argument that's a key */
int keystep; /* The step between first and last key */
// 统计信息
// microseconds 记录了命令执行耗费的总毫微秒数
// calls 是命令被执行的总次数
long long microseconds, calls;
};
typedef struct redisClient {
// 记录被客户端执行的命令
struct redisCommand *cmd;
} redisClient;
7.输出缓冲区
命令执行完毕后,执行结果会存放在输出缓冲区中。输出缓冲区分为固定缓冲区和可变缓冲区,固定缓冲区用于保存长度较短的回复,可变缓冲区用于回复较长的回复,是一个链表对象,将回复放在链表的字符串中。缓冲区定义如下:
代码语言:javascript复制typedef struct redisClient {
/* 固定缓冲区 */
// 回复偏移量
int bufpos;
// 回复缓冲区
char buf[REDIS_REPLY_CHUNK_BYTES];
// 可变缓冲区
list *reply;
} redisClient;
8.身份认证
redisclient的authenticated成员用于记录客户端是否通过了身份验证,如下所示:
代码语言:javascript复制typedef struct redisClient {
// 当 server.requirepass 不为 NULL 时
// 代表认证的状态
// 0 代表未认证, 1 代表已认证
int authenticated; /* when requirepass is non-NULL */
} redisClient;
当服务端启动了身份认证后,如果客户端的authenticated属性是0,则除了身份认证命令auth以外,其余命令服务器均会拒绝执行。
9.时间
客户端记录了几个和时间有关的属性,如下所示:
代码语言:javascript复制typedef struct redisClient {
// 创建客户端的时间
time_t ctime; /* Client creation time */
// 客户端最后一次和服务器互动的时间
time_t lastinteraction; /* time of the last interaction, used for timeout */
// 客户端的输出缓冲区超过软性限制的时间
time_t obuf_soft_limit_reached_time;
} redisClient;
10.完整结构体
完整的redisclient结构体定义如下:
代码语言:javascript复制/* 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;
二、客户端创建与关闭
1.普通客户端创建
当有一个新的客户端连接到服务端时,服务端就会创建一个redisclient对象,并添加到redisserver的clients链表中(见前言)。
2.普通客户端关闭
当客户端退出或者被杀死、客户端发送的命令格式错误、客户端输出缓冲区大小超限时,客户端就会被关闭,对应的redisclient对象从链表中被移除。
3.lua脚本的伪客户端
服务器在初始化时会创建负责执行lua脚本命令的伪客户端,伪客户端保存在redisserver的lua_client中,如下所示:
代码语言:javascript复制typedef struct redisClient {
// 复制执行 Lua 脚本中的 Redis 命令的伪客户端
redisClient *lua_client; /* The "fake client" to query Redis from Lua */
} redisClient;
这个客户端在服务器运行时一直存在,服务器退出时才关闭。
4.aof文件的伪客户端
服务器在载入aof文件时,会创建执行aof命令的伪客户端,载入完成后立即关闭。
总结
本文对redis客户端结构的主要成员做了简要介绍,如有不当,请多多指正。