Redis設(shè)計 - 客戶端

一、客戶端概述

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_CASREDIS_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)閉這個客戶端。


querybuf屬性示例
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ù)組的長度。
argv屬性和argc屬性示例.png
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ū)示例

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

typedef struct redisClient {
    // ...
    list *reply;
    
} redisClient;
可變大小緩沖區(qū)示例
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á)到管理客戶端的目的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容