redis學習筆記——客戶端

Redis服務器是典型的一對多服務器程序:一個服務器能夠與多個客戶端創建網絡鏈接,每一個客戶端能夠向服務器發送命令請求,而服務器則接收並處理客戶端發送的命令請求,並向客戶端返回命令回覆。redis

對於每一個與服務器進行鏈接的客戶端,服務器都爲這些客戶端創建了相應的redis.h/redisClient結構(客戶端狀態),這個結構保存了客戶端當前的狀態信息,以及執行相關功能時須要用到的數據結構,其中包括:數據庫

  • 客戶端的套接字描述符;
  • 客戶端的名字;
  • 客戶端的標誌值(flag);
  • 指向客戶端正在使用的數據庫的指針,以及該數據庫的號碼;
  • 客戶端當前要執行的命令、命令的參數、命令參數的個數,以及指向命令實現函數的指針;
  • 客戶端的輸入緩衝區和輸出緩衝區;
  • ·客戶端的複製狀態信息,以及進行復制所需的數據結構;
  • 客戶端執行BRPOP、BLPOP等列表阻塞命令時使用的數據結構;
  • 客戶端的事務狀態,以及執行WATCH命令時用到的數據結構;
  • 客戶端執行發佈與訂閱功能時用到的數據結構;
  • 客戶端的身份驗證標誌;
  • 客戶端的建立時間,客戶端和服務器最後一次通訊的時間,以及客戶端的輸出緩衝區大小超出軟性限制(soft limit)的時間。

Redis服務器狀態結構的clients屬性是一個鏈表,這個鏈表保存了全部與服務器鏈接的客戶端的狀態結構,對客戶端執行批量操做,或者查找某個指定的客戶端,均可以經過遍歷clients鏈表來完成:數組

struct redisServer {
    // ...
    // 
一個鏈表,保存了全部客戶端狀態
    list *clients;
    // ...
};

 

客戶端屬性

套接字描述符

客戶端狀態的fd屬性記錄了客戶端正在使用的套接字描述符:安全

typedef struct redisClient {
    // ...
    int fd;
    // ...
} redisClient;

 

根據客戶端類型的不一樣,fd屬性的值能夠是-1或者是大於-1的整數:服務器

  • 僞客戶端(fake client)的fd屬性的值爲-1:僞客戶端處理的命令請求來源於AOF文件或者Lua腳本,而不是網絡,因此這種客戶端不須要套接字鏈接,天然也不須要記錄套接字描述符;
  • 普通客戶端的fd屬性的值爲大於-1的整數:普通客戶端使用套接字來與服務器進行通訊,因此服務器會用fd屬性來記錄客戶端套接字的描述符。

執行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 ...數據結構

名字函數

在默認狀況下,一個鏈接到服務器的客戶端是沒有名字的。如上面;spa

使用CLIENT setname命令能夠爲客戶端設置一個名字,讓客戶端的身份變得更清晰。指針

客戶端的名字記錄在客戶端狀態的name屬性裏面:

typedef struct redisClient {
    // ...
    robj *name;
    // ...
} redisClient;

標誌

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

typedef struct redisClient {
    // ...
    int flags;
    // ...
} redisClient;

flags屬性的值能夠是多個標誌或:

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

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

  • 在主從服務器進行復制操做時,主服務器會成爲從服務器的客戶端,而從服務器也會成爲主服務器的客戶端。REDIS_MASTER標誌表示客戶端表明的是一個主服務器,REDIS_SLAVE標誌表示客戶端表明的是一個從服務器;
  • REDIS_LUA_CLIENT標識表示客戶端是專門用於處理Lua腳本里麪包含的Redis命令的僞客戶端;

另一部分標誌則記錄了客戶端目前所處的狀態:

  • REDIS_MONITOR標誌表示客戶端正在執行MONITOR命令;
  • REDIS_UNIX_SOCKET標誌表示服務器使用UNIX套接字來鏈接客戶端;
  • REDIS_BLOCKED標誌表示客戶端正在被BRPOP、BLPOP等命令阻塞;
  • REDIS_UNBLOCKED標誌表示客戶端已經從REDIS_BLOCKED標誌所表示的阻塞狀態中脫離出來,再也不阻塞。REDIS_UNBLOCKED標誌只能在REDIS_BLOCKED標誌已經打開的狀況下使用;
  • REDIS_MULTI標誌表示客戶端正在執行事務;
  • REDIS_DIRTY_CAS標誌表示事務使用WATCH命令監視的數據庫鍵已經被修改,REDIS_DIRTY_EXEC標誌表示事務在命令入隊時出現了錯誤,以上兩個標誌都表示事務的安全性已經被破壞,只要這兩個標記中的任意一個被打開,EXEC命令必然會執行失敗。這兩個標誌只能在客戶端打開了REDIS_MULTI標誌的狀況下使用;
  • REDIS_CLOSE_ASAP標誌表示客戶端的輸出緩衝區大小超出了服務器容許的範圍,服務器會在下一次執行serverCron函數時關閉這個客戶端,以避免服務器的穩定性受到這個客戶端影響。積存在輸出緩衝區中的全部內容會直接被釋放,不會返回給客戶端;
  • REDIS_CLOSE_AFTER_REPLY標誌表示有用戶對這個客戶端執行了CLIENT KILL命令,或者客戶端發送給服務器的命令請求中包含了錯誤的協議內容。服務器會將客戶端積存在輸出緩衝區中的全部內容發送給客戶端,而後關閉客戶端;
  • REDIS_ASKING標誌表示客戶端向集羣節點(運行在集羣模式下的服務器)發送了ASKING命令;
  • REDIS_FORCE_AOF標誌強制服務器將當前執行的命令寫入到AOF文件裏面,REDIS_FORCE_REPL標誌強制主服務器將當前執行的命令複製給全部從服務器。執行PUBSUB命令會使客戶端打開REDIS_FORCE_AOF標誌,執行SCRIPT LOAD命令會使客戶端打開REDIS_FORCE_AOF標誌和REDIS_FORCE_REPL標誌;
  • 在主從服務器進行命令傳播期間,從服務器須要向主服務器發送REPLICATION ACK命令,在發送這個命令以前,從服務器必須打開主服務器對應的客戶端的REDIS_MASTER_FORCE_REPLY標誌,不然發送操做會被拒絕執行。

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

一般狀況下,Redis只會將那些對數據庫進行了修改的命令寫入到AOF文件,並複製到各個從服務器。若是一個命令沒有對數據庫進行任何修改,那麼它就會被認爲是隻讀命令,這個命令不會被寫入到AOF文件,也不會被複制到從服務器。
以上規則適用於絕大部分Redis命令,但PUBSUB命令和SCRIPT LOAD命令是其中的例外。PUBSUB命令雖然沒有修改數據庫,但PUBSUB命令向頻道的全部訂閱者發送消息這一行爲帶有反作用,接收到消息的全部客戶端的狀態都會由於這個命令而改變。所以,服務器須要使用REDIS_FORCE_AOF標誌,強制將這個命令寫入AOF文件,這樣在未來載入AOF文件時,服務器就能夠再次執行相同的PUBSUB命令,併產生相同的反作用。SCRIPT LOAD命令的狀況與PUBSUB命令相似:雖然SCRIPT LOAD命令沒有修改數據庫,但它修改了服務器狀態,因此它是一個帶有反作用的命令,服務器須要使用REDIS_FORCE_AOF標誌,強制將這個命令寫入AOF文件,使得未來在載入AOF文件時,服務器能夠產生相同的反作用。

另外,爲了讓主服務器和從服務器均可以正確地載入SCRIPT LOAD命令指定的腳本,服務器須要使用REDIS_FORCE_REPL標誌,強制將SCRIPT LOAD命令複製給全部從服務器。

一些flags屬性的例子:

# 
客戶端是一個主服務器
REDIS_MASTER
# 
客戶端正在被列表命令阻塞
REDIS_BLOCKED
# 
客戶端正在執行事務,但事務的安全性已被破壞
REDIS_MULTI | REDIS_DIRTY_CAS
# 
客戶端是一個從服務器,而且版本低於Redis 2.8 
REDIS_SLAVE | REDIS_PRE_PSYNC
# 
這是專門用於執行Lua
腳本包含的Redis
命令的僞客戶端
# 
它強制服務器將當前執行的命令寫入AOF
文件,並複製給從服務器
REDIS_LUA_CLIENT | REDIS_FORCE_AOF| REDIS_FORCE_REPL

 

輸入緩衝區

typedef struct redisClient {
    // ...
    sds querybuf;
    // ...
} redisClient;

舉個例子,若是客戶端向服務器發送瞭如下命令請求:SET key value

那麼客戶端狀態的querybuf屬性將是一個包含如下內容的SDS值:*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n

輸入緩衝區的大小會根據輸入內容動態地縮小或者擴大,但它的最大大小不能超過1GB,不然服務器將關閉這個客戶端。

命令與命令參數

typedef struct redisClient {
    // ...
    robj **argv;
    int argc;
    // ...
} redisClient;

命令的實現函數

 

輸出緩衝區

每一個客戶端都有兩個輸出緩衝區可用,一個緩衝區的大小是固定的,另外一個緩衝區的大小是可變的:

  • 固定大小的緩衝區用於保存那些長度比較小的回覆,好比OK;
  • 可變大小的緩衝區用於保存那些長度比較大的回覆。

客戶端的固定大小緩衝區由buf和bufpos兩個屬性組成:

typedef struct redisClient {
    // ...
    char buf[REDIS_REPLY_CHUNK_BYTES];
    int bufpos;
    // ...
} redisClient;

buf是一個大小爲REDIS_REPLY_CHUNK_BYTES字節的字節數組,而bufpos屬性則記錄了buf數組目前已使用的字節數量。

REDIS_REPLY_CHUNK_BYTES常量目前的默認值爲16*1024,也便是說,buf數組的默認大小爲16KB。

當buf數組的空間已經用完,或者回復由於太大而沒辦法放進buf數組裏面時,服務器就會開始使用可變大小緩衝區。
可變大小緩衝區由reply鏈表和一個或多個字符串對象組成:

typedef struct redisClient {
    // ...
    list *reply;
    // ...
} redisClient;

身份驗證

客戶端狀態的authenticated屬性用於記錄客戶端是否經過了身份驗證:

typedef struct redisClient {
    // ...
    int authenticated;
    // ...
} redisClient;

若是authenticated的值爲0,那麼表示客戶端未經過身份驗證;若是authenticated的值爲1,那麼表示客戶端已經經過了身份驗證。

時間

typedef struct redisClient {
    // ...
    time_t ctime;
    time_t lastinteraction;
    time_t obuf_soft_limit_reached_time;
    // ...
} redisClient;

ctime屬性記錄了建立客戶端的時間,這個時間能夠用來計算客戶端與服務器已經鏈接了多少秒,CLIENT list命令的age域記錄了這個秒數:

redis> CLIENT list
addr=127.0.0.1:53428 ... age=1242 ...

lastinteraction屬性記錄了客戶端與服務器最後一次進行互動(interaction)的時間,這裏的互動能夠是客戶端向服務器發送命令請求,也能夠是服務器向客戶端發送命令回覆;

lastinteraction屬性能夠用來計算客戶端的空轉(idle)時間,也便是,距離客戶端與服務器最後一次進行互動以來,已通過去了多少秒,CLIENT list命令的idle域記錄了這個秒數:

redis> CLIENT list
addr=127.0.0.1:53428 ... idle=12 ...

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

關閉客戶端的各類緣由

  • 若是客戶端進程退出或者被殺死,那麼客戶端與服務器之間的網絡鏈接將被關閉,從而形成客戶端被關閉。
  • 若是客戶端向服務器發送了帶有不符合協議格式的命令請求,那麼這個客戶端也會被服務器關閉。
  • 若是客戶端成爲了CLIENT KILL命令的目標,那麼它也會被關閉。
  • 若是用戶爲服務器設置了timeout配置選項,那麼當客戶端的空轉時間超過timeout選項設置的值時,客戶端將被關閉。不過timeout選項有一些例外狀況:若是客戶端是主服務器(打開了REDIS_MASTER標誌),從服務器(打開了REDIS_SLAVE標誌),正在被BLPOP等命令阻塞(打開了REDIS_BLOCKED標誌),或者正在執行SUBSCRIBE、PSUBSCRIBE等訂閱命令,那麼即便客戶端的空轉時間超過了timeout選項的值,客戶端也不會被服務器關閉。
  • 若是客戶端發送的命令請求的大小超過了輸入緩衝區的限制大小(默認爲1 GB),那麼這個客戶端會被服務器關閉。
  • 若是要發送給客戶端的命令回覆的大小超過了輸出緩衝區的限制大小,那麼這個客戶端會被服務器關閉。

前面介紹輸出緩衝區的時候提到過,可變大小緩衝區由一個鏈表和任意多個字符串對象組成,理論上來講,這個緩衝區能夠保存任意長的命令回覆。可是,爲了不客戶端的回覆過大,佔用過多的服務器資源,服務器會時刻檢查客戶端的輸出緩衝區的大小,並在緩衝區的大小超出範圍時,執行相應的限制操做。

服務器使用兩種模式來限制客戶端輸出緩衝區的大小:

  • 硬性限制(hard limit):若是輸出緩衝區的大小超過了硬性限制所設置的大小,那麼服務器當即關閉客戶端。
  • 軟性限制(soft limit):若是輸出緩衝區的大小超過了軟性限制所設置的大小,但還沒超過硬性限制,那麼服務器將使用客戶端狀態結構的obuf_soft_limit_reached_time屬性記錄下客戶端到達軟性限制的起始時間;以後服務器會繼續監視客戶端,若是輸出緩衝區的大小一直超出軟性限制,而且持續時間超過服務器設定的時長,那麼服務器將關閉客戶端;相反地,若是輸出緩衝區的大小在指定時間以內,再也不超出軟性限制,那麼客戶端就不會被關閉,而且obuf_soft_limit_reached_time屬性的值也會被清零。

使用client-output-buffer-limit選項能夠爲普通客戶端、從服務器客戶端、執行發佈與訂閱功能的客戶端分別設置不一樣的軟性限制和硬性限制,該選項的格式爲:

client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>

如下是三個設置示例:

client-output-buffer-limit normal 0 0 0client-output-buffer-limit slave 256mb 64mb 60client-output-buffer-limit pubsub 32mb 8mb 60

相關文章
相關標籤/搜索