本篇內(nèi)容介紹了“redis命令處理流程處理過(guò)程是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
坡頭ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場(chǎng)景,ssl證書未來(lái)市場(chǎng)廣闊!成為成都創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場(chǎng)價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18980820575(備注:SSL證書合作)期待與您的合作!
分析版本:REdis-5.0.4。
REdis命令處理流程可分解成三個(gè)獨(dú)立的流程(不包括復(fù)制和持久化):
接受連接請(qǐng)求流程;
接收請(qǐng)求數(shù)據(jù)和處理請(qǐng)求流程,在這個(gè)過(guò)程并不會(huì)發(fā)送處理結(jié)果給Client,而只是將結(jié)果數(shù)據(jù)寫入響應(yīng)緩沖,將由響應(yīng)請(qǐng)求流程來(lái)發(fā)送;
響應(yīng)請(qǐng)求流程。
上述三個(gè)流程均是異步化的,并且沒有直接的聯(lián)系。它們的共同點(diǎn)均是通過(guò)REdis的簡(jiǎn)單事件驅(qū)動(dòng)(AE,A simple event-driven)觸發(fā),對(duì)于Linux實(shí)際是epoll的包裝,對(duì)于macOS為evport的包裝,對(duì)于FreeBSD對(duì)kqueue的包裝,對(duì)于其它則是select的包裝。
可以把a(bǔ)e.h/ae.c看成是抽象基類,而ae_epoll.c、ae_select.c、ae_evport.c、ae_kqueue.c看成是ea的具體實(shí)現(xiàn),以面向?qū)ο髞?lái)看,大致如下圖所示:
從上圖可以看出,當(dāng)沒有任何數(shù)據(jù)時(shí),進(jìn)程將阻塞在函數(shù)aeApiPoll(對(duì)于epoll實(shí)際為epoll_wait)處直接超時(shí)。
如果有連接請(qǐng)求進(jìn)來(lái),或者有連接發(fā)送數(shù)據(jù)過(guò)來(lái),或者有響應(yīng)數(shù)據(jù)還未發(fā)送完成(連接變成可寫),aeApiPoll均會(huì)立即從阻塞狀態(tài)返回。
注意,只有fd被塞進(jìn)了epoll,并沒有將client或aeFileEvent塞入epoll。因此當(dāng)一個(gè)連接被激活(比如有數(shù)據(jù)需要接收)時(shí),需要通過(guò)fd來(lái)查找到aeFileEvent,而client因?yàn)樵趧?chuàng)建aeFileEvent時(shí)就被賦值給了aeFileEvent的clientData,因此只需要找到aeFileEvent即可。
全局對(duì)象server(類型為redisServer,定義在server.h中)維護(hù)了一個(gè)全局的aeEventLoop在,則aeEventLoop維護(hù)了一個(gè)aeFileEvent數(shù)組,并且aeFileEvent的數(shù)組下標(biāo)為fd,因此很容易通過(guò)fd找到對(duì)應(yīng)的aeFileEvent。
之所以沒有將aeFileEvent直接注入到epoll,是為了統(tǒng)一事件驅(qū)動(dòng),比如select就不支持。在進(jìn)程啟動(dòng)執(zhí)行initServer時(shí),會(huì)調(diào)用aeCreateEventLoop初始化該數(shù)組,數(shù)組大小大于配置項(xiàng)maxclients指定的值(額外加128),這利用了fd作為操作系統(tǒng)內(nèi)核資源是循環(huán)利用的特性。
1. 接受連接請(qǐng)求流程
接受一個(gè)連接后,為該連接創(chuàng)建一個(gè)client對(duì)象,并將該client注冊(cè)到epoll中,注冊(cè)事件為EPLLIN(對(duì)應(yīng)于ea的AE_READABLE)。
對(duì)應(yīng)的偽代碼:
int main() { // “ae”為“A simple event-driven”的縮寫 void aeMain() { while (!eventLoop->stop) { // 響應(yīng)從beforesleep開始, // 未完成部分才會(huì)走到aeApiPoll。 if (eventLoop->beforesleep != NULL) eventLoop->beforesleep(eventLoop); // aeProcessEvents處理各種事件,包括: // 1) 接受連接請(qǐng)求,為每個(gè)連接創(chuàng)建一個(gè)client // 2) 接收請(qǐng)求數(shù)據(jù),和處理請(qǐng)求 // 3) 發(fā)送響應(yīng)數(shù)據(jù) // 4) 處理各類定時(shí)事件(調(diào)用processTimeEvents) int aeProcessEvents() { // aeApiPoll實(shí)為epoll或select等 aeApiPoll(); acceptTcpHandler(int fd) { // fd為listen套接字 // anetTcpAccept底層調(diào)用的是accept int cfd = anetTcpAccept(fd); acceptCommonHandler(cfd) { // createClient會(huì)將c添加server.clients中, // server.clients是一個(gè)鏈接。 client *c = createClient(cfd) { aeCreateFileEvent( server.el,fd,AE_READABLE, readQueryFromClient, c) { // mask值為AE_READABLE(對(duì)應(yīng)于epoll的EPOLLIN), // 對(duì)于epoll實(shí)際調(diào)用的是epoll_ctl。 aeApiAddEvent(eventLoop,fd,mask); } } } } } } } }
2. 接收請(qǐng)求數(shù)據(jù)和處理請(qǐng)求流程
這一塊會(huì)調(diào)用相應(yīng)命令的處理函數(shù),比如SET命令的處理函數(shù)setCommand,GET命令的處理函數(shù)getCommand。命令處理函數(shù)會(huì)修改內(nèi)存數(shù)據(jù)。
并將處理的結(jié)果寫入響應(yīng)緩沖區(qū),但并不立即發(fā)送給client。同時(shí)也會(huì)將處理結(jié)果寫入AOF緩沖區(qū),如果開啟了AOF。以及將命令寫入到復(fù)制積壓緩沖區(qū),如果有開啟或有需要。還會(huì)將命令寫入到slaves的緩沖區(qū),如果需要。
響應(yīng)請(qǐng)求在另一獨(dú)立的流程中進(jìn)行,本流程并不直接發(fā)送響應(yīng)給client。
對(duì)應(yīng)的偽代碼
// 不包括響應(yīng)命令,// 響應(yīng)和接收處理是分開的兩個(gè)過(guò)程。 int main() // server.c:4003{ // “ae”為“A simple event-driven”的縮寫 void aeMain() // ae.c:496 {while (!eventLoop->stop) { // 發(fā)送響應(yīng)先在beforesleep中進(jìn)行, // 如果在beforesleep中沒有發(fā)送完(比如響應(yīng)的數(shù)據(jù)量過(guò)大), // 則后續(xù)的發(fā)送會(huì)由aeApiPoll觸發(fā)。 if (eventLoop->beforesleep != NULL) eventLoop->beforesleep(eventLoop); int aeProcessEvents() // ae.c:358 {// aeApiPoll實(shí)為epoll或select等 aeApiPoll(); // readQueryFromClient是個(gè)回調(diào)函數(shù),// 在創(chuàng)建client時(shí)注冊(cè):// client *createClient(int fd) {// aeCreateFileEvent(// server.el, fd,// AE_READABLE,// readQueryFromClient, c);// } void readQueryFromClient() // networking.c:1494{ // 這里調(diào)用read收數(shù)據(jù) // client傳過(guò)來(lái)的數(shù)據(jù)大小不能超過(guò)配置項(xiàng)client-query-buffer-limit指定的值。 // 默認(rèn)大小為1G,足夠覆蓋大部場(chǎng)景。 // 如果超過(guò)大小,則可看到WARNING日志: // Closing client that reached max query buffer length // 實(shí)際中,一般遠(yuǎn)小于1G,所以可能將這個(gè)值調(diào)小一點(diǎn),以增加對(duì)REdis的保護(hù)。 int nread = read(fd, c->querybuf, readlen); int processCommand(client*) // networking.c:2543 { redisCommand* lookupCommand(name) { // REdis所有命令存儲(chǔ) // 在struct redisServer的command表中: // struct redisServer { // dict *commands; // Command table // }; // 可將redisCommand看作一個(gè)C++抽象基本, // 該抽象基本定義了純虛函數(shù)proc: // typedef void redisCommandProc(client *c); // struct redisCommand { // redisCommandProc *proc; // }; // 而command表中的每一個(gè)成員則為redisCommand的實(shí)現(xiàn)。 return dictFetchValue(commands,name); } void call(client*,flags) // server.c:2414{ // 回調(diào)具體的命令處理: // 如果是SET命令, // 實(shí)際調(diào)用的是t_string.c中的函數(shù)setCommand; // 如果是DEL命令, // 實(shí)際調(diào)用的是db.c中的函數(shù)delCommand。 redisCommand::proc(client*); void propagate(redisCommand*) // server.c:2315 {// 數(shù)據(jù)寫入到AOF文件 feedAppendOnlyFile(); // aof.c:555 // 數(shù)據(jù)復(fù)制給所有Slaves void replicationFeedSlaves(slaves) // replication.c:173{ // 數(shù)據(jù)寫入到復(fù)制積壓(Backlog)緩沖區(qū), // 注意積壓緩沖區(qū)是一個(gè)循環(huán)緩沖區(qū), // 如果滿了,則從頭覆蓋寫, // 循環(huán)緩沖區(qū)的大小, // 則配置項(xiàng)repl-backlog-size決定 feedReplicationBacklog(); // replication.c:126} } } } } } } } } // 以GET命令為列:// 這里的list實(shí)際為server.clients_pending_write// 所以需響應(yīng)的client都添加到server.clients_pending_write鏈表中(可視為隊(duì)列)// struct redisServer server; // Server global state#0 listAddNodeHead (list=0x7fe88bc0f210, value=0x7fe88bc64ec0) at adlist.c:92// 并不是所有的命令都需要WriteHandler,// 因此有些并不會(huì)調(diào)用clientInstallWriteHandler。#1 in clientInstallWriteHandler (c=0x7fe88bc64ec0) at networking.c:185#2 in prepareClientToWrite (c=0x7fe88bc64ec0) at networking.c:228#3 in addReplyString (c=0x7fe88bc64ec0, s=0x7ffdfc2e70c0 "$855\r\n", len=6) at networking.c:338#4 in addReplyLongLongWithPrefix (c=0x7fe88bc64ec0, ll=855, prefix=36 '$') at networking.c:515#5 in addReplyBulkLen (c=0x7fe88bc64ec0, obj=0x7fe889312840) at networking.c:557#6 in addReplyBulk (c=0x7fe88bc64ec0, obj=0x7fe889312840) at networking.c:562#7 in getGenericCommand (c=0x7fe88bc64ec0) at t_string.c:167#8 in getCommand (c=0x7fe88bc64ec0) at t_string.c:173#9 in call (c=0x7fe88bc64ec0, flags=15) at server.c:2437#10 in processCommand (c=0x7fe88bc64ec0) at server.c:2729#11 in processInputBuffer (c=0x7fe88bc64ec0) at networking.c:1451#12 in processInputBufferAndReplicate (c=0x7fe88bc64ec0) at networking.c:1486#13 in readQueryFromClient (el=0x7fe88bc30050, fd=8, privdata=0x7fe88bc64ec0, mask=1) at networking.c:1568#14 in aeProcessEvents (eventLoop=0x7fe88bc30050, flags=11) at ae.c:443#15 in aeMain (eventLoop=0x7fe88bc30050) at ae.c:501#16 in main (argc=2, argv=0x7ffdfc2e75b8) at server.c:4197
3. 響應(yīng)請(qǐng)求流程
對(duì)于每一個(gè)有響應(yīng)的命令,它的響應(yīng)總是首先在beforesleep中進(jìn)行,但如果一次沒能發(fā)送完成,則會(huì)交給sendReplyToClient后續(xù)異步處理(以epoll為例,通過(guò)注冊(cè)epoll的EPOLLOUT事件)。
對(duì)應(yīng)的偽代碼:
// 響應(yīng)和接收處理是分開的兩個(gè)過(guò)程。 int main() { // “ae”為“A simple event-driven”的縮寫 void aeMain() { while (!eventLoop->stop) { // 調(diào)用eventLoop->beforesleep(eventLoop); // 但實(shí)際調(diào)用的是server.c中的beforeSleep: void beforeSleep(struct aeEventLoop*) { int handleClientsWithPendingWrites() { // REdis接收和處理 // 命令流程會(huì)設(shè)置clients_pending_write, // clients_pending_write實(shí)為一個(gè)隊(duì)列鏈接。 // 當(dāng)處理完一個(gè)命令后,調(diào)用clientInstallWriteHandler // 將當(dāng)前client添加到clients_pending_write中。 // 但是有些命令并不需要響應(yīng),因此沒有這個(gè)動(dòng)作。 listRewind(server.clients_pending_write,&li); while((ln = listNext(&li))) { int writeToClient(int fd,client* c) { write(fd,c->buf); // 如果全部發(fā)送完了, // 則調(diào)用aeDeleteFileEvent // 將fd從epoll中移除。 if (!clientHasPendingReplies(c)) { aeDeleteFileEvent( server.el, c->fd, AE_WRITABLE); // 從epoll中刪除EPOLLOUT } } // 如果一次writeToClient調(diào)用沒有發(fā)完, // 則將fd注冊(cè)到epoll if (clientHasPendingReplies(c)) { // 下列動(dòng)作是設(shè)置epoll的EPOLLOUT int ae_flags = AE_WRITABLE; // 將EPOLLOUT添加到epoll中 aeCreateFileEvent( server.el, c->fd, ae_flags, sendReplyToClient, c); } } } } // REdis接收和處理一個(gè)命令流程 aeProcessEvents(); } } }
“REdis命令處理流程處理過(guò)程是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!