一個(gè)命令請(qǐng)求從發(fā)送到獲得回復(fù)的過(guò)程中, 客戶端和服務(wù)器需要完成一系列操作。
舉個(gè)例子, 如果我們使用客戶端執(zhí)行以下命令:
redis> SET KEY VALUE
OK
那么從客戶端發(fā)送 SET KEY VALUE
命令到獲得回復(fù) OK
期間, 客戶端和服務(wù)器共需要執(zhí)行以下操作:
SET KEY VALUE
。SET KEY VALUE
, 在數(shù)據(jù)庫(kù)中進(jìn)行設(shè)置操作, 并產(chǎn)生命令回復(fù) OK
。OK
發(fā)送給客戶端。OK
, 并將這個(gè)回復(fù)打印給用戶觀看。本節(jié)接下來(lái)的內(nèi)容將對(duì)這些操作的執(zhí)行細(xì)節(jié)進(jìn)行補(bǔ)充, 詳細(xì)地說(shuō)明客戶端和服務(wù)器在執(zhí)行命令請(qǐng)求時(shí)所做的各種工作。
Redis 服務(wù)器的命令請(qǐng)求來(lái)自 Redis 客戶端, 當(dāng)用戶在客戶端中鍵入一個(gè)命令請(qǐng)求時(shí), 客戶端會(huì)將這個(gè)命令請(qǐng)求轉(zhuǎn)換成協(xié)議格式, 然后通過(guò)連接到服務(wù)器的套接字, 將協(xié)議格式的命令請(qǐng)求發(fā)送給服務(wù)器, 如圖 14-1 所示。
舉個(gè)例子, 假設(shè)客戶端執(zhí)行命令:
SET KEY VALUE
那么客戶端會(huì)將這個(gè)命令轉(zhuǎn)換成協(xié)議:
*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n
然后將這段協(xié)議內(nèi)容發(fā)送給服務(wù)器。
當(dāng)客戶端與服務(wù)器之間的連接套接字因?yàn)榭蛻舳说膶懭攵兊每勺x時(shí), 服務(wù)器將調(diào)用命令請(qǐng)求處理器來(lái)執(zhí)行以下操作:
argv
屬性和 argc
屬性里面。繼續(xù)用上一個(gè)小節(jié)的 SET 命令為例子, 圖 14-2 展示了程序?qū)⒚钫?qǐng)求保存到客戶端狀態(tài)的輸入緩沖區(qū)之后, 客戶端狀態(tài)的樣子。
之后, 分析程序?qū)?duì)輸入緩沖區(qū)中的協(xié)議:
*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n
進(jìn)行分析, 并將得出的分析結(jié)果保存到客戶端狀態(tài)的 argv
屬性和 argc
屬性里面, 如圖 14-3 所示。
之后, 服務(wù)器將通過(guò)調(diào)用命令執(zhí)行器來(lái)完成執(zhí)行命令所需的余下步驟, 以下幾個(gè)小節(jié)將分別介紹命令執(zhí)行器所執(zhí)行的工作。
命令執(zhí)行器要做的第一件事就是根據(jù)客戶端狀態(tài)的 argv[0]
參數(shù), 在命令表(command table)中查找參數(shù)所指定的命令, 并將找到的命令保存到客戶端狀態(tài)的 cmd
屬性里面。
命令表是一個(gè)字典, 字典的鍵是一個(gè)個(gè)命令名字,比如 "set"
、 "get"
、 "del"
,等等; 而字典的值則是一個(gè)個(gè) redisCommand
結(jié)構(gòu), 每個(gè)redisCommand
結(jié)構(gòu)記錄了一個(gè) Redis 命令的實(shí)現(xiàn)信息, 表 14-1 記錄了這個(gè)結(jié)構(gòu)的各個(gè)主要屬性的類型和作用。
表 14-1 redisCommand
結(jié)構(gòu)的主要屬性
屬性名 | 類型 | 作用 |
---|---|---|
name |
char * |
命令的名字,比如 "set" 。 |
proc |
redisCommandProc * |
函數(shù)指針,指向命令的實(shí)現(xiàn)函數(shù),比如 setCommand 。 redisCommandProc 類型的定義為typedef void redisCommandProc(redisClient *c); 。 |
arity |
int |
命令參數(shù)的個(gè)數(shù),用于檢查命令請(qǐng)求的格式是否正確。 如果這個(gè)值為負(fù)數(shù) -N ,那么表示參數(shù)的數(shù)量大于等于 N 。 注意命令的名字本身也是一個(gè)參數(shù), 比如說(shuō) SET msg "helloworld" 命令的參數(shù)是 "SET" 、 "msg" 、 "hello world" , 而不僅僅是 "msg" 和 "helloworld" 。 |
sflags |
char * |
字符串形式的標(biāo)識(shí)值, 這個(gè)值記錄了命令的屬性, 比如這個(gè)命令是寫命令還是讀命令, 這個(gè)命令是否允許在載入數(shù)據(jù)時(shí)使用, 這個(gè)命令是否允許在 Lua 腳本中使用, 等等。 |
flags |
int |
對(duì) sflags 標(biāo)識(shí)進(jìn)行分析得出的二進(jìn)制標(biāo)識(shí), 由程序自動(dòng)生成。 服務(wù)器對(duì)命令標(biāo)識(shí)進(jìn)行檢查時(shí)使用的都是 flags 屬性而不是 sflags 屬性, 因?yàn)閷?duì)二進(jìn)制標(biāo)識(shí)的檢查可以方便地通過(guò) & 、 ^ 、 ~ 等操作來(lái)完成。 |
calls |
long long |
服務(wù)器總共執(zhí)行了多少次這個(gè)命令。 |
milliseconds |
long long |
服務(wù)器執(zhí)行這個(gè)命令所耗費(fèi)的總時(shí)長(zhǎng)。 |
表 14-2 列出了 sflags
屬性可以使用的標(biāo)識(shí)值, 以及這些標(biāo)識(shí)的意義。
表 14-2 sflags
屬性的標(biāo)識(shí)
標(biāo)識(shí) | 意義 | 帶有這個(gè)標(biāo)識(shí)的命令 |
---|---|---|
w |
這是一個(gè)寫入命令,可能會(huì)修改數(shù)據(jù)庫(kù)。 | SET 、 RPUSH 、 DEL ,等等。 |
r |
這是一個(gè)只讀命令,不會(huì)修改數(shù)據(jù)庫(kù)。 | GET 、 STRLEN 、 EXISTS ,等等。 |
m |
這個(gè)命令可能會(huì)占用大量?jī)?nèi)存, 執(zhí)行之前需要先檢查服務(wù)器的內(nèi)存使用情況, 如果內(nèi)存緊缺的話就禁止執(zhí)行這個(gè)命令。 | SET 、 APPEND 、 RPUSH 、 LPUSH 、 SADD 、SINTERSTORE ,等等。 |
a |
這是一個(gè)管理命令。 | SAVE 、 BGSAVE 、 SHUTDOWN ,等等。 |
p |
這是一個(gè)發(fā)布與訂閱功能方面的命令。 | PUBLISH 、 SUBSCRIBE 、 PUBSUB ,等等。 |
s |
這個(gè)命令不可以在 Lua 腳本中使用。 | BRPOP 、 BLPOP 、 BRPOPLPUSH 、 SPOP ,等等。 |
R |
這是一個(gè)隨機(jī)命令, 對(duì)于相同的數(shù)據(jù)集和相同的參數(shù), 命令返回的結(jié)果可能不同。 | SPOP 、 SRANDMEMBER 、 SSCAN 、 RANDOMKEY ,等等。 |
S |
當(dāng)在 Lua 腳本中使用這個(gè)命令時(shí), 對(duì)這個(gè)命令的輸出結(jié)果進(jìn)行一次排序, 使得命令的結(jié)果有序。 | SINTER 、 SUNION 、 SDIFF 、 SMEMBERS 、KEYS ,等等。 |
l |
這個(gè)命令可以在服務(wù)器載入數(shù)據(jù)的過(guò)程中使用。 | INFO 、 SHUTDOWN 、 PUBLISH ,等等。 |
t |
這是一個(gè)允許從服務(wù)器在帶有過(guò)期數(shù)據(jù)時(shí)使用的命令。 | SLAVEOF 、 PING 、 INFO ,等等。 |
M |
這個(gè)命令在監(jiān)視器(monitor)模式下不會(huì)自動(dòng)被傳播(propagate)。 | EXEC |
圖 14-4 展示了命令表的樣子, 并且以 SET 命令和 GET 命令作為例子, 展示了 redisCommand
結(jié)構(gòu):
"set"
, 實(shí)現(xiàn)函數(shù)為 setCommand
; 命令的參數(shù)個(gè)數(shù)為 -3
, 表示命令接受三個(gè)或以上數(shù)量的參數(shù); 命令的標(biāo)識(shí)為"wm"
, 表示 SET 命令是一個(gè)寫入命令, 并且在執(zhí)行這個(gè)命令之前, 服務(wù)器應(yīng)該對(duì)占用內(nèi)存狀況進(jìn)行檢查, 因?yàn)檫@個(gè)命令可能會(huì)占用大量?jī)?nèi)存。"get"
, 實(shí)現(xiàn)函數(shù)為 getCommand
函數(shù); 命令的參數(shù)個(gè)數(shù)為 2
, 表示命令只接受兩個(gè)參數(shù); 命令的標(biāo)識(shí)為 "r"
, 表示這是一個(gè)只讀命令。繼續(xù)之前 SET 命令的例子, 當(dāng)程序以圖 14-3 中的 argv[0]
作為輸入, 在命令表中進(jìn)行查找時(shí), 命令表將返回 "set"
鍵所對(duì)應(yīng)的redisCommand
結(jié)構(gòu), 客戶端狀態(tài)的 cmd
指針會(huì)指向這個(gè) redisCommand
結(jié)構(gòu), 如圖 14-5 所示。
命令名字的大小寫不影響命令表的查找結(jié)果
因?yàn)槊畋硎褂玫氖谴笮憻o(wú)關(guān)的查找算法, 無(wú)論輸入的命令名字是大寫、小寫或者混合大小寫, 只要命令的名字是正確的, 就能找到相應(yīng)的 redisCommand
結(jié)構(gòu)。
比如說(shuō), 無(wú)論用戶輸入的命令名字是 "SET"
、 "set"
、 "SeT"
又或者 "sEt"
, 命令表返回的都是同一個(gè) redisCommand
結(jié)構(gòu)。
這也是 Redis 客戶端可以發(fā)送不同大小寫的命令, 并且獲得相同執(zhí)行結(jié)果的原因:
# 以下四個(gè)命令的執(zhí)行效果完全一樣
redis> SET msg "hello world"
OK
redis> set msg "hello world"
OK
redis> SeT msg "hello world"
OK
redis> sEt msg "hello world"
OK
到目前為止, 服務(wù)器已經(jīng)將執(zhí)行命令所需的命令實(shí)現(xiàn)函數(shù)(保存在客戶端狀態(tài)的 cmd
屬性)、參數(shù)(保存在客戶端狀態(tài)的 argv
屬性)、參數(shù)個(gè)數(shù)(保存在客戶端狀態(tài)的 argc
屬性)都收集齊了, 但是在真正執(zhí)行命令之前, 程序還需要進(jìn)行一些預(yù)備操作, 從而確保命令可以正確、順利地被執(zhí)行, 這些操作包括:
cmd
指針是否指向 NULL
, 如果是的話, 那么說(shuō)明用戶輸入的命令名字找不到相應(yīng)的命令實(shí)現(xiàn), 服務(wù)器不再執(zhí)行后續(xù)步驟, 并向客戶端返回一個(gè)錯(cuò)誤。cmd
屬性指向的 redisCommand
結(jié)構(gòu)的 arity
屬性, 檢查命令請(qǐng)求所給定的參數(shù)個(gè)數(shù)是否正確, 當(dāng)參數(shù)個(gè)數(shù)不正確時(shí), 不再執(zhí)行后續(xù)步驟, 直接向客戶端返回一個(gè)錯(cuò)誤。 比如說(shuō), 如果 redisCommand
結(jié)構(gòu)的 arity
屬性的值為 -3
, 那么用戶輸入的命令參數(shù)個(gè)數(shù)必須大于等于 3
個(gè)才行。maxmemory
功能, 那么在執(zhí)行命令之前, 先檢查服務(wù)器的內(nèi)存占用情況, 并在有需要時(shí)進(jìn)行內(nèi)存回收, 從而使得接下來(lái)的命令可以順利執(zhí)行。 如果內(nèi)存回收失敗, 那么不再執(zhí)行后續(xù)步驟, 向客戶端返回一個(gè)錯(cuò)誤。stop-writes-on-bgsave-error
功能, 而且服務(wù)器即將要執(zhí)行的命令是一個(gè)寫命令, 那么服務(wù)器將拒絕執(zhí)行這個(gè)命令, 并向客戶端返回一個(gè)錯(cuò)誤。l
標(biāo)識(shí)(比如 INFO 、 SHUTDOWN 、 PUBLISH ,等等)才會(huì)被服務(wù)器執(zhí)行, 其他別的命令都會(huì)被服務(wù)器拒絕。當(dāng)完成了以上預(yù)備操作之后, 服務(wù)器就可以開(kāi)始真正執(zhí)行命令了。
注意
以上只列出了服務(wù)器在單機(jī)模式下執(zhí)行命令時(shí)的檢查操作, 當(dāng)服務(wù)器在復(fù)制或者集群模式下執(zhí)行命令時(shí), 預(yù)備操作還會(huì)更多一些。
在前面的操作中, 服務(wù)器已經(jīng)將要執(zhí)行命令的實(shí)現(xiàn)保存到了客戶端狀態(tài)的 cmd
屬性里面, 并將命令的參數(shù)和參數(shù)個(gè)數(shù)分別保存到了客戶端狀態(tài)的 argv
屬性和 argc
屬性里面, 當(dāng)服務(wù)器決定要執(zhí)行命令時(shí), 它只要執(zhí)行以下語(yǔ)句就可以了:
// client 是指向客戶端狀態(tài)的指針
client->cmd->proc(client);
因?yàn)閳?zhí)行命令所需的實(shí)際參數(shù)都已經(jīng)保存到客戶端狀態(tài)的 argv
屬性里面了, 所以命令的實(shí)現(xiàn)函數(shù)只需要一個(gè)指向客戶端狀態(tài)的指針作為參數(shù)即可。
繼續(xù)以之前的 SET 命令為例子, 圖 14-6 展示了客戶端包含了命令實(shí)現(xiàn)、參數(shù)和參數(shù)個(gè)數(shù)的樣子。
對(duì)于這個(gè)例子來(lái)說(shuō), 執(zhí)行語(yǔ)句:
client->cmd->proc(client);
等于執(zhí)行語(yǔ)句:
setCommand(client);
被調(diào)用的命令實(shí)現(xiàn)函數(shù)會(huì)執(zhí)行指定的操作, 并產(chǎn)生相應(yīng)的命令回復(fù), 這些回復(fù)會(huì)被保存在客戶端狀態(tài)的輸出緩沖區(qū)里面(buf
屬性和 reply
屬性), 之后實(shí)現(xiàn)函數(shù)還會(huì)為客戶端的套接字關(guān)聯(lián)命令回復(fù)處理器, 這個(gè)處理器負(fù)責(zé)將命令回復(fù)返回給客戶端。
對(duì)于前面 SET 命令的例子來(lái)說(shuō), 函數(shù)調(diào)用 setCommand(client);
將產(chǎn)生一個(gè) "+OK\r\n"
回復(fù), 這個(gè)回復(fù)會(huì)被保存到客戶端狀態(tài)的 buf
屬性里面, 如圖 14-7 所示。
在執(zhí)行完實(shí)現(xiàn)函數(shù)之后, 服務(wù)器還需要執(zhí)行一些后續(xù)工作:
redisCommand
結(jié)構(gòu)的 milliseconds
屬性, 并將命令的 redisCommand
結(jié)構(gòu)的 calls
計(jì)數(shù)器的值增一。當(dāng)以上操作都執(zhí)行完了之后, 服務(wù)器對(duì)于當(dāng)前命令的執(zhí)行到此就告一段落了, 之后服務(wù)器就可以繼續(xù)從文件事件處理器中取出并處理下一個(gè)命令請(qǐng)求了。
前面說(shuō)過(guò), 命令實(shí)現(xiàn)函數(shù)會(huì)將命令回復(fù)保存到客戶端的輸出緩沖區(qū)里面, 并為客戶端的套接字關(guān)聯(lián)命令回復(fù)處理器, 當(dāng)客戶端套接字變?yōu)榭蓪憼顟B(tài)時(shí), 服務(wù)器就會(huì)執(zhí)行命令回復(fù)處理器, 將保存在客戶端輸出緩沖區(qū)中的命令回復(fù)發(fā)送給客戶端。
當(dāng)命令回復(fù)發(fā)送完畢之后, 回復(fù)處理器會(huì)清空客戶端狀態(tài)的輸出緩沖區(qū), 為處理下一個(gè)命令請(qǐng)求做好準(zhǔn)備。
以圖 14-7 所示的客戶端狀態(tài)為例子, 當(dāng)客戶端的套接字變?yōu)榭蓪憼顟B(tài)時(shí), 命令回復(fù)處理器會(huì)將協(xié)議格式的命令回復(fù) "+OK\r\n"
發(fā)送給客戶端。
當(dāng)客戶端接收到協(xié)議格式的命令回復(fù)之后, 它會(huì)將這些回復(fù)轉(zhuǎn)換成人類可讀的格式, 并打印給用戶觀看(假設(shè)我們使用的是 Redis 自帶的redis-cli
客戶端), 如圖 14-8 所示。
繼續(xù)以之前的 SET 命令為例子, 當(dāng)客戶端接到服務(wù)器發(fā)來(lái)的 "+OK\r\n"
協(xié)議回復(fù)時(shí), 它會(huì)將這個(gè)回復(fù)轉(zhuǎn)換成 "OK\n"
, 然后打印給用戶看:
redis> SET KEY VALUE
OK
以上就是 Redis 客戶端和服務(wù)器執(zhí)行命令請(qǐng)求的整個(gè)過(guò)程了。
更多建議: