一、客戶端概述
Redis服務(wù)器為每個與之進(jìn)行連接的客戶端,都建立了相應(yīng)的redis.h/redisClient結(jié)構(gòu),這個結(jié)構(gòu)保存了客戶端當(dāng)前的狀態(tài)信息,以及執(zhí)行相關(guān)功能時需要用到的數(shù)據(jù)結(jié)構(gòu),包括:
- 客戶端套接字描述符
- 客戶端名字
- 客戶端標(biāo)志值
- 客戶端使用的數(shù)據(jù)庫指針(Redis有多個數(shù)據(jù)庫,默認(rèn)使用下標(biāo)0號數(shù)據(jù)庫)
- 客戶端當(dāng)前正要執(zhí)行的命令,參數(shù),實現(xiàn)函數(shù)的指針等待。
- 客戶端的輸入緩沖區(qū)、輸出緩沖區(qū)
- 客戶端復(fù)制狀態(tài)信息、以及復(fù)制需要用到的數(shù)據(jù)結(jié)構(gòu)
- 客戶端執(zhí)行BRPOP,BLPOP等列表阻塞命令時使用的數(shù)據(jù)結(jié)構(gòu)
- 客戶端的事務(wù)狀態(tài),以及執(zhí)行watch命令時用到的數(shù)據(jù)結(jié)構(gòu)
- 客戶端執(zhí)行發(fā)布訂閱功能用到的數(shù)據(jù)結(jié)構(gòu)
- 客戶端身份驗證標(biāo)識
- 客戶端的創(chuàng)建時間,最后一次通信時間,客戶端輸出緩沖區(qū)超出軟性(soft limit)限制的時間
Redis服務(wù)器用鏈表(clients)保存了所有與服務(wù)器連接的客戶端
struct redisServer {
// ...
// 一個鏈表,保存了所有客戶端狀態(tài)
list *clients;
// ...
}

二、客戶端屬性
1. 套接字描述符
typedef struct redisClient {
// 套接字描述符
int fd;
} redisClient;
客戶端狀態(tài)(redisClient)的 fd 屬性(int類型)記錄了客戶端正在使用的套接字描述符。根據(jù)客戶端類型不同,fd屬性可以是-1或者是大于-1的整數(shù):
- 偽客戶端的fd屬性值為-1:偽客戶端處理的命令請求來自AOF文件或者Lua腳本,而不是網(wǎng)絡(luò)。目前Redis服務(wù)器兩個地方用到偽客戶端:1)載入AOF文件并還原數(shù)據(jù)庫狀態(tài),2)執(zhí)行Lua腳本中包含的Redis命令。
- 普通客戶端的fd屬性值大于1:因為普通客戶端需要使用套接字來與服務(wù)器進(jìn)行通信。
2. 名字
typedef struct redisClient {
// 名字
robj *name;
} redisClient;
默認(rèn)情況下,連接到服務(wù)器的客戶端都是沒有名字的,但是可以通過CLIENT setname命令為客戶端設(shè)置名字,如果設(shè)置了名字,那么保存名字的屬性 name 就會指向一個字符串對象,否則為NULL。
3. 標(biāo)志
typedef struct redisClient {
// 標(biāo)志
int flags;
} redisClient;
客戶端的標(biāo)志屬性flags記錄了客戶端的角色(role),以及目前客戶端所處的狀態(tài)。
flags的值可以是單個:flags = <flag>,也可以是多個標(biāo)志的二進(jìn)制組合: flags = <flag1> | <flag2>。
每個標(biāo)志使用一個常量表示:
在利用 Redis 主從服務(wù)器實現(xiàn)復(fù)制時,主從服務(wù)器會相互成為對方的客戶端,也就是從服務(wù)器是主服務(wù)器的客戶端,同時主服務(wù)器也是從服務(wù)器的客戶端。Redis 使用REDIS_MASTER 標(biāo)志來表示這個客戶端是主服務(wù)器,而使用 REDIS_SLAVE 標(biāo)志來表示另一個客戶端是從服務(wù)器。
Redis 使用 REDIS_LUA_CLIENT 標(biāo)志來表示該客戶端是一個專門用于處理 Lua 腳本的偽客戶端,它主要用于執(zhí)行 Lua 腳本中包含的 Redis 命令。
Redis 使用 REDIS_PRE_PSYNC 標(biāo)志來表示該客戶端是一個低于 Redis 2.8 版本的從服務(wù)器,此時,對應(yīng)的主服務(wù)器不能使用 PSYNC 命令實現(xiàn)與從服務(wù)器的數(shù)據(jù)同步。只有當(dāng) REDIS_SLAVE 標(biāo)志處于打開狀態(tài)時,才能使用 REDIS_PRE_PSYNC 標(biāo)志。
記錄客戶端當(dāng)前狀態(tài)的標(biāo)志有如下幾個。
REDIS_ASKING 標(biāo)志表示客戶端向運行在集群模式下的服務(wù)器節(jié)點發(fā)送了 ASKING 命令。
REDIS_CLOSE_ASAP 標(biāo)志表示客戶端的輸出緩沖區(qū)過大,超出了服務(wù)器所允許的范圍。當(dāng)服務(wù)器在下一次執(zhí)行 serverCron 函數(shù)時,會關(guān)閉這個輸出緩沖區(qū)過大的客戶端,以此來保證服務(wù)器的穩(wěn)定性不受這個客戶端影響。在關(guān)閉的時候,存儲在這個緩沖區(qū)中的數(shù)據(jù)也會被刪除,并且不會給客戶端返回任何信息。
REDIS_CLOSE_AFTER_REPLY 標(biāo)志表示客戶端給服務(wù)器發(fā)送的命令請求中有錯誤的協(xié)議內(nèi)容,或者用戶在客戶端中執(zhí)行了 CLIENT kill 命令。此時服務(wù)器會將客戶端輸出緩沖區(qū)中存儲的所有數(shù)據(jù)內(nèi)容發(fā)送給客戶端,然后關(guān)閉這個客戶端。
REDIS_DIRTY_CAS 標(biāo)志表示事務(wù)使用 WATCH 命令監(jiān)視的數(shù)據(jù)庫鍵已經(jīng)被修改。
** REDIS_DIRTY_EXEC** 標(biāo)志表示事務(wù)在命令入隊時出現(xiàn)錯誤。
REDIS_DIRTY_CAS 和 REDIS_DIRTY_EXEC 標(biāo)志的出現(xiàn)都表示 Redis 事務(wù)的安全性已被破壞。只要這兩個標(biāo)志中的任何一個被打開,EXEC 命令都會執(zhí)行失敗。而只有在客戶端打開了 REDIS_MULTI 標(biāo)志的情況下,才能使用這兩個標(biāo)志。
REDIS_MULTI 標(biāo)志表示客戶端正處于執(zhí)行事務(wù)的狀態(tài)中。
REDIS_MONITOR 標(biāo)志表示客戶端正處于執(zhí)行 MONITOR 命令的狀態(tài)中。
REDIS_FORCE_AOF 標(biāo)志表示讓服務(wù)器將當(dāng)前正在執(zhí)行的命令強(qiáng)制寫入 AOF 文件中。在執(zhí)行 PUBSUB 命令時,會使客戶端打開 REDIS_FORCE_AOF 標(biāo)志。
REDIS_FORCE_REPL 標(biāo)志表示強(qiáng)制讓主服務(wù)器將當(dāng)前正在執(zhí)行的命令復(fù)制給所有與它連接的從服務(wù)器。當(dāng)執(zhí)行 SCRIPT LOAD 命令時,會使客戶端同時開啟 REDIS_FORCE_AOF 和 REDIS_FORCE_REPL 標(biāo)志。如果要實現(xiàn)主從服務(wù)器可以正確地載入 SCRIPT LOAD 命令指定的腳本,那么服務(wù)器必須使用 REDIS_FORCE_REPL 標(biāo)志,讓主服務(wù)器強(qiáng)制將 SCRIPT LOAD 命令分發(fā)給相應(yīng)的從服務(wù)器。
REDIS_UNIX_SOCKET 標(biāo)志表示服務(wù)器連接客戶端使用的是 UNIX 套接字。
REDIS_BLOCKED 標(biāo)志表示客戶端正處于被 BRPOP、BLPOP 等命令阻塞的狀態(tài)中。
REDIS_UNBLOCKED 標(biāo)志表示客戶端不再阻塞,它從 REDIS_BLOCKED 標(biāo)志的阻塞狀態(tài)中脫離出來。只有在 REDIS_BLOCKED 標(biāo)志被打開的情況下,才能使用 REDIS_UNBLOCKED 標(biāo)志。
REDIS_MASTER_FORCE_REPLY 標(biāo)志:在主從服務(wù)器進(jìn)行命令傳播期間,從服務(wù)器需要向主服務(wù)器發(fā)送 REPLICATION ACK 命令。但是,在發(fā)送此命令之前,從服務(wù)器必須開啟主服務(wù)器對應(yīng)的客戶端的 REDIS_MASTER_FORCE_REPLY 標(biāo)志;否則主服務(wù)器會拒絕執(zhí)行從服務(wù)器發(fā)送的 REPLCATION ACK 命令。
4.輸入緩沖區(qū)
客戶端狀態(tài)的輸入緩沖區(qū)用于保存客戶端發(fā)送的命令請求,Redis從會輸入緩沖區(qū)拉取命令并執(zhí)行。
typedef struct redisClient {
// ...
sds querybuf;
// ...
} redisClient;
輸入緩沖區(qū)的大小會根據(jù)內(nèi)容動態(tài)的縮小或者擴(kuò)大,但它的最大大小不能超過1GB,否則服務(wù)器將關(guān)閉這個客戶端。

5.命令與命令參數(shù)
服務(wù)器將客戶端發(fā)送的命令保存到輸入緩沖區(qū)querybuf屬性之后,服務(wù)器會對命令請求的內(nèi)容進(jìn)行分析,并將得出的命令參數(shù)以及命令參數(shù)的個數(shù)分別保存到客戶端的argv屬性和argc屬性:
typedef struct redisClient {
// 命令參數(shù)(數(shù)組)
robj **argv;
// 命令參數(shù)個數(shù)
int argc;
} redisClient;
- argv屬性是一個數(shù)組,數(shù)組中的每個項都是一個字符串對象,其中args[0]是要實行的命令,后面的是該命令的參數(shù)。
- argc屬性記錄argv數(shù)組的長度。

6.命令的實現(xiàn)函數(shù)
當(dāng)服務(wù)器從協(xié)議內(nèi)容解析得出命令與命令參數(shù)后,服務(wù)器將根據(jù)argv[0]的值,在命令表中查找命令所對應(yīng)的命令實現(xiàn)函數(shù)。
程序在命令表中成功找到argv[0] 對應(yīng)的redisCommand結(jié)構(gòu)時,就會將客戶端狀態(tài)的cmd指針指向這個結(jié)構(gòu)
typedef struct redisClient {
// ...
struct redisCommand *cmd;
} redisClient;
7.輸出緩沖區(qū)
命令回復(fù)的內(nèi)容會被保存在客戶端的輸出緩沖區(qū)里面,每個客戶端有兩個輸出緩沖區(qū)可用,一個緩沖區(qū)是固定大小,另外一個大小是可變的。
- 固定大小的用于保存長度比較小的回復(fù),比如OK,簡短字符串值,整數(shù)值,錯誤回復(fù)等等。
- 可變大小的緩沖區(qū)用于保存那些長度比較大的回復(fù),比如一個非常長的字符串值,一個很多項組成的列表,包含很多元素的集合等等。
固定大小緩沖區(qū)
typedef struct redisClient {
// 字節(jié)數(shù)組,默認(rèn)值16*1024,也就是16KB
char buf[REDIS_REPLY_CHUNK_BYTES];
// 已使用的字節(jié)數(shù)量
int bufpos;
} redisClient;
REDIS_REPLY_CHUNK_BYTES常量默認(rèn)值16*1024,也就是16KB

可變大小緩沖區(qū)
buf數(shù)組的空間用完,或者因為回復(fù)內(nèi)容太大沒法放進(jìn)buf數(shù)組時,服務(wù)器會開始使用可變大小緩沖區(qū)。
typedef struct redisClient {
// ...
list *reply;
} redisClient;

8.身份驗證
客戶端狀態(tài)的authenticated屬性用于記錄客戶端是否通過了身份驗證:
typedef struct redisClient {
// ...
int authenticated;
} redisClient;
如果authenticated=0,表示客戶端未通過身份驗證;如果authenticated=1,表示通過身份驗證。
authenticated屬性只在服務(wù)器啟用了身份驗證功能的時候有效,如果沒啟用,就算為0,服務(wù)器也不會拒絕客戶端的命令。
三、客戶端的創(chuàng)建與關(guān)閉
1. 創(chuàng)建普通客戶端
客戶端通過網(wǎng)絡(luò)與服務(wù)器進(jìn)行連接,那么客戶端使用connect函數(shù)連接時,服務(wù)器就會調(diào)用連接事件處理器,為這個客戶端創(chuàng)建相應(yīng)的數(shù)據(jù)對象,保存信息,并且追加到服務(wù)器狀態(tài)的clients末尾。
2. 關(guān)閉普通客戶端
客戶端可以因為多種原因而被關(guān)閉:
- 客戶端自身退出或者被殺死,導(dǎo)致網(wǎng)絡(luò)連接斷開,造成客戶端關(guān)閉
- 客戶端發(fā)送帶有不符合協(xié)議格式的命令請求,那么該客戶端也會被關(guān)閉
- 客戶端成為CLIENT KILL命令的目標(biāo),則會被關(guān)閉
- 服務(wù)器設(shè)置了timeout選項,客戶端空轉(zhuǎn)時長過長也會被關(guān)閉
- 客戶端發(fā)送的命令請求大小超過了緩沖區(qū)限制,則會被關(guān)閉
- 客戶端的命令回復(fù)大小超過了輸出緩沖區(qū)限制,則會被關(guān)閉
3. lua腳本的偽客戶端
服務(wù)器初始化時會創(chuàng)建一個lua腳本的偽客戶端,并將這個客戶端關(guān)聯(lián)在服務(wù)器的lua_clients屬性中,這個客戶端在服務(wù)器運行的整個生命周期中一直存在。
typedef struct redisClient {
// ...
redisClient *lua_client;
} redisClient;
4. AOF文件的偽客戶端
服務(wù)器載入AOF文件時,會創(chuàng)建用于執(zhí)行AOF文件包含的Redis命令的偽客戶端,載入完成后立即關(guān)閉。
四、總結(jié)
Redis服務(wù)器通過維護(hù)客戶端的元數(shù)據(jù)信息,從而達(dá)到管理客戶端的目的。