摘要:讀取命令請求當客戶端與服務器之間的套接字因客戶端的寫入變得可讀時,服務器將調用命令請求處理器執行以下操作讀取套接字中的命令請求,并將其保存到客戶端狀態的輸入緩沖區。
繼續我們上一節的討論。服務器啟動了,客戶端也發送命令了。接下來,就要到服務器“表演”的時刻了。
1 服務器處理服務器讀取到命令請求后,會進行一系列的處理。
1.1 讀取命令請求當客戶端與服務器之間的套接字因客戶端的寫入變得可讀時,服務器將調用命令請求處理器執行以下操作:
讀取套接字中的命令請求,并將其保存到客戶端狀態的輸入緩沖區。
對輸入緩沖區的命令請求進行分析,提取出命令請求中包含的命令參數及參數個數,然后分別將參數和參數個數保存到客戶端狀態的 argv 屬性和 argc 屬性里。
調用命令執行器,執行客戶端指定的命令。
上面的 SET 命令保存到客戶端狀態的輸入緩存區之后,客戶端狀態如圖 4。
之后,分析程序將對輸入緩沖區中的協議進行分析,并將得出的結果保存的客戶端的 argv 和 argc 屬性中,如圖 5 所示:
之后,服務器將通過調用命令執行器來完成執行命令的余下步驟。
1.2 查找命令實現命令執行器要做的第一件事就是根據 argv[0] 參數,在命令表(commandtable)中查找參數所指定的命令,并將找到的命令保存到 cmd 屬性中。
命令表是一個字典,字典的鍵是一個個命令名稱,比如 "SET"、"GET" 等。而字典的值則是一個個 redisCommand 結構,每個 redisCommand 結構記錄了 Redis 命令的實現信息。源碼如下:
# 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 屬性,可使用的標識值及含義如下表:
標識 | 意義 | 帶有此標識的命令 |
---|---|---|
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 將獲得相同結果。
1.3 執行預備操作到目前為止,服務器已經將執行命令所需要的命令實現函數(客戶端 cmd 屬性)、參數(客戶端 argv 屬性)、參數個數(客戶端 argc 屬性)都初始化完畢。但在真正執行命令之前,程序還會進行一些預備操作,保證命令可以正確、順利的被執行。預備操作包括:
檢查客戶端的 cmd 指針是否指向 NULL,如果是的話,說明用戶輸入的命令名稱沒有對應的函數,服務器將不再執行后續操作,并向客戶端返回一個錯誤。
根據客戶端 cmd 屬性指向的 redisCommand 結果的 arity 屬性,檢查命令請求所給定的參數個數是否正確。
檢查客戶端是否已經通過了身份驗證。未通過身份驗證的客戶端只能執行 AUTH 命令。否則,將會向客戶端返回一個錯誤。
如果服務器打開了 maxmemory 功能,在執行命令之前,會先檢查服務器的內存占用情況,并在有需要時進行內存回收,從而使得接下來的命令可以順利執行。如果內存回收失敗,將不再執行后續步驟,向客戶端返回一個錯誤。
如果服務器上一次執行 BGSAVE 命令時出錯,并且服務器打開了 stop-writes-on-bgsave-error 功能,而將要執行的命令是一個寫命令,那么服務器將拒絕執行這個鞋命令,并向客戶端返回一個錯誤。
如果客戶端正在用 SUBSCRIBE 和 PSUBSCRIBE 命令訂閱頻道或模式,那么服務器只會執行客戶端發來的 SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE、PUNSUBSCRIBE 四個命令,其它命令都會被拒絕。
如果服務器正在進行數據載入,那么客戶端發送是命令必須帶有 l 標識才會被服務器執行。
如果客戶端正在執行事務,那么服務器只會執行 EXEC、DISCARD、MULTI、WATCH 四個命令,其他命令都會被放進事務隊列中。
如果服務器打開了監視器功能,那么服務器會將要執行的命令和參數等信息發送給監視器。
當完成了以上預備操作之后,服務器就開始真正的執行命令了。
要注意的是,上面列出的預備操作只是服務器在單機模式下的檢查操作。如果在復制或者集群模式下,預備操作還會更多。
1.4 調用命令的實現函數在前面的操作中 ,服務器已經將要執行的命令實現、參數、參數個數保存在客戶端結構中。
對于我們上面的 SET KEY VALUE 命令,圖 8 包含了命令實現、參數和參數個數結構:
當服務器決定要執行命令時,只要執行以下語句即可:
// client 是指向客戶端狀態的指針。server.c/call() client->cmd->proc(client);
上面的執行語句實際上就是調用 setCommand 函數(t_string.c)。
被調用的命令實現函數會執行指定的操作,并產生相應的命令回復,這些回復會被保存在客戶端狀態的輸出緩沖區中(bug 屬性 和 reply 屬性),之后實現函數會為客戶端的套接字關聯命令回復處理器,由命令回復處理器返回給客戶端。
回到我們的示例,setCommand(client) 將產生一個 "+OKrn" 回復,這個回復被保存在客戶端的 buf 屬性中。如圖 9 所示:
1.5 執行后續工作實現函數執行完后,服務器還會執行一些后續工作,主要包括:
如果服務器開啟了 slow-log 功能,那么慢查詢日志模塊將會檢查是否需要將剛執行的命令添加到慢查詢日志。
更新 redisCommand 結構的 milliseconds 和 calls 屬性。
如果服務器開啟了 AOF 持久化功能,那么 AOF 持久化模塊會將剛剛執行的命令請求寫入到 AOF 緩沖區中。
如果有其它服務器正在復制當前這個服務器,那么服務器將會把剛剛執行的命令傳播給所有從服務器。
以上后續操作執行完畢后,一條執行命令也就執行完成了。服務器可以繼續處理后續的命令。
1.6 將命令回復發送給客戶端上面過程中,命令實現函數會將命令回復保存到客戶端的輸出緩沖區中,并為客戶端的套接字關聯命令回復處理器。當客戶端套接字變為可寫狀態時,服務器就會執行命令回復處理器,將命令回復發送給客戶端。
當命令回復發送完畢后,回復處理器會情況客戶端的輸出緩沖區,為處理下一個命令請求做好準備。
以圖 9 所示的客戶端狀態為例,當客戶端的套接字變為可寫狀態時,命令回復處理器會將協議格式的命令回復 "+OKrn" 發送給客戶端。
1.7 源碼解讀命令處理請求,函數調用堆棧信息如圖 3-7-1:
命令回復,函數調用堆棧信息如圖 3-7-2:
客戶端接收到命令回復之后,會將回復轉換成我們可讀的格式,并打印在屏幕上(對于 redis-cli 客戶端),如圖 10 所示。
至此,我們走完了從發起一個命令請求,到收到回復的所有過程。對于我們最開始提的問題,服務器如何響應客戶端請求,你有答案了嗎?
總結服務器通過 networking.c/readQueryFromClient() 讀取和執行對應命令。
服務器通過 networking.c/writeToClient() 將命令回復發送給客戶端。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/62115.html
摘要:現在客戶端和服務器都準備好了,那么客戶端和服務器如何建立連接服務器又是如何響應客戶端的請求呢連接服務器客戶端和服務器進行通訊,首先應該就是建立連接。以上是客戶端發送命令給服務器的過程,在下一節中,我們再來認識服務器是如何響應客戶端請的。 上次我們通過問題啟動服務器,程序都干了什么?,跟著源碼,深入了解了 Redis 服務器的啟動過程。 既然啟動了 Redis 服務器,那我們就要連上 R...
摘要:此時服務器處于休眠狀態,并使用進行事件輪詢,等待監聽事件的發生。繼續執行被調試程序,直至下一個斷點或程序結束縮寫。服務啟動包括初始化基礎配置數據結構對外提供服務的準備工作還原數據庫執行事件循環等。 一直很羨慕那些能讀 Redis 源碼的童鞋,也一直想自己解讀一遍,但迫于 C 大魔王的壓力,解讀日期遙遙無期。 相信很多小伙伴應該也都對或曾對源碼感興趣,但一來覺得自己不會 C 語言,二來也...
閱讀 1729·2021-11-11 10:58
閱讀 4222·2021-09-09 09:33
閱讀 1269·2021-08-18 10:23
閱讀 1558·2019-08-30 15:52
閱讀 1636·2019-08-30 11:06
閱讀 1879·2019-08-29 14:03
閱讀 1518·2019-08-26 14:06
閱讀 2971·2019-08-26 10:39