环境说明:redis源码版本 5.0.3;我在阅读源码过程做了注释,git地址:https://gitee.com/xiaoangg/redis_annotation
参考书籍:《redis的设计与实现》
目录
一 客户端属性
1.套接字描述符fd
2.客户端名称name
3.flags标识
4.输入缓冲区querybuf属性
5 命令与命令参数 argv、argc属性
6.命令的实现函数 struct redisCommand *cmd属性
7.输出缓冲区
8.身份验证authenticated属性
9.时间
二.客户端的创建与关闭
1.创建普通客户端
2. 关闭客户端
Redis 服务器是典型的一对多服务器程序:一个服务器可以与多个客户端建立网络连接,接受、处理、回复 每个客户端发送命令请求。
通过使用由 I/O 多路复用技术实现的文件事件处理器, Redis 服务器使用单线程单进程的方式来处理命令请求,并与多个客户端进行网络通信。
服务器都为客户端建立了相应的 server.h/redisClient结构(客户端状态),这个结构保存了客户端当前的状态信息,以及执行相关功能时需要用到的数据结构;
server.h/redisServer中clients链表中;对客户端的批操作,或者查找指定的客户端,都可以通过遍历clients链表来完成:
struct redisServer {
/*
.......
*/
list *clients; /* List of active clients */ //链表 保存来客户端端所有状态
list *clients_to_close; /* Clients to close asynchronously */ //异步关闭的客户端
list *clients_pending_write; /* There is to write or install handler. */
/*
.......
*/
};
一 客户端属性
客户端状态包含的属性可以分为两类:
- 一类是比较通用的属性,这些属性很少与特定功能相关,无论客户端执行的是什么工 作,它们都要用到这些属性。
- 另外一类是和特定功能相关的属性,比如 执行事务时需要用到的mstate属性,以及执行WATCH命令时需要用到的watched_key s属性等。
/**
* 客户端
* 对于多路复用,我们需要记录每个客户端的状态
*/
/* With multiplexing we need to take per-client state.
* Clients are taken in a linked list. */
typedef struct client {
uint64_t id; /* Client incremental unique ID. *///客户端的全局唯一 自增id
int fd; /* Client socket. *///客户单套接字描述符
redisDb *db; /* Pointer to currently SELECTed DB. */ //当前选中的DB
robj *name; /* As set by CLIENT SETNAME. */ //客户端的名字
sds querybuf; /* Buffer we use to accumulate client queries. */ //输入缓冲区,用于保存客户端发送的命令请求
size_t qb_pos; /* The position we have read in querybuf. */ //读到的输入缓存的位置
sds pending_querybuf; /* If this client is flagged as master, this buffer
represents the yet not applied portion of the
replication stream that we are receiving from
the master. */
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size. */
int argc; /* Num of arguments of current command. */ //当前执行命令的 参数的数量
robj **argv; /* Arguments of current command. */ //当前执行命令的 参数数组
struct redisCommand *cmd, *lastcmd; /* Last command executed. */
int reqtype; /* Request protocol type: PROTO_REQ_* */
int multibulklen; /* Number of multi bulk arguments left to read. */
long bulklen; /* Length of bulk argument in multi bulk request. */
list *reply; /* List of reply objects to send to the client. */ //要发送到客户端的 对象列表
unsigned long long reply_bytes; /* Tot bytes of objects in reply list. */
size_t 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; /* Client flags: CLIENT_* macros. */
int authenticated; /* When requirepass is non-NULL. */
int replstate; /* Replication state if this is a slave. */
int repl_put_online_on_ack; /* Install slave write handler on ACK. */
int repldbfd; /* Replication DB file descriptor. */
off_t repldboff; /* Replication DB file offset. */
off_t repldbsize; /* Replication DB file size. */
sds replpreamble; /* Replication DB preamble. */
long long read_reploff; /* Read replication offset if this is a master. */
long long reploff; /* Applied replication offset if this is a master. */
long long repl_ack_off; /* Replication ack offset, if this is a slave. */
long long repl_ack_time;/* Replication ack time, if this is a slave. */
long long psync_initial_offset; /* FULLRESYNC reply offset other slaves
copying this slave output buffer
should use. */
char replid[CONFIG_RUN_ID_SIZE+1]; /* Master replication ID (if master). */
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
char slave_ip[NET_IP_STR_LEN]; /* Optionally given by REPLCONF ip-address */
int slave_capa; /* Slave capabilities: SLAVE_CAPA_* bitwise OR. */
multiState mstate; /* MULTI/EXEC state */
int btype; /* Type of blocking op if CLIENT_BLOCKED. */
blockingState bpop; /* blocking state */
long long woff; /* Last write global replication offset. */
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
sds peerid; /* Cached peer ID. */
listNode *client_list_node; /* list node in client list */
/* Response buffer */ //输出缓冲区
int bufpos;
char buf[PROTO_REPLY_CHUNK_BYTES];
} client;
1.套接字描述符fd
fd 属性的值可以是 -1 或者是大于 -1 的整数:
- 伪客户端(fake client)的fd属性的值为-1:伪客户端处理的命令请求来源于AOF文件或者Lua脚本,而不是网络,所以这种客户端不需要套接字连接;
- 普通客户端的 fd 属性的值为大于 -1的整数:普通客户端使用套接字来与服务器进行通信,服务器会用 fd属性来记录客户端套接字的描述符
tips:执行CLIENT list 命令可以列出目前所有连接到服务器的普通客户端,命令会输出fd值;
2.客户端名称name
在默认情况下,客户端是没有名字的,name属性指向NULL指针;
tips:使用CLIENT setname命令可以为客户端设置一个名字
3.flags标识
客户端的标志属性flags记录了客户端的角色(role),以及客户端目前所处的状态
/**
* Client结构体中flags属性值宏定义
*
* 在主从服务器进行复制操作时,主服务器会成为从服务器的客户端,而从服务器也会成 为主服务器的客户端。
*/
/* Client flags */
/**
* 表示客户端代表的是一个从服务器。
*/
#define CLIENT_SLAVE (1<<0) /* This client is a slave server */
/**
* 表示客户端代表的是一个主服务器。
*/
#define CLIENT_MATER (1<<1) /* This client is a master server */
/**
* 表示客户端正在执行MONITOR命令。
*/
#define CLIENT_MONITOR (1<<2) /* This client is a slave monitor, see MONITOR */
/**
* 表示客户端正在执行事务
*/
#define CLIENT_MULTI (1<<3) /* This client is in a MULTI ontext */
/**
* 表示客户端正在被命令阻塞 如BRPOP、BLPOP等。
*/
#define CIENT_BLOCKED (1<<4) /* The client is waiting in a blocking operatin */
/**
* 表示事务使用WATCH命令监视的数据库键已经被修改,
* 事务的安全性已经被破坏,这两个标记被打开,EXEC命令必然会执行失败。
*/
#define CLIENT_DIRTY_CAS (1<<5) /* Watched keys modified. EXEC will fail. */
/**
* 表示有用户对这个客户端执行了CLIENT KILL命 令,或者客户端发送给服务器的命令请求中包含了错误的协议内容。
* 服务器会将客户端积存 在输出缓冲区中的所有内容发送给客户端,然后关闭客户端。
*/
#define CLIENT_CLOSE_AFTER_REPLY (1<<6) /* Close after writing entire reply. */
/** * 表示客户端已经从CIENT_BLOCKED标志所表示的阻塞状态中脱离出来,不再阻塞。
* REDIS_UNBLOCKED标志只能在CIENT_BLOCKED标志已经打开 的情况下使用。
*/
#define CLIENT_UNBLOCKED (1<<7) /* This client was unblocked and is stored in
server.unblocked_clients *//**
* 表示客户端是处理Lua脚本伪客户端。
*/
#define CLIENT_LUA (1<8) /* This is a n on connected client used by Lua */
/**
* 表示客户端向集群节点(运行在集群模式下的服务器)发送了 ASKING命令。
*/
#define CLIENT_ASKING (1<<9) /* Client issued the ASKING command */
/**
* 客户端的输出缓冲区大小超出了服务器允许的范围,服务器会在下一次执行serverCron函数时关闭这个客户端,
* 以免服务器的稳定性受到这个客户端影响。
* 积存在输出缓冲区中的所有内容会直接被释放,不会返回给客户端。
*/
#define CLIENT_CLOSE_ASAP (1<<10)/* Close this client ASAP */
/**
* 表示服务器使用UNIX套接字来 接客端。
*/
#define CLIENT_UNIX_SOCKET (1<<11) /* Client connected via Unix domain socket */
#define CLIENT_ fail for errors while queueing */
#define CLIENT_MASTER_FORCE_REPLY (1<<13) /* Queue replies even if is master */
/**
* 强制服务器将当前执行的命令写入到AOF文件里面
*/
#define CLIENT_FORCE_AOF (1<<14) /* Force AOF propagation of current cmd. */
/**
* 标志强制主服务器将当前执行的命令复制给所有从服务器
*/
#define CLIENT_FORCE_REPL (1<<15) /* Force replication of current cmd. */
/**
* 表示客户端代表的是一个版本低于Redis2.8的从服务器,
* 主服务器不能使用PSYNC命令与这个从服务器进行同步。这个标志只能在REDIS_SLAVE标志处 于打开状态时使用。
*/
#define CLIENT_PRE_PSYNC (1<<16) /* Instance don'tunderstand PSYNC. */
#define CLIENT_READONLY (1<<17) /* Cluster client is in read-only state. */
#define CLIENT_PUBSUB (1<<18) /* Client is in Pub/Sub mode. */
#define CLIENT_PREVENT_AOF_PROP (1<<19) /* Don't propagate to AOF. */
#define CLIENT_PREVENT_REPL_PROP (1<<20) /* Don't propagate to slaves. */
#define CLENT_PREVENT_PROP (CLIENT_PREVENT_AOF_PROP|CLIENT_PREVENT_RPL_PROP)
#define CLIENT_PENDING_WRITE (1<<21) /* Client has output to send but a write
handler is yet not installed. */
#define CLIENT_REPLY_OFF (1<<22) /* Don't send replies to client. */
#define CLIENT_REPLY_SKIP_NEXT (1<<23) /* Set CLIENT_REPLY_SKIP for next cmd */
#define CLIENT_REPLY_SKIP (1<<24) /* Don't send just this reply.*/
#define CLIENT_LUA_DEBUG (1<<25) /* Run EVAL in debug mode. */
#define CLIENT_LUA_DEBUG_SYNC (1<<26) /* EVAL debugging withoutfork() */
#define CLIENT_MODULE (1<<27) /* Non connected client used by some module. */
#define CLIENT_PROTECTED (1<<28) /* Client should not be freed for now. */
4.输入缓冲区querybuf属性
输入缓冲区用于保存客户端发送的命令请求,输入缓冲区的大小会根据输入内容动态地缩小或者扩大,但它的最大大小不能超过
1GB ,否则服务器将关闭这个客户端。
5 命令与命令参数 argv、argc属性
服务器将querybuf命令请求的内容进行分析,并将得出的命令参数以及命令参数的个数分别保存到客户端状态的 argv 属性和 argc属性;
argv属性是一个数组,数组中的每个项都是一个字符串对象,其中argv[0]是要执行的命令,之后的其他项是传给命令的参数。
argc 属性则负责记录 argv 数组的长度。
6.命令的实现函数 struct redisCommand *cmd属性
当服务器从协议内容中分析并得出argv属性和argc属性的值之后,服务器将根据项 argv[0]的值,在命令表(命令表的定义位于server.c/redisCommandTable)中查找命令所对应的命令实现函数。 将cmd属性指向该实现函数;
7.输出缓冲区
每个客户端都有两 个输出缓冲区可用:
- 固定大小的缓冲区:用于保存那些长度比较小的回复;比如“OK”,简短字符串、错误回复等;
- 可变大小的缓冲区用于保存那些长度比较大的回复;
固定大小缓冲区由 buf 和 bufpos两个属性组成;
buf 是一个大小为 REDIS_REPLY_CHUNK_BYTES 字节的字节数组;bufpos属性记录buf数组目前已使用的字节数量。
可变大小缓冲区由 reply 链表和一个或多个字符串对象组成:
8.身份验证authenticated属性
authenticated的值为0,那么表示客户端未通过身份验证;
authenticated的值为 1,那么表示客户端已经通过了身份验证。
authenticated属性仅在服务器启用了身份验证功能时才有效;
9.时间
ctime属性记录了创建客户端的时间,这个时间可以用来计算客户端与服务器已经连接了多少秒;
lastinteraction属性记录了客户端与服务器最后一次进行互动(interaction)的时间,这里 的互动可以是客户端向服务器发送命令请求,也可以是服务器向客户端发送命令回复。
lastinteraction属性可以用来计算客户端的空转(idle)时间
二.客户端的创建与关闭
1.创建普通客户端
客户端是通过网络连接与服务器进行连接的普通客户端,那么在客户端使用connect 函数连接到服务器时,服务器就会调用连接事件处理器,为客户端创建 相应的客户端状态,并将这个新的客户端状态添加到服务器状态结构 clients 链表的末尾。
创建客户端的入口路径如下
1.server.c/main()
2. server.c/initServer(初始化服务器)
3.ae.c/aeCreateFileEvent (注册acceptTcpHandler tcp处理函数,tcp accept() )
4.networking.c/acceptCommonHandler
5.networking.c/createClient(fd)) 创建客户端;
2. 关闭客户端
一个普通客户端可能因为多种原因而被关闭:
- 客户端进程退出或者被杀死,那么客户端与服务器之间的网络连接将被关闭,从而造成客户端被关闭。
- 客户端向服务器发送了带有不符合协议格式的命令请求,那么这个客户端也会被服务器关闭。
- 客户端成为了CLIENTKILL命令的目标,那么它也会被关闭。
- 服务器设置了timeout配置选项,那么当客户端的空转时间超过timeout选项设置的值时,客户端将被关闭。
- 客户端发送的命令请求的大小超过了输入缓冲区的限制大小(默认为1 么这个客户端会被服务器关闭。
- 发送给客户端的命令回复的大小超过了输出缓冲区的限制大小,那么这个客户端会被服务器关闭。