Redis深度解析(13)-服務(wù)器

發(fā)表于 討論求助 2023-05-10 14:56:27

命令請(qǐng)求的執(zhí)行過程??

一個(gè)命令請(qǐng)求從發(fā)送到獲得回復(fù)的過程中, 客戶端和服務(wù)器需要完成一系列操作。

舉個(gè)例子, 如果我們使用客戶端執(zhí)行以下命令:

redis> SET KEY VALUE
OK

那么從客戶端發(fā)送?SET?KEY?VALUE?命令到獲得回復(fù)?OK?期間, 客戶端和服務(wù)器共需要執(zhí)行以下操作:

  1. 客戶端向服務(wù)器發(fā)送命令請(qǐng)求?SET?KEY?VALUE?。

  2. 服務(wù)器接收并處理客戶端發(fā)來的命令請(qǐng)求?SET?KEY?VALUE?, 在數(shù)據(jù)庫中進(jìn)行設(shè)置操作, 并產(chǎn)生命令回復(fù)?OK?。

  3. 服務(wù)器將命令回復(fù)?OK?發(fā)送給客戶端。

  4. 客戶端接收服務(wù)器返回的命令回復(fù)?OK?, 并將這個(gè)回復(fù)打印給用戶觀看。

本節(jié)接下來的內(nèi)容將對(duì)這些操作的執(zhí)行細(xì)節(jié)進(jìn)行補(bǔ)充, 詳細(xì)地說明客戶端和服務(wù)器在執(zhí)行命令請(qǐng)求時(shí)所做的各種工作。


發(fā)送命令請(qǐng)求

Redis 服務(wù)器的命令請(qǐng)求來自 Redis 客戶端, 當(dāng)用戶在客戶端中鍵入一個(gè)命令請(qǐng)求時(shí), 客戶端會(huì)將這個(gè)命令請(qǐng)求轉(zhuǎn)換成協(xié)議格式, 然后通過連接到服務(wù)器的套接字, 將協(xié)議格式的命令請(qǐng)求發(fā)送給服務(wù)器, 如圖 14-1 所示。

舉個(gè)例子, 假設(shè)客戶端執(zhí)行命令:

SET KEY VALUE

那么客戶端會(huì)將這個(gè)命令轉(zhuǎn)換成協(xié)議:

*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n

然后將這段協(xié)議內(nèi)容發(fā)送給服務(wù)器。


讀取命令請(qǐng)求

當(dāng)客戶端與服務(wù)器之間的連接套接字因?yàn)榭蛻舳说膶懭攵兊每勺x時(shí), 服務(wù)器將調(diào)用命令請(qǐng)求處理器來執(zhí)行以下操作:

  1. 讀取套接字中協(xié)議格式的命令請(qǐng)求, 并將其保存到客戶端狀態(tài)的輸入緩沖區(qū)里面。

  2. 對(duì)輸入緩沖區(qū)中的命令請(qǐng)求進(jìn)行分析, 提取出命令請(qǐng)求中包含的命令參數(shù), 以及命令參數(shù)的個(gè)數(shù), 然后分別將參數(shù)和參數(shù)個(gè)數(shù)保存到客戶端狀態(tài)的?argv?屬性和?argc?屬性里面。

  3. 調(diào)用命令執(zhí)行器, 執(zhí)行客戶端指定的命令。

繼續(xù)用上一個(gè)小節(jié)的?SET?命令為例子, 圖 14-2 展示了程序?qū)⒚钫?qǐng)求保存到客戶端狀態(tài)的輸入緩沖區(qū)之后, 客戶端狀態(tài)的樣子。

之后, 分析程序?qū)?duì)輸入緩沖區(qū)中的協(xié)議:

*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n

進(jìn)行分析, 并將得出的分析結(jié)果保存到客戶端狀態(tài)的?argv?屬性和?argc?屬性里面, 如圖 14-3 所示。

之后, 服務(wù)器將通過調(diào)用命令執(zhí)行器來完成執(zhí)行命令所需的余下步驟, 以下幾個(gè)小節(jié)將分別介紹命令執(zhí)行器所執(zhí)行的工作。


命令執(zhí)行器(1):查找命令實(shí)現(xiàn)

命令執(zhí)行器要做的第一件事就是根據(jù)客戶端狀態(tài)的?argv[0]?參數(shù), 在命令表(command table)中查找參數(shù)所指定的命令, 并將找到的命令保存到客戶端狀態(tài)的?cmd?屬性里面。

命令表是一個(gè)字典, 字典的鍵是一個(gè)個(gè)命令名字,比如?"set","get","del"?,等等; 而字典的值則是一個(gè)個(gè)?redisCommand?結(jié)構(gòu), 每個(gè)redisCommand?結(jié)構(gòu)記錄了一個(gè) Redis 命令的實(shí)現(xiàn)信息, 表 14-1 記錄了這個(gè)結(jié)構(gòu)的各個(gè)主要屬性的類型和作用。

圖 14-4 展示了命令表的樣子, 并且以?SET?命令和?GET?命令作為例子, 展示了?redisCommand?結(jié)構(gòu):

  • SET?命令的名字為?"set"?, 實(shí)現(xiàn)函數(shù)為?setCommand?; 命令的參數(shù)個(gè)數(shù)為?-3?, 表示命令接受三個(gè)或以上數(shù)量的參數(shù); 命令的標(biāo)識(shí)為"wm"?, 表示?SET?命令是一個(gè)寫入命令, 并且在執(zhí)行這個(gè)命令之前, 服務(wù)器應(yīng)該對(duì)占用內(nèi)存狀況進(jìn)行檢查, 因?yàn)檫@個(gè)命令可能會(huì)占用大量?jī)?nèi)存。

  • GET?命令的名字為?"get"?, 實(shí)現(xiàn)函數(shù)為?getCommand?函數(shù); 命令的參數(shù)個(gè)數(shù)為?2?, 表示命令只接受兩個(gè)參數(shù); 命令的標(biāo)識(shí)為?"r"?, 表示這是一個(gè)只讀命令。

繼續(xù)之前?SET?命令的例子, 當(dāng)程序以圖 14-3 中的?argv[0]?作為輸入, 在命令表中進(jìn)行查找時(shí), 命令表將返回?"set"?鍵所對(duì)應(yīng)的redisCommand?結(jié)構(gòu), 客戶端狀態(tài)的?cmd?指針會(huì)指向這個(gè)?redisCommand?結(jié)構(gòu), 如圖 14-5 所示。

命令名字的大小寫不影響命令表的查找結(jié)果

因?yàn)槊畋硎褂玫氖谴笮憻o關(guān)的查找算法, 無論輸入的命令名字是大寫、小寫或者混合大小寫, 只要命令的名字是正確的, 就能找到相應(yīng)的?redisCommand?結(jié)構(gòu)。

比如說, 無論用戶輸入的命令名字是?"SET"?、?"set"?、?"SeT"?又或者?"sEt"?, 命令表返回的都是同一個(gè)?redisCommand?結(jié)構(gòu)。

這也是 Redis 客戶端可以發(fā)送不同大小寫的命令, 并且獲得相同執(zhí)行結(jié)果的原因:

# 以下四個(gè)命令的執(zhí)行效果完全一樣

redis> SET msg "hello world"
OK

redis> set msg "hello world"
OK

redis> SeT msg "hello world"
OK

redis> sEt msg "hello world"
OK


命令執(zhí)行器(2):執(zhí)行預(yù)備操作

到目前為止, 服務(wù)器已經(jīng)將執(zhí)行命令所需的命令實(shí)現(xiàn)函數(shù)(保存在客戶端狀態(tài)的?cmd?屬性)、參數(shù)(保存在客戶端狀態(tài)的?argv?屬性)、參數(shù)個(gè)數(shù)(保存在客戶端狀態(tài)的?argc?屬性)都收集齊了, 但是在真正執(zhí)行命令之前, 程序還需要進(jìn)行一些預(yù)備操作, 從而確保命令可以正確、順利地被執(zhí)行, 這些操作包括:

  • 檢查客戶端狀態(tài)的?cmd?指針是否指向?NULL?, 如果是的話, 那么說明用戶輸入的命令名字找不到相應(yīng)的命令實(shí)現(xiàn), 服務(wù)器不再執(zhí)行后續(xù)步驟, 并向客戶端返回一個(gè)錯(cuò)誤。

  • 根據(jù)客戶端?cmd?屬性指向的?redisCommand?結(jié)構(gòu)的?arity?屬性, 檢查命令請(qǐng)求所給定的參數(shù)個(gè)數(shù)是否正確, 當(dāng)參數(shù)個(gè)數(shù)不正確時(shí), 不再執(zhí)行后續(xù)步驟, 直接向客戶端返回一個(gè)錯(cuò)誤。 比如說, 如果?redisCommand?結(jié)構(gòu)的?arity?屬性的值為?-3?, 那么用戶輸入的命令參數(shù)個(gè)數(shù)必須大于等于?3?個(gè)才行。

  • 檢查客戶端是否已經(jīng)通過了身份驗(yàn)證, 未通過身份驗(yàn)證的客戶端只能執(zhí)行?AUTH?命令, 如果未通過身份驗(yàn)證的客戶端試圖執(zhí)行除?AUTH命令之外的其他命令, 那么服務(wù)器將向客戶端返回一個(gè)錯(cuò)誤。

  • 如果服務(wù)器打開了?maxmemory?功能, 那么在執(zhí)行命令之前, 先檢查服務(wù)器的內(nèi)存占用情況, 并在有需要時(shí)進(jìn)行內(nèi)存回收, 從而使得接下來的命令可以順利執(zhí)行。 如果內(nèi)存回收失敗, 那么不再執(zhí)行后續(xù)步驟, 向客戶端返回一個(gè)錯(cuò)誤。

  • 如果服務(wù)器上一次執(zhí)行?BGSAVE?命令時(shí)出錯(cuò), 并且服務(wù)器打開了?stop-writes-on-bgsave-error?功能, 而且服務(wù)器即將要執(zhí)行的命令是一個(gè)寫命令, 那么服務(wù)器將拒絕執(zhí)行這個(gè)命令, 并向客戶端返回一個(gè)錯(cuò)誤。

  • 如果客戶端當(dāng)前正在用?SUBSCRIBE?命令訂閱頻道, 或者正在用?PSUBSCRIBE?命令訂閱模式, 那么服務(wù)器只會(huì)執(zhí)行客戶端發(fā)來的SUBSCRIBE?、?PSUBSCRIBE?、?UNSUBSCRIBE?、?PUNSUBSCRIBE?四個(gè)命令, 其他別的命令都會(huì)被服務(wù)器拒絕。

  • 如果服務(wù)器正在進(jìn)行數(shù)據(jù)載入, 那么客戶端發(fā)送的命令必須帶有?l?標(biāo)識(shí)(比如?INFO?、?SHUTDOWN?、?PUBLISH?,等等)才會(huì)被服務(wù)器執(zhí)行, 其他別的命令都會(huì)被服務(wù)器拒絕。

  • 如果服務(wù)器因?yàn)閳?zhí)行 Lua 腳本而超時(shí)并進(jìn)入阻塞狀態(tài), 那么服務(wù)器只會(huì)執(zhí)行客戶端發(fā)來的?SHUTDOWN nosave?命令和?SCRIPT KILL?命令, 其他別的命令都會(huì)被服務(wù)器拒絕。

  • 如果客戶端正在執(zhí)行事務(wù), 那么服務(wù)器只會(huì)執(zhí)行客戶端發(fā)來的?EXEC?、?DISCARD?、?MULTI?、?WATCH?四個(gè)命令, 其他命令都會(huì)被放進(jìn)事務(wù)隊(duì)列中。

  • 如果服務(wù)器打開了監(jiān)視器功能, 那么服務(wù)器會(huì)將要執(zhí)行的命令和參數(shù)等信息發(fā)送給監(jiān)視器。

當(dāng)完成了以上預(yù)備操作之后, 服務(wù)器就可以開始真正執(zhí)行命令了。

注意

以上只列出了服務(wù)器在單機(jī)模式下執(zhí)行命令時(shí)的檢查操作, 當(dāng)服務(wù)器在復(fù)制或者集群模式下執(zhí)行命令時(shí), 預(yù)備操作還會(huì)更多一些。


命令執(zhí)行器(3):調(diào)用命令的執(zhí)行函數(shù)

在前面的操作中, 服務(wù)器已經(jīng)將要執(zhí)行命令的實(shí)現(xiàn)保存到了客戶端狀態(tài)的?cmd?屬性里面, 并將命令的參數(shù)和參數(shù)個(gè)數(shù)分別保存到了客戶端狀態(tài)的?argv?屬性和?argc?屬性里面, 當(dāng)服務(wù)器決定要執(zhí)行命令時(shí), 它只要執(zhí)行以下語句就可以了:

// client 是指向客戶端狀態(tài)的指針

client->cmd->proc(client);

因?yàn)閳?zhí)行命令所需的實(shí)際參數(shù)都已經(jīng)保存到客戶端狀態(tài)的?argv?屬性里面了, 所以命令的實(shí)現(xiàn)函數(shù)只需要一個(gè)指向客戶端狀態(tài)的指針作為參數(shù)即可。

繼續(xù)以之前的?SET?命令為例子, 圖 14-6 展示了客戶端包含了命令實(shí)現(xiàn)、參數(shù)和參數(shù)個(gè)數(shù)的樣子。

對(duì)于這個(gè)例子來說, 執(zhí)行語句:

client->cmd->proc(client);

等于執(zhí)行語句:

setCommand(client);

被調(diào)用的命令實(shí)現(xiàn)函數(shù)會(huì)執(zhí)行指定的操作, 并產(chǎn)生相應(yīng)的命令回復(fù), 這些回復(fù)會(huì)被保存在客戶端狀態(tài)的輸出緩沖區(qū)里面(buf?屬性和?reply屬性), 之后實(shí)現(xiàn)函數(shù)還會(huì)為客戶端的套接字關(guān)聯(lián)命令回復(fù)處理器, 這個(gè)處理器負(fù)責(zé)將命令回復(fù)返回給客戶端。

對(duì)于前面?SET?命令的例子來說, 函數(shù)調(diào)用?setCommand(client);?將產(chǎn)生一個(gè)?"+OK\r\n"?回復(fù), 這個(gè)回復(fù)會(huì)被保存到客戶端狀態(tài)的?buf?屬性里面, 如圖 14-7 所示。


命令執(zhí)行器(4):執(zhí)行后續(xù)工作

在執(zhí)行完實(shí)現(xiàn)函數(shù)之后, 服務(wù)器還需要執(zhí)行一些后續(xù)工作:

  • 如果服務(wù)器開啟了慢查詢?nèi)罩竟δ埽?那么慢查詢?nèi)罩灸K會(huì)檢查是否需要為剛剛執(zhí)行完的命令請(qǐng)求添加一條新的慢查詢?nèi)罩尽?/p>

  • 根據(jù)剛剛執(zhí)行命令所耗費(fèi)的時(shí)長, 更新被執(zhí)行命令的?redisCommand?結(jié)構(gòu)的?milliseconds?屬性, 并將命令的?redisCommand?結(jié)構(gòu)的?calls計(jì)數(shù)器的值增一。

  • 如果服務(wù)器開啟了 AOF 持久化功能, 那么 AOF 持久化模塊會(huì)將剛剛執(zhí)行的命令請(qǐng)求寫入到 AOF 緩沖區(qū)里面。

  • 如果有其他從服務(wù)器正在復(fù)制當(dāng)前這個(gè)服務(wù)器, 那么服務(wù)器會(huì)將剛剛執(zhí)行的命令傳播給所有從服務(wù)器。

當(dāng)以上操作都執(zhí)行完了之后, 服務(wù)器對(duì)于當(dāng)前命令的執(zhí)行到此就告一段落了, 之后服務(wù)器就可以繼續(xù)從文件事件處理器中取出并處理下一個(gè)命令請(qǐng)求了。


將命令回復(fù)發(fā)送給客戶端

前面說過, 命令實(shí)現(xiàn)函數(shù)會(huì)將命令回復(fù)保存到客戶端的輸出緩沖區(qū)里面, 并為客戶端的套接字關(guān)聯(lián)命令回復(fù)處理器, 當(dāng)客戶端套接字變?yōu)榭蓪憼顟B(tài)時(shí), 服務(wù)器就會(huì)執(zhí)行命令回復(fù)處理器, 將保存在客戶端輸出緩沖區(qū)中的命令回復(fù)發(fā)送給客戶端。

當(dāng)命令回復(fù)發(fā)送完畢之后, 回復(fù)處理器會(huì)清空客戶端狀態(tài)的輸出緩沖區(qū), 為處理下一個(gè)命令請(qǐng)求做好準(zhǔn)備。

以圖 14-7 所示的客戶端狀態(tài)為例子, 當(dāng)客戶端的套接字變?yōu)榭蓪憼顟B(tài)時(shí), 命令回復(fù)處理器會(huì)將協(xié)議格式的命令回復(fù)?"+OK\r\n"?發(fā)送給客戶端。


客戶端接收并打印命令回復(fù)

當(dāng)客戶端接收到協(xié)議格式的命令回復(fù)之后, 它會(huì)將這些回復(fù)轉(zhuǎn)換成人類可讀的格式, 并打印給用戶觀看(假設(shè)我們使用的是 Redis 自帶的redis-cli?客戶端), 如圖 14-8 所示。

繼續(xù)以之前的?SET?命令為例子, 當(dāng)客戶端接到服務(wù)器發(fā)來的?"+OK\r\n"?協(xié)議回復(fù)時(shí), 它會(huì)將這個(gè)回復(fù)轉(zhuǎn)換成?"OK\n"?, 然后打印給用戶看:

redis> SET KEY VALUE
OK

以上就是 Redis 客戶端和服務(wù)器執(zhí)行命令請(qǐng)求的整個(gè)過程了。


總結(jié)

  • 一個(gè)命令請(qǐng)求從發(fā)送到完成主要包括以下步驟:

    • 1. 客戶端將命令請(qǐng)求發(fā)送給服務(wù)器;

    • 2. 服務(wù)器讀取命令請(qǐng)求,并分析出命令參數(shù);

    • 3. 命令執(zhí)行器根據(jù)參數(shù)查找命令的實(shí)現(xiàn)函數(shù),然后執(zhí)行實(shí)現(xiàn)函數(shù)并得出命令回復(fù);

    • 4. 服務(wù)器將命令回復(fù)返回給客戶端。

  • serverCron?函數(shù)默認(rèn)每隔?100?毫秒執(zhí)行一次, 它的工作主要包括更新服務(wù)器狀態(tài)信息, 處理服務(wù)器接收的?SIGTERM?信號(hào), 管理客戶端資源和數(shù)據(jù)庫狀態(tài), 檢查并執(zhí)行持久化操作, 等等。

  • 服務(wù)器從啟動(dòng)到能夠處理客戶端的命令請(qǐng)求需要執(zhí)行以下步驟:

    • 1. 初始化服務(wù)器狀態(tài);

    • 2. 載入服務(wù)器配置;

    • 3. 初始化服務(wù)器數(shù)據(jù)結(jié)構(gòu);

    • 4. 還原數(shù)據(jù)庫狀態(tài);

    • 5. 執(zhí)行事件循環(huán)。


前期文章回顧:

Redis深度解析(12)-客戶端

Redis深度解析(11)-事件


發(fā)表
26906人 簽到看排名