13 客戶端

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ū)bufbufpos兩個屬性組成:

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

?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,534評論 19 139
  • 1.Redis特性 1)速度快:數(shù)據(jù)存放在內(nèi)存上、基于C語言實現(xiàn)、單線程架構(gòu)預(yù)防多線程競爭問題;2)基于鍵值對的數(shù)...
    Sponge1128閱讀 724評論 0 1
  • 半壁東南五楚雄,劉郎死去霸圖空。 幾時痛飲黃龍酒,橫攬江流一奠公。 黃易先生也走了,曌,一路好走。 浙東雁蕩山...
    蠡舟閱讀 268評論 0 3
  • 文/國境之南 -1- 記得清楚,那年你的生日。 提前了好久,我開始做準(zhǔn)備,想過許多有創(chuàng)意的,將我們的合照找出來,找...
    國境之南_閱讀 860評論 4 12
  • 有事要回老家,列車出行必須先到北京。而18點北京出發(fā)的列車,從保定來比較之后就只有1點40分出發(fā)15點多到的一趟符...
    樂播報閱讀 299評論 0 3

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