Redis服務(wù)器是典型的一對多服務(wù)器程序:一個服務(wù)器可以與多個客戶端建立網(wǎng)絡(luò)連接,每個客戶端可以向服務(wù)器發(fā)送命令請求,而服務(wù)器則接受并處理客戶端發(fā)送的命令請求,并向客戶端返回命令回復(fù)。
通過使用由I/O多路復(fù)用技術(shù)實現(xiàn)的文件事件處理器,Redis服務(wù)器使用單線程單進(jìn)程的方式來處理命令請求,并與多個客戶端進(jìn)行網(wǎng)絡(luò)通信。
對于每個與服務(wù)器進(jìn)行連接的客戶端,服務(wù)器都為這些客戶端建立了相應(yīng)的redis.h/redisClient結(jié)構(gòu)(客戶端狀態(tài)),這個結(jié)構(gòu)保存了客戶端當(dāng)前的狀態(tài)信息,以及執(zhí)行相關(guān)功能時需要用到的數(shù)據(jù)結(jié)構(gòu),其中包括
- 客戶端的套接字描述符
- 客戶端的名字
- 客戶端的標(biāo)志值
- 指向客戶端正在使用的數(shù)據(jù)庫的指針,以及該數(shù)據(jù)庫的號碼
- 客戶端當(dāng)前要執(zhí)行的命令、命令的參數(shù)、命令參數(shù)的個數(shù),以及指向命令實現(xiàn)函數(shù)的指針。
- 客戶端的輸入緩沖區(qū)和輸出緩沖區(qū)。
- 客戶端的復(fù)制狀態(tài)信息,以及進(jìn)行復(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)建時間,客戶端和服務(wù)器最后一次通信時間,以及客戶端的輸出緩沖區(qū)大小超出軟性限制(
soft limit)的時間
Redis服務(wù)器狀態(tài)結(jié)構(gòu)clients屬性是一個鏈表,這個鏈表保存了所有域服務(wù)器連接的客戶端的狀態(tài)結(jié)構(gòu),對客戶端執(zhí)行批量操作,或者查找某個指定的客戶端,都可以通過遍歷clients鏈表來完成。
13.1 客戶端屬性
客戶端狀態(tài)包含的屬性可以分為兩類
- 一類是比較通用的屬性,這些屬性很少與特定功能相關(guān),無論客戶端執(zhí)行的是什么工作,他們都要用到這些屬性。
- 另外一類是和特定功能相關(guān)的屬性,比如操作數(shù)據(jù)庫時需要用到的
db屬性和dictid屬性,執(zhí)行事務(wù)時需要用到的mstate屬性,以及執(zhí)行WATCH命令時需要用到的watched_keys屬性等等。
13.1.1 套接字描述符
客戶端狀態(tài)的fd屬性記錄了客戶端正在使用套接字描述符:
typedef struct redisClient{
// ...
int fd;
// ...
}redisClient;
根據(jù)客戶端類型的不同,fd屬性的值可以是-1或者是大于-1的整數(shù)
- 偽客戶端(
fake client)的fd屬性的值為-1:偽客戶端處理的命令請求來源于AOF文件或者Lua腳本,而不是網(wǎng)絡(luò),所以這種客戶端不需要套接字連接,自然也不需要記錄套接字描述符。目前Redis服務(wù)器會在兩個地方用到為客戶端,一個用于載入AOF文件并還原數(shù)據(jù)庫狀態(tài),而另一個則用于執(zhí)行Lua腳本中包含的Redis命令。 - 普通客戶端的
fd屬性的值為大于-1的整數(shù):普通客戶端使用套接字來與服務(wù)器進(jìn)行通信,所以服務(wù)器會用fd屬性來記錄客戶端套接字的描述符。因為合法的套接字描述符不能是-1,所以普通客戶端的套接字描述符的值必然是大于-1的整數(shù)。
13.1.2 名字
在默認(rèn)情況下,一個連接到服務(wù)器的客戶端是沒有名字的。
使用CLIENT setname命令可以為客戶端設(shè)置一個名字,然給客戶端的身份變得更為清晰。
客戶端的名字記錄在客戶端狀態(tài)的name屬性里面:
typedef struct redisClient{
// ...
robj *name;
// ...
}redisClient;
如果客戶端沒有為自己設(shè)置名字,那么響應(yīng)客戶端狀態(tài)的name屬性指向NULL指針;相反的,如果客戶端為自己設(shè)置了名字,那么name屬性將指向一個字符串對象,而該對象就保存著客戶端的名字。
13.1.3 標(biāo)志
客戶端的標(biāo)志屬性flags記錄了客戶端的角色(role),以及客戶端目前所處的狀態(tài):
typedef struct redisClient{
// ...
int flags;
// ...
}redisClient;
flags屬性的值可以是單個標(biāo)志:
flags=<flag>
也可以是多個標(biāo)志的二進(jìn)制或
flags=<flag1>|<flag2>|...
13.1.4 輸入緩存區(qū)
客戶端狀態(tài)的輸入緩沖區(qū)用于保存客戶端發(fā)送的命令請求:
typedef struct redisClient{
// ...
sds querybuf;
// ...
}redisClient;
輸入緩沖區(qū)的大小會根據(jù)輸入內(nèi)容動態(tài)地縮小或者擴(kuò)大,但它的最大大小不能超過1GB,否者服務(wù)器將關(guān)閉這個客戶端。
13.1.5 命令與命令參數(shù)
在服務(wù)器將客戶端發(fā)送吃的命令請求保存到客戶端狀態(tài)querybuf屬性之后,服務(wù)器將對命令請求的內(nèi)容進(jìn)行分析,并將得出的命令參數(shù)以及命令參數(shù)的個數(shù)分別保存到客戶端狀態(tài)argv屬性和argc屬性:
typedef struct redisClient{
// ...
robj **argv;
int argc;
// ...
}redisClient;
argv屬性是一個數(shù)組,數(shù)組中的每個項都是一個字符串對象,其中argv[0]是要執(zhí)行的名,而之后的其他項是傳給命令的參數(shù)。
argc屬性則負(fù)責(zé)記錄argv數(shù)組的長度。
13.1.6 命令的實現(xiàn)函數(shù)
當(dāng)服務(wù)器從協(xié)議內(nèi)容中分析并得出argv屬性和argc屬性的值之后,服務(wù)器將根據(jù)項argv[0]的值,在命令表中查找命令所對應(yīng)的命令實現(xiàn)函數(shù)。
當(dāng)程序在命令中成功找到argv[0]所對應(yīng)的redisCommand結(jié)構(gòu)時,它會將客戶端狀態(tài)的cmd指向這個結(jié)構(gòu):
typedef struct redisClient{
// ...
struct redisCommand *cmd;
// ...
}redisClient;
之后,服務(wù)器就可以使用cmd屬性所指向的redisCommand結(jié)構(gòu),以及argv,argc屬性中所保存的命令參數(shù)信息,調(diào)用命令實現(xiàn)函數(shù),執(zhí)行客戶端指定的命令。
13.1.7 輸出緩沖區(qū)
執(zhí)行命令所得到命令回復(fù)會被保存在客戶端狀態(tài)的輸出緩沖區(qū)里面,每個客戶端都有兩個輸出緩沖區(qū)可用,一個緩沖區(qū)的大小是規(guī)定的,另一個緩沖區(qū)的大小是可變的:
- 固定大小的緩沖區(qū)用于保存那些長度比較小的回復(fù),比如
OK、簡短的字符串值、整數(shù)值、錯誤回復(fù)等等。 - 可變大小的緩沖區(qū)用于保存那些長度比較大的回復(fù)。
客戶端的固定大小緩沖區(qū)buf和bufpos兩個屬性組成:
typedef struct redisClient{
// ...
char buf[REDIS_REPLY_CHUNK_BYTES];
int bufpos;
// ...
}redisClient;
buf是一個大小為REDIS_REPLY_CHUNK_BYTES字節(jié)的字節(jié)數(shù)組,而bufpos屬性則記錄了buf數(shù)組目前已使用的字節(jié)數(shù)量。
REDIS_REPLY_CHUNK_BYTES常量目前的默認(rèn)值為16*1024,為16KB
當(dāng)buf數(shù)組的空間已經(jīng)用完,或者回復(fù)太大沒有辦法放進(jìn)buf數(shù)組里面時,服務(wù)器就會開始使用可變大小緩沖區(qū)。
可變大小緩沖區(qū)由reply鏈表和一個或多個字符串對象組成:
typedef struct redisClient{
// ...
list *reply;
// ...
}redisClient;
通過使用鏈表來連接多個字符串對象,服務(wù)器可以為客戶端保存一個非常長的命令回復(fù)。
13.1.8 身份驗證
客戶端狀態(tài)的authenticated屬性用于記錄客戶端是否通過了身份驗證:
typedef struct redisClient{
// ...
int authenticated;
// ...
}redisClient;
如果authentication的值為0,那么表示客戶端未通過身份驗證;如果authentication的值為1,那么小時客戶端已經(jīng)通過了身份驗證。
當(dāng)客戶端authentication屬性的值為0時,除了AUTH命令之外,客戶端發(fā)送的所有其他命令都會被服務(wù)器拒絕執(zhí)行。
當(dāng)客戶端通過AUTH命令成功進(jìn)行身份驗證之后,客戶端狀態(tài)authentication屬性的值就會從0變成1,可以正常發(fā)送命令請求
authentication屬性僅在服務(wù)器啟用了身份驗證功能時使用。如果服務(wù)器沒有啟用身份驗證功能的話,那么authentication屬性的值為0,服務(wù)器也不會拒絕執(zhí)行客戶端發(fā)送的命令請求。
13.1.9 時間
typedef struct redisClient{
// ...
time_t ctime;
time_t lastinteraction;
time_t obuf_soft_limit_reached_time;
// ...
}redisClient;
ctime屬性記錄了創(chuàng)建客戶端的時間,這個時間可以用來計算客戶端與服務(wù)器已經(jīng)連接了多少秒
lastinteraction屬性記錄了客戶端與服務(wù)器最后一次進(jìn)行互動的時間。
obuf_soft_limit_reached_time屬性記錄輸出緩存區(qū)第一次到達(dá)軟限制的時間。
13.2 客戶端的創(chuàng)建與關(guān)閉
服務(wù)器使用不同的方式來創(chuàng)建和關(guān)閉不同類型的客戶端。
13.2.1 創(chuàng)建普通客戶端
如果客戶端通過網(wǎng)絡(luò)連接與服務(wù)器進(jìn)行連接的普通用戶客戶端,那么在客戶端使用connect函數(shù)連接到服務(wù)器時,服務(wù)器就會調(diào)用連接事件處理器,為客戶端創(chuàng)建相應(yīng)的客戶端狀態(tài),并將這個新的客戶端狀態(tài)添加到服務(wù)器狀態(tài)結(jié)構(gòu)clients鏈表的末尾。
13.2.2 關(guān)閉普通客戶端
一個普通客戶端可以因為多種原因而被關(guān)閉:
- 客戶端進(jìn)程退出或者被殺死
- 客戶端發(fā)送不符合協(xié)議格式的命令請求
- 客戶端成為了
CLIENT KILL命令的目標(biāo) - 設(shè)置了
timeout配置選項,同時客戶端的空轉(zhuǎn)時間超過這個值。 - 客戶端輸入緩沖區(qū)超過限制大小
- 客戶端輸出緩沖區(qū)超過限制大小
服務(wù)器使用兩種模式來限制客戶端輸出緩沖區(qū)大小:
- 硬性限制:超過硬性限制,立即關(guān)閉
- 軟性限制:超過軟性限制,沒有超過硬性限制。使用
obuf_soft_limit_reached_time記錄時間,如果一直超過限制,并且持續(xù)時間超過服務(wù)器設(shè)定的市場,那么服務(wù)器關(guān)閉客戶端;相反,在規(guī)定的時間內(nèi),沒有超過軟性限制,那么客戶端不會被關(guān)閉,同時obuf_soft_limit_reached_time會被清零。
使用client-output-buffer-limit選項可以為普通客戶端、從服務(wù)器客戶端。執(zhí)行發(fā)布與訂閱功能的客戶端分別設(shè)置不同的軟性限制和硬性限制
client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
13.2.3 Lua腳本的偽客戶端
服務(wù)器會在初始化時創(chuàng)建負(fù)責(zé)執(zhí)行Lua腳本中包含的Redis命令的偽客戶端,并將這個偽客戶端關(guān)聯(lián)在服務(wù)器狀態(tài)結(jié)構(gòu)lua_client屬性中:
struct redisServer{
// ...
redisClient *lua_client;
// ...
};
lua_client偽客戶端在服務(wù)器運行的整個生命期中會一直存在,只有服務(wù)器被關(guān)閉時,這個客戶端才會被關(guān)閉。
13.2.4 AOF文件的偽客戶端
服務(wù)器在載入AOF文件時,會創(chuàng)建用于執(zhí)行AOF文件包含的Redis命令的偽客戶端,并在載入完成之后,關(guān)閉這個偽客戶端。