你知道 Redis 服務(wù)器接收到一條命令是如何執(zhí)行的嗎?
本文轉(zhuǎn)載自微信公眾號「Java極客技術(shù)」,作者鴨血粉絲。轉(zhuǎn)載本文請聯(lián)系Java極客技術(shù)公眾號。
Hello 大家好,我是阿粉,Redis 作為工作中不可缺少的緩存組件,相信很多小伙伴都會使用到,我們?nèi)粘J褂玫臅r候都是通過代碼或者客戶端去鏈接 Redis 服務(wù)器來操作數(shù)據(jù)的。那么一條簡單的set name ziyou 命令是如何執(zhí)行的,中間都經(jīng)歷了哪些過程想必很少會有人去了解。今天阿粉就帶大家看一下一條簡單的set name ziyou 命令是如何執(zhí)行的。
我們可以看到在執(zhí)行set name ziyou 這個命令過后,先顯示一個OK 在終端里面。下面我們看下這整個過程都經(jīng)歷了哪些步驟。
命令的整個執(zhí)行分為下面幾個步驟,我們先看流程,在仔細分析:
- 客戶端發(fā)送命令請求;
- 服務(wù)端讀取命令請求;
- 命令執(zhí)行器進行操作
- 命令執(zhí)行器查找命令實現(xiàn)函數(shù);
- 命令執(zhí)行器執(zhí)行預(yù)備操作;
- 命令執(zhí)行器調(diào)用命令的實現(xiàn)函數(shù);
- 命令執(zhí)行器執(zhí)行后續(xù)工作;
- 服務(wù)端將命令回復(fù)發(fā)送給客戶端;
- 客戶端接收并打印命令回復(fù)內(nèi)容;
客戶端發(fā)送命令請求
首先當(dāng)客戶端和服務(wù)端建立好了鏈接過后,當(dāng)我們輸入命令 set name ziyou 命令請求的時候,客戶端會將這個命令進行協(xié)議轉(zhuǎn)換,然后通過連接將轉(zhuǎn)換后的協(xié)議發(fā)送到服務(wù)端。
比如當(dāng)我們輸入命令set name ziyou 的時候,客戶端會將這個原始命令轉(zhuǎn)換成*3\r\n$3\r\nset\r\n$4\r\nname\r\n$5\r\nziyou,這個協(xié)議大家應(yīng)該比較眼熟,就是 Redis 管道的文件格式。簡單解釋下這個協(xié)議的意思,前面的*3 表示這個命令總共有三個參數(shù),其中的$3,$4,$5 表示相應(yīng)參數(shù)的長度。
服務(wù)端讀取命令請求
當(dāng)服務(wù)端收到該客戶端的數(shù)據(jù)時,就會調(diào)用命令請求處理器來處理對應(yīng)的消息。這塊主要涉及到三個操作,第一個是保存命令,也就是會將命名的請求信息讀取出來保存到對應(yīng)客戶端的輸入緩沖區(qū)里面;保存完了過后會對輸入緩沖區(qū)里面的內(nèi)容進行解析,也就是對上面轉(zhuǎn)換后的協(xié)議進行解析,解析出要執(zhí)行的命令和對應(yīng)的參數(shù),將參數(shù)內(nèi)容和參數(shù)個數(shù)保存到客戶端的對應(yīng)參數(shù)里面;第三步是調(diào)用命令執(zhí)行器來執(zhí)行命令。執(zhí)行的命令和參數(shù)保存在RedisClient 結(jié)構(gòu)的 argv 參數(shù)中,如下圖所示,命令分析完成后,第三步才能更好的進行執(zhí)行操作:
命令執(zhí)行器
命令執(zhí)行器查找實現(xiàn)函數(shù)
思考一個問題,我們這里 argv[0] 參數(shù)中的命令的是進行set 操作,在這里是個 set 字符串,那么 Redis 服務(wù)器是如何進行執(zhí)行的呢?我們可以想到的是需要根據(jù)這個字符串找到對應(yīng)的函數(shù)來進行操作,Redis 在內(nèi)部有個的命令表,是一個字典結(jié)果,key就是對應(yīng)的命令名字,字典的值就是一個個 RedisCommand 結(jié)構(gòu),記錄了命令的實現(xiàn)信息。
結(jié)構(gòu)如下,簡單來說就是通過 argv[0] 中的命令名稱找到命令表中對應(yīng)的redisCommand 結(jié)構(gòu),然后根據(jù) proc 指針找到對應(yīng)的執(zhí)行命令。這里說明一下,命令名稱的大小寫沒有任何影響,我們在輸入的時候不用關(guān)心命令名稱的大小寫問題。
命令執(zhí)行器執(zhí)行預(yù)備操作
在 Redis 服務(wù)器執(zhí)行相關(guān)命令之前,為了保證命令能夠正確的執(zhí)行,還需要進行相關(guān)的預(yù)備處理,部分預(yù)操作如下:
- 檢查命令的參數(shù)和輸入的參數(shù)個數(shù)是否一致,不一致則直接返回錯誤;
- 檢查客戶端是否通過身份驗證,未通過身份驗證則只能執(zhí)行 AUTH 命令進行身份驗證;
- 檢查服務(wù)器的內(nèi)容使用情況,為了保證命令執(zhí)行成功,可能會需要進行內(nèi)容回收;
除了上面的功能之外還有很多需要預(yù)備執(zhí)行的動作,而且根據(jù)服務(wù)器部署的情況不一樣,單機還是集群需要執(zhí)行的操作還有不同。只有當(dāng)所有的 預(yù)備操作都執(zhí)行成功過后,才會真正的執(zhí)行用戶的命令。
由此可見 Redis 的性能是真正的高效,在有這么做操作流程的情況下還能保住命令執(zhí)行的如此快速,不得不說真的很優(yōu)秀。
命令執(zhí)行器調(diào)用命令的實現(xiàn)函數(shù)
當(dāng)前面的預(yù)備操作都完成過后,命令執(zhí)行器就會調(diào)用對應(yīng)的實現(xiàn)函數(shù),在我們這里的例子就是調(diào)用 setCommand(redisClient *c) 函數(shù)進行數(shù)據(jù)寫入操作,具體的 key 值和 value 值在 redisClient 結(jié)構(gòu)中已經(jīng)保存了,所以只要傳遞一個指針進去就可以了。setCommand() 命令執(zhí)行后會返回一個OK\r\n ,這個返回會被保存到客戶端的輸出緩沖區(qū)當(dāng)中,輸出緩沖區(qū)的內(nèi)容后續(xù)會被返回到客戶端,給用戶展示出來,如前面的圖片顯示的內(nèi)容。
命令執(zhí)行器執(zhí)行后續(xù)工作
當(dāng)命令執(zhí)行器調(diào)用具體的實現(xiàn)函數(shù)過后,服務(wù)器還會有相應(yīng)的一些操作要做,比如如果開啟了慢日志功能,會檢查是否要寫入慢日志;如果開啟了 AOF 則需要將剛剛執(zhí)行的命令寫入 AOF 的緩沖區(qū)中;以及如果有服務(wù)器備份或者監(jiān)聽的時候,會把剛剛執(zhí)行的命令廣播過去。
服務(wù)端將命令回復(fù)發(fā)送給客戶端
實現(xiàn)函數(shù)執(zhí)行完過后會將執(zhí)行結(jié)果保存到客戶端的輸出緩沖區(qū)中,此時服務(wù)器的命令回復(fù)處理器會將緩沖區(qū)中的命令回復(fù)發(fā)送給客戶端。命令回復(fù)處理器發(fā)送完數(shù)據(jù)過后會將客戶端的輸出緩沖區(qū)清理,方便后續(xù)的命令存入數(shù)據(jù),同樣回復(fù)的數(shù)據(jù)也是經(jīng)過協(xié)議轉(zhuǎn)換的。
客戶端接收并打印命令回復(fù)內(nèi)容
客戶端收到回復(fù)數(shù)據(jù)過后就數(shù)據(jù)轉(zhuǎn)換成可讀的形式,輸出到控制臺。這樣就得到了我們第一張圖片的結(jié)果。
總結(jié)
通過上面所有的過程,我們可以看到,就是一個簡單的set name ziyou 這樣的語句,整個執(zhí)行的過程也還是很復(fù)雜的,Redis 服務(wù)器在設(shè)計的時候要考慮很多東西,安全,性能等等方面。
引用
《Redis 設(shè)計與實現(xiàn)第二版》