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