Redis 客戶端屬性

2018-08-02 14:54 更新

客戶端狀態(tài)包含的屬性可以分為兩類:

  • 一類是比較通用的屬性, 這些屬性很少與特定功能相關, 無論客戶端執(zhí)行的是什么工作, 它們都要用到這些屬性。
  • 另外一類是和特定功能相關的屬性, 比如操作數(shù)據(jù)庫時需要用到的 db 屬性和 dictid 屬性, 執(zhí)行事務時需要用到的 mstate 屬性, 以及執(zhí)行 WATCH 命令時需要用到的 watched_keys 屬性, 等等。

本章將對客戶端狀態(tài)中比較通用的那部分屬性進行介紹, 至于那些和特定功能相關的屬性, 則會在相應的章節(jié)進行介紹。

套接字描述符

客戶端狀態(tài)的 fd 屬性記錄了客戶端正在使用的套接字描述符:

typedef struct redisClient {

    // ...

    int fd;

    // ...

} redisClient;

根據(jù)客戶端類型的不同, fd 屬性的值可以是 -1 或者是大于 -1 的整數(shù):

  • 偽客戶端(fake client)的 fd 屬性的值為 -1 : 偽客戶端處理的命令請求來源于 AOF 文件或者 Lua 腳本, 而不是網(wǎng)絡, 所以這種客戶端不需要套接字連接, 自然也不需要記錄套接字描述符。 目前 Redis 服務器會在兩個地方用到偽客戶端, 一個用于載入 AOF 文件并還原數(shù)據(jù)庫狀態(tài), 而另一個則用于執(zhí)行 Lua 腳本中包含的 Redis 命令。
  • 普通客戶端的 fd 屬性的值為大于 -1 的整數(shù): 普通客戶端使用套接字來與服務器進行通訊, 所以服務器會用 fd 屬性來記錄客戶端套接字的描述符。 因為合法的套接字描述符不能是 -1 , 所以普通客戶端的套接字描述符的值必然是大于 -1 的整數(shù)。

執(zhí)行 CLIENT_LIST 命令可以列出目前所有連接到服務器的普通客戶端, 命令輸出中的 fd 域顯示了服務器連接客戶端所使用的套接字描述符:

redis> CLIENT list

addr=127.0.0.1:53428 fd=6 name= age=1242 idle=0 ...
addr=127.0.0.1:53469 fd=7 name= age=4 idle=4 ...

名字

在默認情況下, 一個連接到服務器的客戶端是沒有名字的。

比如在下面展示的 CLIENT_LIST 命令示例中, 兩個客戶端的 name 域都是空白的:

redis> CLIENT list

addr=127.0.0.1:53428 fd=6 name= age=1242 idle=0 ...
addr=127.0.0.1:53469 fd=7 name= age=4 idle=4 ...

使用 CLIENT_SETNAME 命令可以為客戶端設置一個名字, 讓客戶端的身份變得更清晰。

以下展示的是客戶端執(zhí)行 CLIENT_SETNAME 命令之后的客戶端列表:

redis> CLIENT list

addr=127.0.0.1:53428 fd=6 name=message_queue age=2093 idle=0 ...
addr=127.0.0.1:53469 fd=7 name=user_relationship age=855 idle=2 ...

其中, 第一個客戶端的名字是 message_queue , 我們可以猜測它是負責處理消息隊列的客戶端; 第二個客戶端的名字是 user_relationship , 我們可以猜測它為負責處理用戶關系的客戶端。

客戶端的名字記錄在客戶端狀態(tài)的 name 屬性里面:

typedef struct redisClient {

    // ...

    robj *name;

    // ...

} redisClient;

如果客戶端沒有為自己設置名字, 那么相應客戶端狀態(tài)的 name 屬性指向 NULL 指針; 相反地, 如果客戶端為自己設置了名字, 那么 name屬性將指向一個字符串對象, 而該對象就保存著客戶端的名字。

圖 13-3 展示了一個客戶端狀態(tài)示例, 根據(jù) name 屬性顯示, 客戶端的名字為 "message_queue" 。

標志

客戶端的標志屬性 flags 記錄了客戶端的角色(role), 以及客戶端目前所處的狀態(tài):

typedef struct redisClient {

    // ...

    int flags;

    // ...

} redisClient;

flags 屬性的值可以是單個標志:

flags = <flag>

也可以是多個標志的二進制或, 比如:

flags = <flag1> | <flag2> | ...

每個標志使用一個常量表示, 一部分標志記錄了客戶端的角色:

  • 在主從服務器進行復制操作時, 主服務器會成為從服務器的客戶端, 而從服務器也會成為主服務器的客戶端。 REDIS_MASTER 標志表示客戶端代表的是一個主服務器, REDIS_SLAVE 標志表示客戶端代表的是一個從服務器。
  • REDIS_PRE_PSYNC 標志表示客戶端代表的是一個版本低于 Redis 2.8 的從服務器, 主服務器不能使用 PSYNC 命令與這個從服務器進行同步。 這個標志只能在 REDIS_SLAVE 標志處于打開狀態(tài)時使用。
  • REDIS_LUA_CLIENT 標識表示客戶端是專門用于處理 Lua 腳本里面包含的 Redis 命令的偽客戶端。

而另外一部分標志則記錄了客戶端目前所處的狀態(tài):

  • REDIS_MONITOR 標志表示客戶端正在執(zhí)行 MONITOR 命令。
  • REDIS_UNIX_SOCKET 標志表示服務器使用 UNIX 套接字來連接客戶端。
  • REDIS_BLOCKED 標志表示客戶端正在被 BRPOP 、 BLPOP 等命令阻塞。
  • REDIS_UNBLOCKED 標志表示客戶端已經(jīng)從 REDIS_BLOCKED 標志所表示的阻塞狀態(tài)中脫離出來, 不再阻塞。 REDIS_UNBLOCKED 標志只能在REDIS_BLOCKED 標志已經(jīng)打開的情況下使用。
  • REDIS_MULTI 標志表示客戶端正在執(zhí)行事務。
  • REDIS_DIRTY_CAS 標志表示事務使用 WATCH 命令監(jiān)視的數(shù)據(jù)庫鍵已經(jīng)被修改, REDIS_DIRTY_EXEC 標志表示事務在命令入隊時出現(xiàn)了錯誤, 以上兩個標志都表示事務的安全性已經(jīng)被破壞, 只要這兩個標記中的任意一個被打開, EXEC 命令必然會執(zhí)行失敗。 這兩個標志只能在客戶端打開了 REDIS_MULTI 標志的情況下使用。
  • REDIS_CLOSE_ASAP 標志表示客戶端的輸出緩沖區(qū)大小超出了服務器允許的范圍, 服務器會在下一次執(zhí)行 serverCron 函數(shù)時關閉這個客戶端, 以免服務器的穩(wěn)定性受到這個客戶端影響。 積存在輸出緩沖區(qū)中的所有內(nèi)容會直接被釋放, 不會返回給客戶端。
  • REDIS_CLOSE_AFTER_REPLY 標志表示有用戶對這個客戶端執(zhí)行了 CLIENT_KILL 命令, 或者客戶端發(fā)送給服務器的命令請求中包含了錯誤的協(xié)議內(nèi)容。 服務器會將客戶端積存在輸出緩沖區(qū)中的所有內(nèi)容發(fā)送給客戶端, 然后關閉客戶端。
  • REDIS_ASKING 標志表示客戶端向集群節(jié)點(運行在集群模式下的服務器)發(fā)送了 ASKING 命令。
  • REDIS_FORCE_AOF 標志強制服務器將當前執(zhí)行的命令寫入到 AOF 文件里面, REDIS_FORCE_REPL 標志強制主服務器將當前執(zhí)行的命令復制給所有從服務器。 執(zhí)行 PUBSUB 命令會使客戶端打開 REDIS_FORCE_AOF 標志, 執(zhí)行 SCRIPT_LOAD 命令會使客戶端打開 REDIS_FORCE_AOF 標志和 REDIS_FORCE_REPL 標志。
  • 在主從服務器進行命令傳播期間, 從服務器需要向主服務器發(fā)送 REPLICATION ACK 命令, 在發(fā)送這個命令之前, 從服務器必須打開主服務器對應的客戶端的 REDIS_MASTER_FORCE_REPLY 標志, 否則發(fā)送操作會被拒絕執(zhí)行。

以上提到的所有標志都定義在 redis.h 文件里面。

PUBSUB 命令和 SCRIPT LOAD 命令的特殊性

通常情況下, Redis 只會將那些對數(shù)據(jù)庫進行了修改的命令寫入到 AOF 文件, 并復制到各個從服務器: 如果一個命令沒有對數(shù)據(jù)庫進行任何修改, 那么它就會被認為是只讀命令, 這個命令不會被寫入到 AOF 文件, 也不會被復制到從服務器。

以上規(guī)則適用于絕大部分 Redis 命令, 但 PUBSUB 命令和 SCRIPT_LOAD 命令是其中的例外。

PUBSUB 命令雖然沒有修改數(shù)據(jù)庫, 但 PUBSUB 命令向頻道的所有訂閱者發(fā)送消息這一行為帶有副作用, 接收到消息的所有客戶端的狀態(tài)都會因為這個命令而改變。 因此, 服務器需要使用 REDIS_FORCE_AOF 標志, 強制將這個命令寫入 AOF 文件, 這樣在將來載入 AOF 文件時, 服務器就可以再次執(zhí)行相同的 PUBSUB 命令, 并產(chǎn)生相同的副作用。

SCRIPT_LOAD 命令的情況與 PUBSUB 命令類似: 雖然 SCRIPT_LOAD 命令沒有修改數(shù)據(jù)庫, 但它修改了服務器狀態(tài), 所以它是一個帶有副作用的命令, 服務器需要使用 REDIS_FORCE_AOF 標志, 強制將這個命令寫入 AOF 文件, 使得將來在載入 AOF 文件時, 服務器可以產(chǎn)生相同的副作用。

另外, 為了讓主服務器和從服務器都可以正確地載入 SCRIPT_LOAD 命令指定的腳本, 服務器需要使用 REDIS_FORCE_REPL 標志, 強制將SCRIPT_LOAD 命令復制給所有從服務器。

以下是一些 flags 屬性的例子:

# 客戶端是一個主服務器
REDIS_MASTER

# 客戶端正在被列表命令阻塞
REDIS_BLOCKED

# 客戶端正在執(zhí)行事務,但事務的安全性已被破壞
REDIS_MULTI | REDIS_DIRTY_CAS

# 客戶端是一個從服務器,并且版本低于 Redis 2.8
REDIS_SLAVE | REDIS_PRE_PSYNC

# 這是專門用于執(zhí)行 Lua 腳本包含的 Redis 命令的偽客戶端
# 它強制服務器將當前執(zhí)行的命令寫入 AOF 文件,并復制給從服務器
REDIS_LUA_CLIENT | REDIS_FORCE_AOF | REDIS_FORCE_REPL

輸入緩沖區(qū)

客戶端狀態(tài)的輸入緩沖區(qū)用于保存客戶端發(fā)送的命令請求:

typedef struct redisClient {

    // ...

    sds querybuf;

    // ...

} redisClient;

舉個例子, 如果客戶端向服務器發(fā)送了以下命令請求:

SET key value

那么客戶端狀態(tài)的 querybuf 屬性將是一個包含以下內(nèi)容的 SDS 值:

*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n

圖 13-4 展示了這個 SDS 值以及 querybuf 屬性的樣子:

輸入緩沖區(qū)的大小會根據(jù)輸入內(nèi)容動態(tài)地縮小或者擴大, 但它的最大大小不能超過 1 GB , 否則服務器將關閉這個客戶端。

命令與命令參數(shù)

在服務器將客戶端發(fā)送的命令請求保存到客戶端狀態(tài)的 querybuf 屬性之后, 服務器將對命令請求的內(nèi)容進行分析, 并將得出的命令參數(shù)以及命令參數(shù)的個數(shù)分別保存到客戶端狀態(tài)的 argv 屬性和 argc 屬性:

typedef struct redisClient {

    // ...

    robj **argv;

    int argc;

    // ...

} redisClient;

argv 屬性是一個數(shù)組, 數(shù)組中的每個項都是一個字符串對象: 其中 argv[0] 是要執(zhí)行的命令, 而之后的其他項則是傳給命令的參數(shù)。

argc 屬性則負責記錄 argv 數(shù)組的長度。

舉個例子, 對于圖 13-4 所示的 querybuf 屬性來說, 服務器將分析并創(chuàng)建圖 13-5 所示的 argv 屬性和 argc 屬性。

注意, 在圖 13-5 展示的客戶端狀態(tài)中, argc 屬性的值為 3 , 而不是 2 , 因為命令的名字 "SET" 本身也是一個參數(shù)。

命令的實現(xiàn)函數(shù)

當服務器從協(xié)議內(nèi)容中分析并得出 argv 屬性和 argc 屬性的值之后, 服務器將根據(jù)項 argv[0] 的值, 在命令表中查找命令所對應的命令實現(xiàn)函數(shù)。

圖 13-6 展示了一個命令表示例, 該表是一個字典, 字典的鍵是一個 SDS 結(jié)構, 保存了命令的名字, 字典的值是命令所對應的redisCommand 結(jié)構, 這個結(jié)構保存了命令的實現(xiàn)函數(shù)、 命令的標志、 命令應該給定的參數(shù)個數(shù)、 命令的總執(zhí)行次數(shù)和總消耗時長等統(tǒng)計信息。

當程序在命令表中成功找到 argv[0] 所對應的 redisCommand 結(jié)構時, 它會將客戶端狀態(tài)的 cmd 指針指向這個結(jié)構:

typedef struct redisClient {

    // ...

    struct redisCommand *cmd;

    // ...

} redisClient;

之后, 服務器就可以使用 cmd 屬性所指向的 redisCommand 結(jié)構, 以及 argv 、 argc 屬性中保存的命令參數(shù)信息, 調(diào)用命令實現(xiàn)函數(shù), 執(zhí)行客戶端指定的命令。

圖 13-7 演示了服務器在 argv[0] 為 "SET" 時, 查找命令表并將客戶端狀態(tài)的 cmd 指針指向目標 redisCommand 結(jié)構的整個過程。

針對命令表的查找操作不區(qū)分輸入字母的大小寫, 所以無論 argv[0] 是 "SET" 、 "set" 、或者 "SeT , 等等, 查找的結(jié)果都是相同的。

輸出緩沖區(qū)

執(zhí)行命令所得的命令回復會被保存在客戶端狀態(tài)的輸出緩沖區(qū)里面, 每個客戶端都有兩個輸出緩沖區(qū)可用, 一個緩沖區(qū)的大小是固定的, 另一個緩沖區(qū)的大小是可變的:

  • 固定大小的緩沖區(qū)用于保存那些長度比較小的回復, 比如 OK 、簡短的字符串值、整數(shù)值、錯誤回復,等等。
  • 可變大小的緩沖區(qū)用于保存那些長度比較大的回復, 比如一個非常長的字符串值, 一個由很多項組成的列表, 一個包含了很多元素的集合, 等等。

客戶端的固定大小緩沖區(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 常量目前的默認值為 16*1024 , 也即是說, buf 數(shù)組的默認大小為 16 KB 。

圖 13-8 展示了一個使用固定大小緩沖區(qū)來保存返回值 +OK\r\n 的例子。

當 buf 數(shù)組的空間已經(jīng)用完, 或者回復因為太大而沒辦法放進 buf 數(shù)組里面時, 服務器就會開始使用可變大小緩沖區(qū)。

可變大小緩沖區(qū)由 reply 鏈表和一個或多個字符串對象組成:

typedef struct redisClient {

    // ...

    list *reply;

    // ...

} redisClient;

通過使用鏈表來連接多個字符串對象, 服務器可以為客戶端保存一個非常長的命令回復, 而不必受到固定大小緩沖區(qū) 16 KB 大小的限制。

圖 13-9 展示了一個包含三個字符串對象的 reply 鏈表。

身份驗證

客戶端狀態(tài)的 authenticated 屬性用于記錄客戶端是否通過了身份驗證:

typedef struct redisClient {

    // ...

    int authenticated;

    // ...

} redisClient;

如果 authenticated 的值為 0 , 那么表示客戶端未通過身份驗證; 如果 authenticated 的值為 1 , 那么表示客戶端已經(jīng)通過了身份驗證。

舉個例子, 對于一個尚未進行身份驗證的客戶端來說, 客戶端狀態(tài)的 authenticated 屬性將如圖 13-10 所示。

當客戶端 authenticated 屬性的值為 0 時, 除了 AUTH 命令之外, 客戶端發(fā)送的所有其他命令都會被服務器拒絕執(zhí)行:

redis> PING
(error) NOAUTH Authentication required.

redis> SET msg "hello world"
(error) NOAUTH Authentication required.

當客戶端通過 AUTH 命令成功進行身份驗證之后, 客戶端狀態(tài) authenticated 屬性的值就會從 0 變?yōu)?nbsp;1 , 如圖 13-11 所示, 這時客戶端就可以像往常一樣向服務器發(fā)送命令請求了:

# authenticated 屬性的值從 0 變?yōu)?1
redis> AUTH 123321
OK

redis> PING
PONG

redis> SET msg "hello world"
OK

authenticated 屬性僅在服務器啟用了身份驗證功能時使用: 如果服務器沒有啟用身份驗證功能的話, 那么即使 authenticated 屬性的值為 0(這是默認值), 服務器也不會拒絕執(zhí)行客戶端發(fā)送的命令請求。

關于服務器身份驗證的更多信息可以參考示例配置文件對 requirepass 選項的相關說明。

時間

最后, 客戶端還有幾個和時間有關的屬性:

typedef struct redisClient {

    // ...

    time_t ctime;

    time_t lastinteraction;

    time_t obuf_soft_limit_reached_time;

    // ...

} redisClient;

ctime 屬性記錄了創(chuàng)建客戶端的時間, 這個時間可以用來計算客戶端與服務器已經(jīng)連接了多少秒 —— CLIENT_LIST 命令的 age 域記錄了這個秒數(shù):

redis> CLIENT list

addr=127.0.0.1:53428 ... age=1242 ...

lastinteraction 屬性記錄了客戶端與服務器最后一次進行互動(interaction)的時間, 這里的互動可以是客戶端向服務器發(fā)送命令請求, 也可以是服務器向客戶端發(fā)送命令回復。

lastinteraction 屬性可以用來計算客戶端的空轉(zhuǎn)(idle)時間, 也即是, 距離客戶端與服務器最后一次進行互動以來, 已經(jīng)過去了多少秒 —— CLIENT_LIST 命令的 idle 域記錄了這個秒數(shù):

redis> CLIENT list

addr=127.0.0.1:53428 ... idle=12 ...

obuf_soft_limit_reached_time 屬性記錄了輸出緩沖區(qū)第一次到達軟性限制(soft limit)的時間, 稍后介紹輸出緩沖區(qū)大小限制的時候會詳細說明這個屬性的作用。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號