久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

Redis中命令執(zhí)行過程的示例分析

176次閱讀
沒有評論

共計 5425 個字符,預計需要花費 14 分鐘才能閱讀完成。

這篇文章將為大家詳細講解有關(guān) Redis 中命令執(zhí)行過程的示例分析,丸趣 TV 小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

Redis 是怎么執(zhí)行遠程客戶端發(fā)來的命令的

Redis client(客戶端)

Redis 是單線程應用,它是如何與多個客戶端簡歷網(wǎng)絡(luò)鏈接并處理命令的?
由于 Redis 是基于 I/O 多路復用技術(shù),為了能夠處理多個客戶端的請求,Redis 在本地為每一個鏈接到 Redis 服務器的客戶端創(chuàng)建了一個 redisClient 的數(shù)據(jù)結(jié)構(gòu),這個數(shù)據(jù)結(jié)構(gòu)包含了每個客戶端各自的狀態(tài)和執(zhí)行的命令。Redis 服務器使用一個鏈表來維護多個 redisClient 數(shù)據(jù)結(jié)構(gòu)。

在服務器端用一個鏈表來管理所有的 redisClient。

struct redisServer {
 //...
 list *clients; /* List of active clients */
 //...
}

所以我就看看 redisClient 包含的數(shù)據(jù)結(jié)構(gòu)和重要參數(shù):

typedef struct redisClient {
 //  客戶端狀態(tài)標志
 int flags; /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */
 
 //  套接字描述符
 int fd;
 //  當前正在使用的數(shù)據(jù)庫
 redisDb *db;
 //  當前正在使用的數(shù)據(jù)庫的  id (號碼) int dictid;
 //  客戶端的名字
 robj *name; /* As set by CLIENT SETNAME */
 //  查詢緩沖區(qū)
 sds querybuf;
 //  查詢緩沖區(qū)長度峰值
 size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size */
 //  參數(shù)數(shù)量
 int argc;
 //  參數(shù)對象數(shù)組
 robj **argv;
 //  記錄被客戶端執(zhí)行的命令
 struct redisCommand *cmd, *lastcmd;
 //  請求的類型:內(nèi)聯(lián)命令還是多條命令
 int reqtype;
 //  剩余未讀取的命令內(nèi)容數(shù)量
 int multibulklen; /* number of multi bulk arguments left to read */
 //  命令內(nèi)容的長度
 long bulklen; /* length of bulk argument in multi bulk request */
 //  回復鏈表
 list *reply;
 //  回復鏈表中對象的總大小
 unsigned long reply_bytes; /* Tot bytes of objects in reply list */
 //  已發(fā)送字節(jié),處理  short write  用
 int sentlen; /* Amount of bytes already sent in the current
 buffer or object being sent. */
 //  回復偏移量
 int bufpos;
 //  回復緩沖區(qū)
 char buf[REDIS_REPLY_CHUNK_BYTES];
 // ...
}

這里需要特別的注意,redisClient 并非指遠程的客戶端,而是一個 Redis 服務本地的數(shù)據(jù)結(jié)構(gòu),我們可以理解這個 redisClient 是遠程客戶端的一個映射或者代理。

flags

flags 表示了目前客戶端的角色,以及目前所處的狀態(tài)。他比較特殊可以單獨表示一個狀態(tài)或者多個狀態(tài)。

querybuf

querybuf 是一個 sds 動態(tài)字符串類型,所謂 buf 說明是它只是一個緩沖區(qū),用于存儲沒有被解析的命令。

argc argv

上文的 querybuf 是一個沒有處理過的命令,當 Redis 將 querybuf 命令解析以后,會將得出的參數(shù)個數(shù)和以及參數(shù)分別保存在 argc 和 argv 中。argv 是一個 redisObject 的數(shù)組。

cmd

Redis 使用一個字典保存了所有的 redisCommand。key 是 redisCommand 的名字,值就是一個 redisCommand 結(jié)構(gòu),這個結(jié)構(gòu)保存了命令的實現(xiàn)函數(shù),命令的標志,命令應該給定的參數(shù)個數(shù),命令的執(zhí)行次數(shù)和總消耗時長等統(tǒng)計信息,cmd 是一個 redisCommand。

當 Redis 解析出 argv 和 argc 后,會根據(jù)數(shù)組 argv[0],到字典中查詢出對應的 redisCommand。上文的例子中 Redis 就會去字典去查找 SET 這個命令對應的 redisCommand。redis 會執(zhí)行 redisCommand 中命令的實現(xiàn)函數(shù)。

buf bufpos reply

buf 是一個長度為 REDIS_REPLY_CHUNK_BYTES 的數(shù)組。Redis 執(zhí)行相應的操作以后,就會將需要返回的返回的數(shù)據(jù)存儲到 buf 中,bufpos 用于記錄 buf 中已用的字節(jié)數(shù)數(shù)量,當需要恢復的數(shù)據(jù)大于 REDIS_REPLY_CHUNK_BYTES 時,redis 就會是用 reply 這個鏈表來保存數(shù)據(jù)。

其他參數(shù)

其他參數(shù)大家看注釋就能明白,就是字面的意思。省略的參數(shù)基本上涉及 Redis 集群管理的參數(shù),在之后的文章中會繼續(xù)講解。

客戶端的鏈接和斷開

上文說過 redisServer 是用一個鏈表來維護所有的 redisClient 狀態(tài),每當有一個客戶端發(fā)起鏈接以后,就會在 Redis 中生成一個對應的 redisClient 數(shù)據(jù)結(jié)構(gòu),增加到 clients 這個鏈表之后。

一個客戶端很可能被多種原因斷開。

總體分為幾種類型:

客戶端主動退出或者被 kill。

timeout 超時。

Redis 為了自我保護,會斷開發(fā)的數(shù)據(jù)超過限制大小的客戶端。

Redis 為了自我保護,會斷需要返回的數(shù)據(jù)超過限制大小的客戶端。

調(diào)用總結(jié)

當客戶端和服務器端的嵌套字變得可讀的時候,服務器將會調(diào)用命令請求處理器來執(zhí)行以下操作:

讀取嵌套字中的數(shù)據(jù),寫入 querybuf。

解析 querybuf 中的命令,記錄到 argc 和 argv 中。

根據(jù) argv[0] 查找對應的 recommand。

執(zhí)行 recommand 對應的實現(xiàn)函數(shù)。

執(zhí)行以后將結(jié)果存入 buf bufpos reply 中,返回給調(diào)用方。

Redis Server (服務端)

上文是從 redisClient 的角度來觀察命令的執(zhí)行,文章接下來的部分將會從 Redis 的代碼層面,微觀的觀察 Redis 是怎么實現(xiàn)命令的執(zhí)行的。

redisServer 的啟動

在了解 redisServer 的工作機制的工作機制之前,需要了解 redisServer 的啟動做了什么:

可以繼續(xù)觀察 Redis 的 main() 函數(shù)。

int main(int argc, char **argv) {
 //...
 //  創(chuàng)建并初始化服務器數(shù)據(jù)結(jié)構(gòu)
 initServer();
 //...
}

我們只關(guān)注 initServer() 這個函數(shù),他負責初始化服務器的數(shù)據(jù)結(jié)構(gòu)。繼續(xù)跟蹤代碼:

void initServer() {
 //...
 // 創(chuàng)建 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  連接關(guān)聯(lián)連接應答(accept)處理器
 //  用于接受并應答客戶端的  connect()  調(diào)用
 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. 
 }
 }
 //  為本地套接字關(guān)聯(lián)應答處理器
 if (server.sofd   0   aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
 acceptUnixHandler,NULL) == AE_ERR) redisPanic( Unrecoverable error creating server.sofd file event. 
 //...
}

篇幅限制,我們省略了很多與本編文章無關(guān)的代碼,保留了核心邏輯代碼。

在上一篇文章中《Redis 中的事件驅(qū)動模型》我們講解過,redis 使用不同的事件處理器,處理不同的事件。

在這段代碼里面:

初始化了事件處理器的 eventLoop

向 eventLoop 中注冊了兩個事件處理器 acceptTcpHandler 和 acceptUnixHandler,分別處理遠程的鏈接和本地鏈接。

redisClient 的創(chuàng)建

當有一個遠程客戶端連接到 Redis 的服務器,會觸發(fā) acceptTcpHandler 事件處理器.

acceptTcpHandler 事件處理器,會創(chuàng)建一個鏈接。然后繼續(xù)調(diào)用 acceptCommonHandler。

acceptCommonHandler 事件處理器的作用是:

調(diào)用 createClient() 方法創(chuàng)建 redisClient

檢查已經(jīng)創(chuàng)建的 redisClient 是否超過 server 允許的數(shù)量的上限

如果超過上限就拒絕遠程連接

否則創(chuàng)建 redisClient 創(chuàng)建成功

并更新連接的統(tǒng)計次數(shù),更新 redisClinet 的 flags 字段

這個時候 Redis 在服務端創(chuàng)建了 redisClient 數(shù)據(jù)結(jié)構(gòu),這個時候遠程的客戶端就在 redisServer 中創(chuàng)建了一個代理。遠程的客戶端就與 Redis 服務器建立了聯(lián)系,就可以向服務器發(fā)送命令了。

處理命令

在 createClient() 行數(shù)中:

//  綁定讀事件到事件  loop (開始接收命令請求)if (aeCreateFileEvent(server.el,fd,AE_READABLE,readQueryFromClient, c) == AE_ERR)

向 eventLoop 中注冊了 readQueryFromClient。readQueryFromClient 的作用就是從 client 中讀取客戶端的查詢緩沖區(qū)內(nèi)容。

然后調(diào)用函數(shù) processInputBuffer 來處理客戶端的請求。在 processInputBuffer 中有幾個核心函數(shù):

processInlineBuffer 和 processMultibulkBuffer 解析 querybuf 中的命令,記錄到 argc 和 argv 中。

processCommand 根據(jù) argv[0] 查找對應的 recommen, 執(zhí)行 recommend 對應的執(zhí)行函數(shù)。在執(zhí)行之前還會驗證命令的正確性。將結(jié)果存入 buf bufpos reply 中

返回數(shù)據(jù)

萬事具備了,執(zhí)行完了命令就需要把數(shù)據(jù)返回給遠程的調(diào)用方。調(diào)用鏈如下

processCommand – addReply – prepareClientToWrite

在 prepareClientToWrite 中我們有見到了熟悉的代碼:

aeCreateFileEvent(server.el, c- fd, AE_WRITABLE,sendReplyToClient, c) == AE_ERR) return REDIS_ERR;

向 eventloop 綁定了 sendReplyToClient 事件處理器。

在 sendReplyToClient 中觀察代碼發(fā)現(xiàn),如果 bufpos 大于 0,將會把 buf 發(fā)送給遠程的客戶端,如果鏈表 reply 的長度大于 0,就會將遍歷鏈表 reply,發(fā)送給遠程的客戶端,這里需要注意的是,為了避免 reply 數(shù)據(jù)量過大,就會過度的占用資源引起 Redis 相應慢。為了解決這個問題,當寫入的總數(shù)量大于 REDIS_MAX_WRITE_PER_EVENT 時,Redis 將會臨時中斷寫入,記錄操作的進度,將處理時間讓給其他操作,剩余的內(nèi)容等下次繼續(xù)。這樣的套路我們一路走來看過太多了。

總結(jié)

遠程客戶端連接到 redis 后,redis 服務端會為遠程客戶端創(chuàng)建一個 redisClient 作為代理。

redis 會讀取嵌套字中的數(shù)據(jù),寫入 querybuf 中。

解析 querybuf 中的命令,記錄到 argc 和 argv 中。

根據(jù) argv[0] 查找對應的 recommand。

執(zhí)行 recommend 對應的執(zhí)行函數(shù)。

執(zhí)行以后將結(jié)果存入 buf bufpos reply 中。

返回給調(diào)用方。返回數(shù)據(jù)的時候,會控制寫入數(shù)據(jù)量的大小,如果過大會分成若干次。保證 redis 的相應時間。

Redis 作為單線程應用,一直貫徹的思想就是,每個步驟的執(zhí)行都有一個上限(包括執(zhí)行時間的上限或者文件尺寸的上限)一旦達到上限,就會記錄下當前的執(zhí)行進度,下次再執(zhí)行。保證了 Redis 能夠及時響應不發(fā)生阻塞。

關(guān)于“Redis 中命令執(zhí)行過程的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

正文完
 
丸趣
版權(quán)聲明:本站原創(chuàng)文章,由 丸趣 2023-08-04發(fā)表,共計5425字。
轉(zhuǎn)載說明:除特殊說明外本站除技術(shù)相關(guān)以外文章皆由網(wǎng)絡(luò)搜集發(fā)布,轉(zhuǎn)載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 昌都县| 克拉玛依市| 满洲里市| 商河县| 依安县| 西宁市| 澄江县| 额尔古纳市| 且末县| 玉环县| 光山县| 卢氏县| 九江市| 开平市| 黔西| 界首市| 南江县| 文成县| 新民市| 台安县| 永和县| 津市市| 曲阜市| 章丘市| 娄底市| 吐鲁番市| 米林县| 当涂县| 和硕县| 会宁县| 承德市| 巴林右旗| 克拉玛依市| 襄樊市| 霍邱县| 泰来县| 中宁县| 泸水县| 邻水| 翁源县| 敦化市|