Redis服務器是典型的一對多服務器程序:一個服務器能夠與多個客戶端創建網絡鏈接,每一個客戶端能夠向服務器發送命令請求,而服務器則接收並處理客戶端發送的命令請求,並向客戶端返回命令回覆。redis
對於每一個與服務器進行鏈接的客戶端,服務器都爲這些客戶端創建了相應的redis.h/redisClient結構(客戶端狀態),這個結構保存了客戶端當前的狀態信息,以及執行相關功能時須要用到的數據結構,其中包括:數據庫
Redis服務器狀態結構的clients屬性是一個鏈表,這個鏈表保存了全部與服務器鏈接的客戶端的狀態結構,對客戶端執行批量操做,或者查找某個指定的客戶端,均可以經過遍歷clients鏈表來完成:數組
struct redisServer { // ... // 一個鏈表,保存了全部客戶端狀態 list *clients; // ... };
客戶端狀態的fd屬性記錄了客戶端正在使用的套接字描述符:安全
typedef struct redisClient { // ... int fd; // ... } redisClient;
根據客戶端類型的不一樣,fd屬性的值能夠是-1或者是大於-1的整數:服務器
執行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> | ...
每一個標誌使用一個常量表示,一部分標誌記錄了客戶端的角色:
另一部分標誌則記錄了客戶端目前所處的狀態:
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;
命令的實現函數
輸出緩衝區
每一個客戶端都有兩個輸出緩衝區可用,一個緩衝區的大小是固定的,另外一個緩衝區的大小是可變的:
客戶端的固定大小緩衝區由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-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