Redis 是如何执行的?
在以往的面試中,當問到一些面試者:Redis 是如何執行的?收到的答案往往是:客戶端發命令給服務器端,服務端收到執行之后再返回給客戶端。然而對于執行細節卻「避而不談」 ,當繼續追問服務器端是如何執行的?能回答上來的人更是寥寥無幾,這未免讓人有些遺憾,一個我們每天都在用的技術,知道原理的人卻寥若晨星。
對于任何一門技術,如果你只停留在「會用」的階段,那就很難有所成就,甚至還有被裁員和找不到工作的風險,我相信能看此篇文章的你,一定是積極上進想有所作為的人,那么借此機會,我們來深入的解一下 Redis 的執行細節。
命令執行流程
一條命令的執行過程有很多細節,但大體可分為:客戶端先將用戶輸入的命令,轉化為 Redis 相關的通訊協議,再用 socket 連接的方式將內容發送給服務器端,服務器端在接收到相關內容之后,先將內容轉化為具體的執行命令,再判斷用戶授權信息和其他相關信息,當驗證通過之后會執行最終命令,命令執行完之后,會進行相關的信息記錄和數據統計,然后再把執行結果發送給客戶端,這樣一條命令的執行流程就結束了。如果是集群模式的話,主節點還會將命令同步至子節點,下面我們一起來看更加具體的執行流程。
步驟一:用戶輸入一條命令
步驟二:客戶端先將命令轉換成 Redis 協議,然后再通過 socket 連接發送給服務器端
客戶端和服務器端是基于 socket 通信的,服務器端在初始化時會創建了一個 socket 監聽,用于監測鏈接客戶端的 socket 鏈接,源碼如下:
void initServer(void) {//......// 開啟 Socket 事件監聽if (server.port != 0 &&listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)exit(1);//...... }socket 小知識:每個 socket 被創建后,會分配兩個緩沖區,輸入緩沖區和輸出緩沖區。 寫入函數并不會立即向網絡中傳輸數據,而是先將數據寫入緩沖區中,再由 TCP 協議將數據從緩沖區發送到目標機器。一旦將數據寫入到緩沖區,函數就可以成功返回,不管它們有沒有到達目標機器,也不管它們何時被發送到網絡,這些都是 TCP 協議負責的事情。 注意:數據有可能剛被寫入緩沖區就發送到網絡,也可能在緩沖區中不斷積壓,多次寫入的數據被一次性發送到網絡,這取決于當時的網絡情況、當前線程是否空閑等諸多因素,不由程序員控制。 讀取函數也是如此,它也是從輸入緩沖區中讀取數據,而不是直接從網絡中讀取。
當 socket 成功連接之后,客戶端會先把命令轉換成 Redis 通訊協議(RESP 協議,REdis Serialization Protocol)發送給服務器端,這個通信協議是為了保障服務器能最快速的理解命令的含義而制定的,如果沒有這個通訊協議,那么 Redis 服務器端要遍歷所有的空格以確認此條命令的含義,這樣會加大服務器的運算量,而直接發送通訊協議,相當于把服務器端的解析工作交給了每一個客戶端,這樣會很大程度的提高 Redis 的運行速度。例如,當我們輸入 set key val 命令時,客戶端會把這個命令轉換為 *3\r\n$3\r\nSET\r\n$4\r\nKEY\r\n$4\r\nVAL\r\n 協議發送給服務器端。 更多通訊協議,可訪問官方文檔:https://redis.io/topics/protocol
擴展知識:I/O 多路復用
Redis 使用的是 I/O 多路復用功能來監聽多 socket 鏈接的,這樣就可以使用一個線程鏈接來處理多個請求,減少線程切換帶來的開銷,同時也避免了 I/O 阻塞操作,從而大大提高了 Redis 的運行效率。
I/O 多路復用機制如下圖所示: 綜合來說,此步驟的執行流程如下:
- 與服務器端以 socket 和 I/O 多路復用的技術建立鏈接;
- 將命令轉換為 Redis 通訊協議,再將這些協議發送至緩沖區。
步驟三:服務器端接收到命令
服務器會先去輸入緩沖中讀取數據,然后判斷數據的大小是否超過了系統設置的值(默認是 1GB),如果大于此值就會返回錯誤信息,并關閉客戶端連接。 默認大小如下圖所示: 當數據大小驗證通過之后,服務器端會對輸入緩沖區中的請求命令進行分析,提取命令請求中包含的命令參數,存儲在 client 對象(服務器端會為每個鏈接創建一個 Client 對象)的屬性中。
步驟四:執行前準備
① 判斷是否為退出命令,如果是則直接返回;
② 非 null 判斷,檢查 client 對象是否為 null,如果是返回錯誤信息;
③ 獲取執行命令,根據 client 對象存儲的屬性信息去 redisCommand 結構中查詢執行命令;
④ 用戶權限效驗,未通過身份驗證的客戶端只能執行 AUTH(授權) 命令,未通過身份驗證的客戶端執行了 AUTH 之外的命令則返回錯誤信息;
⑤ 集群相關操作,如果是集群模式,把命令重定向到目標節點,如果是 master(主節點) 則不需要重定向;
⑥ 檢查服務器端最大內存限制,如果服務器端開啟了最大內存限制,會先檢查內存大小,如果內存超過了最大值會對內存進行回收操作;
⑦ 持久化檢測,檢查服務器是否開啟了持久化和持久化出錯停止寫入配置,如果開啟了此配置并且有持久化失敗的情況,禁止執行寫命令;
⑧ 集群模式最少從節點(slave)驗證,如果是集群模式并且配置了 replminslavestowrite(最小從節點寫入),當從節點的數量少于配置項時,禁止執行寫命令;
⑨ 只讀從節點驗證,當此服務器為只讀從節點時,只接受 master 的寫命令;
⑩ 客戶端訂閱判斷,當客戶端正在訂閱頻道時,只會執行部分命令(只會執行 SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE、PUNSUBSCRIBE,其他命令都會被拒絕)。
? 從節點狀態效驗,當服務器為 slave 并且沒有連接 master 時,只會執行狀態查詢相關的命令,如 info 等;
? 服務器初始化效驗,當服務器正在啟動時,只會執行 loading 標志的命令,其他的命令都會被拒絕;
? lua 腳本阻塞效驗,當服務器因為執行 lua 腳本阻塞時,只會執行部分命令;
? 事務命令效驗,如果執行的是事務命令,則開啟事務把命令放入等待隊列;
? 監視器 (monitor) 判斷,如果服務器打開了監視器功能,那么服務器也會把執行命令和相關參數發送給監視器 (監視器是用于監控服務器運行狀態的)。
當服務器經過以上操作之后,就可以執行真正的操作命令了。
步驟五:執行最終命令,調用 redisCommand 中的 proc 函數執行命令。
步驟六:執行完后相關記錄和統計 ① 檢查慢查詢是否開啟,如果開啟會記錄慢查詢日志; ② 檢查統計信息是否開啟,如果開啟會記錄一些統計信息,例如執行命令所耗費時長和計數器(calls)加1; ③ 檢查持久化功能是否開啟,如果開啟則會記錄持久化信息; ④ 如果有其它從服務器正在復制當前服務器,則會將剛剛執行的命令傳播給其他從服務器。
步驟七:返回結果給客戶端 命令執行完之后,服務器會通過 socket 的方式把執行結果發送給客戶端,客戶端再把結果展示給用戶,至此一條命令的執行就結束了。
小結
當用戶輸入一條命令之后,客戶端會以 socket 的方式把數據轉換成 Redis 協議,并發送至服務器端,服務器端在接受到數據之后,會先將協議轉換為真正的執行命令,在經過各種驗證以保證命令能夠正確并安全的執行,但驗證處理完之后,會調用具體的方法執行此條命令,執行完成之后會進行相關的統計和記錄,然后再把執行結果返回給客戶端,整個執行流程,如下圖所示: 更多執行細節,可在 Redis 的源碼文件 server.c 中查看。
總結
以上是生活随笔為你收集整理的Redis 是如何执行的?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 200+ 面试题补充② Net
- 下一篇: 高质量SQL的30条建议!(后端必备)