繼續咱們上一節的討論。服務器啓動了,客戶端也發送命令了。接下來,就要到服務器「表演」的時刻了。git
服務器讀取到命令請求後,會進行一系列的處理。github
當客戶端與服務器之間的套接字因客戶端的寫入變得可讀時,服務器將調用命令請求處理器執行如下操做:redis
上面的 SET
命令保存到客戶端狀態的輸入緩存區以後,客戶端狀態如圖 4。數據庫
以後,分析程序將對輸入緩衝區中的協議進行分析,並將得出的結果保存的客戶端的 argv 和 argc 屬性中,如圖 5 所示:緩存
以後,服務器將經過調用命令執行器來完成執行命令的餘下步驟。服務器
命令執行器要作的第一件事就是根據 argv[0] 參數,在命令表(commandtable)中查找參數所指定的命令,並將找到的命令保存到 cmd 屬性中。函數
命令表是一個字典,字典的鍵是一個個命令名稱,好比 "SET"、"GET" 等。而字典的值則是一個個 redisCommand 結構,每一個 redisCommand 結構記錄了 Redis 命令的實現信息。源碼以下:lua
# server.h/redisCommand struct redisCommand { char *name; // 命令名稱。如 "SET" redisCommandProc *proc; // 對應函數指針,指向命令的實現函數。好比 SET 對應的 setCommand 函數 int arity; // 命令參數的格個數。用來檢查命令請求的格式是否合法。 // 要注意的命令的名稱也是一個參數。像咱們上面的 SET KEY VALUE 命令,實際上有三個參數。 char *sflags; // 字符串形式的標識值。記錄了命令的屬性。 int flags; // 對 sflags 標識分析得出的二進制標識,由程序自動生成。檢查命令時,實際上使用的是此字段 redisGetKeysProc *getkeys_proc; // 指針函數,經過此方法來指定 key 的位置。 int firstkey; // 第一個 key 的位置 int lastkey; // 最後一個 key 的位置 int keystep; // key 之間的間距 long long microseconds, calls; // 命令的總調用時間及調用次數 };
另外,對於 sflags 屬性,可以使用的標識值及含義以下表:spa
標識 | 意義 | 帶有此標識的命令 |
---|---|---|
w | 這是一個寫入命令,可能會修改數據庫 | SET、RPUSH、DEL 等 |
r | 這是一個只讀命令,不會修改數據庫 | GET、STRLEN 等 |
m | 此命令可能會佔用大量內存,執行器需先檢查內存使用狀況,若是內存緊缺就禁止執行此命令 | SET、APPEND、RPUSH、SADD 等 |
a | 這是一個管理命令 | SAVE、BGSAVE 等 |
p | 這是一個發佈與訂閱功能的命令 | PUBLISH、SUBSRIBE 等 |
s | 這個命令不能夠在 lua 腳步中使用 | BPOP、BLPOP 等 |
R | 這是一個隨機命令。對於相同的數據集和相同的參數,返回結果可能不一樣 | SPOP、SRANDMEMBER 等 |
S | 當在 lua 腳步中使用此命令時,對返回結果進行排序,使得結果有序 | SINTER、SUNION 等 |
l | 這個命令能夠在服務器載入數據的過程當中使用 | INFO、PUBLISH 等 |
t | 這個命令容許在從庫有過時數據時使用 | SLAVEOF、PING 等 |
M | 這個命令在監視模式下,不會被自動傳播 | EXEC |
k | 集羣模式下,若是對應槽點標記位「導入」,則接受此命令 | restore-asking |
F | 這個命令在程序執行時應該馬上執行 | SETNX、GET 等 |
命令表結構如圖 6:指針
對於咱們上面的 SET KEY VALUE
命令,當程序以圖 5 中的 argv[0] 做爲輸入,在命令表中進行查找時,命令表返回 "set" 鍵對於的 redisCommand 結構,客戶端狀態的 cmd 指針會指向這個 redisCommand 結構。如圖 7 所示:
要注意的是,對於 Redis 而言,命令名字的大小寫不影響命令表的查找結果,也就是命令名稱不區分大小寫。執行 SET 和 set、Set 將得到相同結果。
到目前爲止,服務器已經將執行命令所須要的命令實現函數(客戶端 cmd 屬性)、參數(客戶端 argv 屬性)、參數個數(客戶端 argc 屬性)都初始化完畢。但在真正執行命令以前,程序還會進行一些預備操做,保證命令能夠正確、順利的被執行。預備操做包括:
AUTH
命令。不然,將會向客戶端返回一個錯誤。BGSAVE
命令時出錯,而且服務器打開了 stop-writes-on-bgsave-error 功能,而將要執行的命令是一個寫命令,那麼服務器將拒絕執行這個鞋命令,並向客戶端返回一個錯誤。SUBSCRIBE
和 PSUBSCRIBE
命令訂閱頻道或模式,那麼服務器只會執行客戶端發來的 SUBSCRIBE
、PSUBSCRIBE
、UNSUBSCRIBE
、PUNSUBSCRIBE
四個命令,其它命令都會被拒絕。l
標識才會被服務器執行。EXEC
、DISCARD
、MULTI
、WATCH
四個命令,其餘命令都會被放進事務隊列中。當完成了以上預備操做以後,服務器就開始真正的執行命令了。
要注意的是,上面列出的預備操做只是服務器在單機模式下的檢查操做。若是在複製或者集羣模式下,預備操做還會更多。
在前面的操做中 ,服務器已經將要執行的命令實現、參數、參數個數保存在客戶端結構中。
對於咱們上面的 SET KEY VALUE
命令,圖 8 包含了命令實現、參數和參數個數結構:
當服務器決定要執行命令時,只要執行如下語句便可:
// client 是指向客戶端狀態的指針。server.c/call() client->cmd->proc(client);
上面的執行語句實際上就是調用 setCommand
函數(t_string.c)。
被調用的命令實現函數會執行指定的操做,併產生相應的命令回覆,這些回覆會被保存在客戶端狀態的輸出緩衝區中(bug 屬性 和 reply 屬性),以後實現函數會爲客戶端的套接字關聯命令回覆處理器,由命令回覆處理器返回給客戶端。
回到咱們的示例,setCommand(client)
將產生一個 "+OKrn" 回覆,這個回覆被保存在客戶端的 buf 屬性中。如圖 9 所示:
實現函數執行完後,服務器還會執行一些後續工做,主要包括:
redisCommand
結構的 milliseconds 和 calls 屬性。以上後續操做執行完畢後,一條執行命令也就執行完成了。服務器能夠繼續處理後續的命令。
上面過程當中,命令實現函數會將命令回覆保存到客戶端的輸出緩衝區中,併爲客戶端的套接字關聯命令回覆處理器。當客戶端套接字變爲可寫狀態時,服務器就會執行命令回覆處理器,將命令回覆發送給客戶端。
當命令回覆發送完畢後,回覆處理器會狀況客戶端的輸出緩衝區,爲處理下一個命令請求作好準備。
以圖 9 所示的客戶端狀態爲例,當客戶端的套接字變爲可寫狀態時,命令回覆處理器會將協議格式的命令回覆 "+OKrn" 發送給客戶端。
命令處理請求,函數調用堆棧信息如圖 3-7-1:
命令回覆,函數調用堆棧信息如圖 3-7-2:
客戶端接收到命令回覆以後,會將回復轉換成咱們可讀的格式,並打印在屏幕上(對於 redis-cli 客戶端),如圖 10 所示。
至此,咱們走完了從發起一個命令請求,到收到回覆的全部過程。對於咱們最開始提的問題,服務器如何響應客戶端請求,你有答案了嗎?
networking.c/readQueryFromClient()
讀取和執行對應命令。networking.c/writeToClient()
將命令回覆發送給客戶端。