Redis
服務器是典型的一對多服務器程序:一個服務器可以與多個客戶端建立網(wǎng)絡連接,每個客戶端可以向服務器發(fā)送命令請求父阻,而服務器則接受并處理客戶端發(fā)送的命令請求,并向客戶端返回命令回復。
通過使用由I/O
多路復用技術實現(xiàn)的文件事件處理器扯再,Redis
服務器使用單線程單進程的方式來處理命令請求离熏,并與多個客戶端進行網(wǎng)絡通信佳谦。
對于每個與服務器進行連接的客戶端,服務器都為這些客戶端建立了相應的redis.h/redisClient
結構(客戶端狀態(tài))滋戳,這個結構保存了客戶端當前的狀態(tài)信息钻蔑,以及執(zhí)行相關功能時需要用到的數(shù)據(jù)結構,其中包括
- 客戶端的套接字描述符
- 客戶端的名字
- 客戶端的標志值
- 指向客戶端正在使用的數(shù)據(jù)庫的指針奸鸯,以及該數(shù)據(jù)庫的號碼
- 客戶端當前要執(zhí)行的命令咪笑、命令的參數(shù)、命令參數(shù)的個數(shù)娄涩,以及指向命令實現(xiàn)函數(shù)的指針窗怒。
- 客戶端的輸入緩沖區(qū)和輸出緩沖區(qū)。
- 客戶端的復制狀態(tài)信息蓄拣,以及進行復制所需要的數(shù)據(jù)結構扬虚。
- 客戶端執(zhí)行
BRPOP
、BLPOP
等列表命令時使用的數(shù)據(jù)結構球恤。 - 客戶端的事務狀態(tài)辜昵,以及執(zhí)行
WATCH
命令時用到的數(shù)據(jù)結構。 - 客戶端執(zhí)行發(fā)布與訂閱功能時用到數(shù)據(jù)結構咽斧。
- 客戶端的身份驗證標志堪置。
- 客戶端的創(chuàng)建時間贷洲,客戶端和服務器最后一次通信時間,以及客戶端的輸出緩沖區(qū)大小超出軟性限制(
soft limit
)的時間
Redis
服務器狀態(tài)結構clients
屬性是一個鏈表晋柱,這個鏈表保存了所有域服務器連接的客戶端的狀態(tài)結構优构,對客戶端執(zhí)行批量操作,或者查找某個指定的客戶端雁竞,都可以通過遍歷clients
鏈表來完成钦椭。
13.1 客戶端屬性
客戶端狀態(tài)包含的屬性可以分為兩類
- 一類是比較通用的屬性,這些屬性很少與特定功能相關碑诉,無論客戶端執(zhí)行的是什么工作彪腔,他們都要用到這些屬性。
- 另外一類是和特定功能相關的屬性进栽,比如操作數(shù)據(jù)庫時需要用到的
db
屬性和dictid
屬性德挣,執(zhí)行事務時需要用到的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)絡唠帝,所以這種客戶端不需要套接字連接屯掖,自然也不需要記錄套接字描述符。目前Redis
服務器會在兩個地方用到為客戶端襟衰,一個用于載入AOF
文件并還原數(shù)據(jù)庫狀態(tài)贴铜,而另一個則用于執(zhí)行Lua
腳本中包含的Redis
命令。 - 普通客戶端的
fd
屬性的值為大于-1
的整數(shù):普通客戶端使用套接字來與服務器進行通信瀑晒,所以服務器會用fd
屬性來記錄客戶端套接字的描述符绍坝。因為合法的套接字描述符不能是-1
,所以普通客戶端的套接字描述符的值必然是大于-1
的整數(shù)苔悦。
13.1.2 名字
在默認情況下轩褐,一個連接到服務器的客戶端是沒有名字的。
使用CLIENT setname
命令可以為客戶端設置一個名字间坐,然給客戶端的身份變得更為清晰灾挨。
客戶端的名字記錄在客戶端狀態(tài)的name
屬性里面:
typedef struct redisClient{
// ...
robj *name;
// ...
}redisClient;
如果客戶端沒有為自己設置名字,那么響應客戶端狀態(tài)的nam
e屬性指向NULL
指針竹宋;相反的劳澄,如果客戶端為自己設置了名字,那么name
屬性將指向一個字符串對象蜈七,而該對象就保存著客戶端的名字秒拔。
13.1.3 標志
客戶端的標志屬性flags
記錄了客戶端的角色(role
),以及客戶端目前所處的狀態(tài):
typedef struct redisClient{
// ...
int flags;
// ...
}redisClient;
flags屬性的值可以是單個標志:
flags=<flag>
也可以是多個標志的二進制或
flags=<flag1>|<flag2>|...
13.1.4 輸入緩存區(qū)
客戶端狀態(tài)的輸入緩沖區(qū)用于保存客戶端發(fā)送的命令請求:
typedef struct redisClient{
// ...
sds querybuf;
// ...
}redisClient;
輸入緩沖區(qū)的大小會根據(jù)輸入內容動態(tài)地縮小或者擴大飒硅,但它的最大大小不能超過1GB
砂缩,否者服務器將關閉這個客戶端作谚。
13.1.5 命令與命令參數(shù)
在服務器將客戶端發(fā)送吃的命令請求保存到客戶端狀態(tài)querybuf
屬性之后,服務器將對命令請求的內容進行分析庵芭,并將得出的命令參數(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.1.6 命令的實現(xiàn)函數(shù)
當服務器從協(xié)議內容中分析并得出argv
屬性和argc
屬性的值之后好乐,服務器將根據(jù)項argv[0]
的值匾竿,在命令表中查找命令所對應的命令實現(xiàn)函數(shù)。
當程序在命令中成功找到argv[0]
所對應的redisCommand
結構時蔚万,它會將客戶端狀態(tài)的cmd
指向這個結構:
typedef struct redisClient{
// ...
struct redisCommand *cmd;
// ...
}redisClient;
之后岭妖,服務器就可以使用cmd
屬性所指向的redisCommand
結構,以及argv
反璃,argc
屬性中所保存的命令參數(shù)信息昵慌,調用命令實現(xiàn)函數(shù),執(zhí)行客戶端指定的命令版扩。
13.1.7 輸出緩沖區(qū)
執(zhí)行命令所得到命令回復會被保存在客戶端狀態(tài)的輸出緩沖區(qū)里面废离,每個客戶端都有兩個輸出緩沖區(qū)可用侄泽,一個緩沖區(qū)的大小是規(guī)定的礁芦,另一個緩沖區(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
司草,為16KB
當buf
數(shù)組的空間已經(jīng)用完,或者回復太大沒有辦法放進buf
數(shù)組里面時泡仗,服務器就會開始使用可變大小緩沖區(qū)埋虹。
可變大小緩沖區(qū)由reply
鏈表和一個或多個字符串對象組成:
typedef struct redisClient{
// ...
list *reply;
// ...
}redisClient;
通過使用鏈表來連接多個字符串對象,服務器可以為客戶端保存一個非常長的命令回復娩怎。
13.1.8 身份驗證
客戶端狀態(tài)的authenticated
屬性用于記錄客戶端是否通過了身份驗證:
typedef struct redisClient{
// ...
int authenticated;
// ...
}redisClient;
如果authentication
的值為0
搔课,那么表示客戶端未通過身份驗證;如果authentication
的值為1
截亦,那么小時客戶端已經(jīng)通過了身份驗證爬泥。
當客戶端authentication
屬性的值為0時柬讨,除了AUTH
命令之外,客戶端發(fā)送的所有其他命令都會被服務器拒絕執(zhí)行袍啡。
當客戶端通過AUTH
命令成功進行身份驗證之后踩官,客戶端狀態(tài)authentication
屬性的值就會從0
變成1
如孝,可以正常發(fā)送命令請求
authentication
屬性僅在服務器啟用了身份驗證功能時使用赞季。如果服務器沒有啟用身份驗證功能的話剂跟,那么authentication
屬性的值為0
哀军,服務器也不會拒絕執(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)建客戶端的時間熏挎,這個時間可以用來計算客戶端與服務器已經(jīng)連接了多少秒
lastinteraction
屬性記錄了客戶端與服務器最后一次進行互動的時間骚腥。
obuf_soft_limit_reached_time
屬性記錄輸出緩存區(qū)第一次到達軟限制的時間懊悯。
13.2 客戶端的創(chuàng)建與關閉
服務器使用不同的方式來創(chuàng)建和關閉不同類型的客戶端窗悯。
13.2.1 創(chuàng)建普通客戶端
如果客戶端通過網(wǎng)絡連接與服務器進行連接的普通用戶客戶端区匣,那么在客戶端使用connect
函數(shù)連接到服務器時,服務器就會調用連接事件處理器蒋院,為客戶端創(chuàng)建相應的客戶端狀態(tài)亏钩,并將這個新的客戶端狀態(tài)添加到服務器狀態(tài)結構clients
鏈表的末尾。
13.2.2 關閉普通客戶端
一個普通客戶端可以因為多種原因而被關閉:
- 客戶端進程退出或者被殺死
- 客戶端發(fā)送不符合協(xié)議格式的命令請求
- 客戶端成為了
CLIENT KILL
命令的目標 - 設置了
timeout
配置選項欺旧,同時客戶端的空轉時間超過這個值姑丑。 - 客戶端輸入緩沖區(qū)超過限制大小
- 客戶端輸出緩沖區(qū)超過限制大小
服務器使用兩種模式來限制客戶端輸出緩沖區(qū)大小:
- 硬性限制:超過硬性限制辞友,立即關閉
- 軟性限制:超過軟性限制栅哀,沒有超過硬性限制。使用
obuf_soft_limit_reached_time
記錄時間称龙,如果一直超過限制留拾,并且持續(xù)時間超過服務器設定的市場,那么服務器關閉客戶端鲫尊;相反痴柔,在規(guī)定的時間內,沒有超過軟性限制疫向,那么客戶端不會被關閉咳蔚,同時obuf_soft_limit_reached_time
會被清零。
使用client-output-buffer-limit
選項可以為普通客戶端搔驼、從服務器客戶端谈火。執(zhí)行發(fā)布與訂閱功能的客戶端分別設置不同的軟性限制和硬性限制
client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
13.2.3 Lua腳本的偽客戶端
服務器會在初始化時創(chuàng)建負責執(zhí)行Lua
腳本中包含的Redis
命令的偽客戶端,并將這個偽客戶端關聯(lián)在服務器狀態(tài)結構lua_client
屬性中:
struct redisServer{
// ...
redisClient *lua_client;
// ...
};
lua_client
偽客戶端在服務器運行的整個生命期中會一直存在匙奴,只有服務器被關閉時堆巧,這個客戶端才會被關閉。
13.2.4 AOF文件的偽客戶端
服務器在載入AOF
文件時,會創(chuàng)建用于執(zhí)行AOF
文件包含的Redis
命令的偽客戶端谍肤,并在載入完成之后啦租,關閉這個偽客戶端。