您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Redis中命令執行過程的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
Redis 是怎么執行遠程客戶端發來的命令的
Redis client(客戶端)
Redis 是單線程應用,它是如何與多個客戶端簡歷網絡鏈接并處理命令的?
由于 Redis 是基于 I/O 多路復用技術,為了能夠處理多個客戶端的請求,Redis 在本地為每一個鏈接到 Redis 服務器的客戶端創建了一個 redisClient 的數據結構,這個數據結構包含了每個客戶端各自的狀態和執行的命令。 Redis 服務器使用一個鏈表來維護多個 redisClient 數據結構。
在服務器端用一個鏈表來管理所有的 redisClient。
struct redisServer { //... list *clients; /* List of active clients */ //... }
所以我就看看 redisClient 包含的數據結構和重要參數:
typedef struct redisClient { // 客戶端狀態標志 int flags; /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */ // 套接字描述符 int fd; // 當前正在使用的數據庫 redisDb *db; // 當前正在使用的數據庫的 id (號碼) int dictid; // 客戶端的名字 robj *name; /* As set by CLIENT SETNAME */ // 查詢緩沖區 sds querybuf; // 查詢緩沖區長度峰值 size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size */ // 參數數量 int argc; // 參數對象數組 robj **argv; // 記錄被客戶端執行的命令 struct redisCommand *cmd, *lastcmd; // 請求的類型:內聯命令還是多條命令 int reqtype; // 剩余未讀取的命令內容數量 int multibulklen; /* number of multi bulk arguments left to read */ // 命令內容的長度 long bulklen; /* length of bulk argument in multi bulk request */ // 回復鏈表 list *reply; // 回復鏈表中對象的總大小 unsigned long reply_bytes; /* Tot bytes of objects in reply list */ // 已發送字節,處理 short write 用 int sentlen; /* Amount of bytes already sent in the current buffer or object being sent. */ // 回復偏移量 int bufpos; // 回復緩沖區 char buf[REDIS_REPLY_CHUNK_BYTES]; // ... }
這里需要特別的注意,redisClient 并非指遠程的客戶端,而是一個 Redis 服務本地的數據結構,我們可以理解這個 redisClient 是遠程客戶端的一個映射或者代理。
flags
flags 表示了目前客戶端的角色,以及目前所處的狀態。他比較特殊可以單獨表示一個狀態或者多個狀態。
querybuf
querybuf 是一個 sds 動態字符串類型,所謂 buf 說明是它只是一個緩沖區,用于存儲沒有被解析的命令。
argc & argv
上文的 querybuf 是一個沒有處理過的命令,當 Redis 將 querybuf 命令解析以后,會將得出的參數個數和以及參數分別保存在 argc 和 argv 中。argv 是一個 redisObject 的數組。
cmd
Redis 使用一個字典保存了所有的 redisCommand。key 是 redisCommand 的名字,值就是一個 redisCommand 結構,這個結構保存了命令的實現函數,命令的標志,命令應該給定的參數個數,命令的執行次數和總消耗時長等統計信息,cmd 是一個 redisCommand。
當 Redis 解析出 argv 和 argc 后,會根據數組 argv[0],到字典中查詢出對應的 redisCommand。上文的例子中 Redis 就會去字典去查找 SET 這個命令對應的 redisCommand。redis 會執行 redisCommand 中命令的實現函數。
buf & bufpos & reply
buf 是一個長度為 REDIS_REPLY_CHUNK_BYTES 的數組。Redis 執行相應的操作以后,就會將需要返回的返回的數據存儲到 buf 中,bufpos 用于記錄 buf 中已用的字節數數量,當需要恢復的數據大于 REDIS_REPLY_CHUNK_BYTES 時,redis 就會是用 reply 這個鏈表來保存數據。
其他參數
其他參數大家看注釋就能明白,就是字面的意思。省略的參數基本上涉及 Redis 集群管理的參數,在之后的文章中會繼續講解。
客戶端的鏈接和斷開
上文說過 redisServer 是用一個鏈表來維護所有的 redisClient 狀態,每當有一個客戶端發起鏈接以后,就會在 Redis 中生成一個對應的 redisClient 數據結構,增加到clients這個鏈表之后。
一個客戶端很可能被多種原因斷開。
總體分為幾種類型:
客戶端主動退出或者被 kill。
timeout 超時。
Redis 為了自我保護,會斷開發的數據超過限制大小的客戶端。
Redis 為了自我保護,會斷需要返回的數據超過限制大小的客戶端。
調用總結
當客戶端和服務器端的嵌套字變得可讀的時候,服務器將會調用命令請求處理器來執行以下操作:
讀取嵌套字中的數據,寫入 querybuf。
解析 querybuf 中的命令,記錄到 argc 和 argv 中。
根據 argv[0] 查找對應的 recommand。
執行 recommand 對應的實現函數。
執行以后將結果存入 buf & bufpos & reply 中,返回給調用方。
Redis Server (服務端)
上文是從 redisClient 的角度來觀察命令的執行,文章接下來的部分將會從 Redis 的代碼層面,微觀的觀察 Redis 是怎么實現命令的執行的。
redisServer 的啟動
在了解redisServer 的工作機制的工作機制之前,需要了解 redisServer 的啟動做了什么:
可以繼續觀察 Redis 的 main() 函數。
int main(int argc, char **argv) { //... // 創建并初始化服務器數據結構 initServer(); //... }
我們只關注 initServer() 這個函數,他負責初始化服務器的數據結構。繼續跟蹤代碼:
void initServer() { //... //創建eventLoop server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR); /* Create an event handler for accepting new connections in TCP and Unix * domain sockets. */ // 為 TCP 連接關聯連接應答(accept)處理器 // 用于接受并應答客戶端的 connect() 調用 for (j = 0; j < server.ipfd_count; j++) { if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler,NULL) == AE_ERR) { redisPanic( "Unrecoverable error creating server.ipfd file event."); } } // 為本地套接字關聯應答處理器 if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE, acceptUnixHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.sofd file event."); //... }
篇幅限制,我們省略了很多與本編文章無關的代碼,保留了核心邏輯代碼。
在上一篇文章中 《Redis 中的事件驅動模型》 我們講解過,redis 使用不同的事件處理器,處理不同的事件。
在這段代碼里面:
初始化了事件處理器的 eventLoop
向 eventLoop 中注冊了兩個事件處理器 acceptTcpHandler 和 acceptUnixHandler,分別處理遠程的鏈接和本地鏈接。
redisClient 的創建
當有一個遠程客戶端連接到 Redis 的服務器,會觸發 acceptTcpHandler 事件處理器.
acceptTcpHandler 事件處理器,會創建一個鏈接。然后繼續調用 acceptCommonHandler。
acceptCommonHandler 事件處理器的作用是:
調用 createClient() 方法創建 redisClient
檢查已經創建的 redisClient 是否超過 server 允許的數量的上限
如果超過上限就拒絕遠程連接
否則創建 redisClient 創建成功
并更新連接的統計次數,更新 redisClinet 的 flags 字段
這個時候 Redis 在服務端創建了 redisClient 數據結構,這個時候遠程的客戶端就在 redisServer 中創建了一個代理。遠程的客戶端就與 Redis 服務器建立了聯系,就可以向服務器發送命令了。
處理命令
在 createClient() 行數中:
// 綁定讀事件到事件 loop (開始接收命令請求) if (aeCreateFileEvent(server.el,fd,AE_READABLE,readQueryFromClient, c) == AE_ERR)
向 eventLoop 中注冊了 readQueryFromClient。 readQueryFromClient 的作用就是從client中讀取客戶端的查詢緩沖區內容。
然后調用函數 processInputBuffer 來處理客戶端的請求。在 processInputBuffer 中有幾個核心函數:
processInlineBuffer 和 processMultibulkBuffer 解析 querybuf 中的命令,記錄到 argc 和 argv 中。
processCommand 根據 argv[0] 查找對應的 recommen,執行 recommend 對應的執行函數。在執行之前還會驗證命令的正確性。將結果存入 buf & bufpos & reply 中
返回數據
萬事具備了,執行完了命令就需要把數據返回給遠程的調用方。調用鏈如下
processCommand -> addReply -> prepareClientToWrite
在 prepareClientToWrite 中我們有見到了熟悉的代碼:
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,sendReplyToClient, c) == AE_ERR) return REDIS_ERR;
向 eventloop 綁定了 sendReplyToClient 事件處理器。
在 sendReplyToClient 中觀察代碼發現,如果 bufpos 大于 0,將會把 buf 發送給遠程的客戶端,如果鏈表 reply 的長度大于0,就會將遍歷鏈表 reply,發送給遠程的客戶端,這里需要注意的是,為了避免 reply 數據量過大,就會過度的占用資源引起 Redis 相應慢。為了解決這個問題,當寫入的總數量大于 REDIS_MAX_WRITE_PER_EVENT 時,Redis 將會臨時中斷寫入,記錄操作的進度,將處理時間讓給其他操作,剩余的內容等下次繼續。這樣的套路我們一路走來看過太多了。
總結
遠程客戶端連接到 redis 后,redis服務端會為遠程客戶端創建一個 redisClient 作為代理。
redis 會讀取嵌套字中的數據,寫入 querybuf 中。
解析 querybuf 中的命令,記錄到 argc 和 argv 中。
根據 argv[0] 查找對應的 recommand。
執行 recommend 對應的執行函數。
執行以后將結果存入 buf & bufpos & reply 中。
返回給調用方。返回數據的時候,會控制寫入數據量的大小,如果過大會分成若干次。保證 redis 的相應時間。
Redis 作為單線程應用,一直貫徹的思想就是,每個步驟的執行都有一個上限(包括執行時間的上限或者文件尺寸的上限)一旦達到上限,就會記錄下當前的執行進度,下次再執行。保證了 Redis 能夠及時響應不發生阻塞。
關于“Redis中命令執行過程的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。