成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

跟著大彬讀源碼 - Redis 4 - 服務(wù)器的事件驅(qū)動有什么含義?

姘存按 / 2793人閱讀

摘要:服務(wù)器會為執(zhí)行不同任務(wù)的套接字關(guān)聯(lián)不同的事件處理器。當(dāng)服務(wù)器套接字變得可寫時(shí),套接字會產(chǎn)生事件。多路復(fù)用程序允許服務(wù)器同時(shí)監(jiān)聽套接字的事件和事件。

眾所周知,Redis 服務(wù)器是一個(gè)事件驅(qū)動程序。那么事件驅(qū)動對于 Redis 而言有什么含義?源碼中又是如何實(shí)現(xiàn)事件驅(qū)動的呢?今天,我們一起來認(rèn)識下 Redis 服務(wù)器的事件驅(qū)動。

對于 Redis 而言,服務(wù)器需要處理以下兩類事件:

文件事件(file event):Redis 服務(wù)器通過套接字與客戶端進(jìn)行連接,而文件事件就是服務(wù)器對套接字操作的抽象。服務(wù)器與客戶端的通信會產(chǎn)生相應(yīng)的文件事件,而服務(wù)器則通過監(jiān)聽并處理這些事件來完成一系列的網(wǎng)絡(luò)通信操作。

時(shí)間時(shí)間(time event):Redis 服務(wù)器中的一些操作(比如 serverCron 函數(shù))需要在給定的時(shí)間點(diǎn)執(zhí)行,而時(shí)間事件就是服務(wù)器對這類定時(shí)操作的抽象。

接下來,我們先來認(rèn)識下文件事件。

1 文件事件

Redis 基于 Reactor 模式開發(fā)了自己的網(wǎng)絡(luò)事件處理器,這個(gè)處理器被稱為文件事件處理器(file event handler)

文件事件處理器使用 IO 多路復(fù)用程序來同時(shí)監(jiān)聽多個(gè)套接字,并根據(jù)套接字目前執(zhí)行的任務(wù)來為套接字關(guān)聯(lián)不同的事件處理器。

當(dāng)被監(jiān)聽的套接字準(zhǔn)備好執(zhí)行連接應(yīng)答(accept)、讀?。╮ead)、寫入(write)、關(guān)閉(close)等操作時(shí),與操作相對應(yīng)的文件事件就會產(chǎn)生,這時(shí)文件事件處理器就會調(diào)用套接字之前關(guān)聯(lián)好的事件處理器來處理這些事件。

雖然文件處理器以單線程方式運(yùn)行,但通過 IO 多路復(fù)用程序監(jiān)聽多個(gè)套接字,既實(shí)現(xiàn)了高性能的網(wǎng)絡(luò)通信模型,又可以很好的與 Redis 服務(wù)器中其它同樣以單線程運(yùn)行的模塊進(jìn)行對接,保持了 Redis 內(nèi)部單線程設(shè)計(jì)的簡潔。

1.1 文件事件處理器的構(gòu)成

圖 1 展示了文件事件處理器的四個(gè)組成部分:

套接字;

IO 多路復(fù)用程序;

文件事件分派器(dispatcher);

事件處理器;

文件事件是對套接字的抽象。每當(dāng)一個(gè)套接字準(zhǔn)備好執(zhí)行連接應(yīng)答(accept)、寫入、讀取、關(guān)閉等操作時(shí),就好產(chǎn)生一個(gè)文件事件。因?yàn)橐粋€(gè)服務(wù)器通常會連接多個(gè)套接字,所以多個(gè)文件事件有可能會并發(fā)的出現(xiàn)。

而 IO 多了復(fù)用程序負(fù)責(zé)監(jiān)聽多個(gè)套接字,并向文件事件分派器分發(fā)那些產(chǎn)生事件的套接字。

盡管多個(gè)文件事件可能會并發(fā)的出現(xiàn),但 IO 多路復(fù)用程序總是會將所有產(chǎn)生事件的套接字都放到一個(gè)隊(duì)列里面,然后通過這個(gè)隊(duì)列,以有序、同步的方式,把每一個(gè)套接字傳輸給文件事件分派器。當(dāng)上一個(gè)套接字產(chǎn)生的事件被處理完畢之后(即,該套接字為事件所關(guān)聯(lián)的事件處理器執(zhí)行完畢),IO 多路復(fù)用程序才會繼續(xù)向文件事件分派器傳送下一個(gè)套接字。如圖 2 所示:

文件事件分派器接收 IO 多路復(fù)用程序傳來的套接字,并根據(jù)套接字產(chǎn)生的事件類型,調(diào)用相應(yīng)的事件處理器。

服務(wù)器會為執(zhí)行不同任務(wù)的套接字關(guān)聯(lián)不同的事件處理器。這些處理器本質(zhì)上就是一個(gè)個(gè)函數(shù)。它們定義了某個(gè)事件發(fā)生時(shí),服務(wù)器應(yīng)該執(zhí)行的動作。

1.2 IO 多路復(fù)用程序的實(shí)現(xiàn)

Redis 的 IO 多路復(fù)用程序的所有功能都是通過包裝常見的 select、epoll、evport 和 kqueue 這些 IO 多路復(fù)用函數(shù)庫來實(shí)現(xiàn)的。每個(gè) IO 多路復(fù)用函數(shù)庫在 Redis 源碼中都對應(yīng)一個(gè)多帶帶的文件,比如 ae_select.c、ae_poll.c、ae_kqueue.c 等。

由于 Redis 為每個(gè) IO 多路復(fù)用函數(shù)庫都實(shí)現(xiàn)了相同的 API,所以 IO 多路復(fù)用程序的底層實(shí)現(xiàn)是可以互換的,如圖 3 所示:

Redis 在 IO 多路復(fù)用程序的實(shí)現(xiàn)源碼中用 #include 宏定義了相應(yīng)的規(guī)則,**程序會在編譯時(shí)自動選擇系統(tǒng)中性能最高的 IO 多路復(fù)用函數(shù)庫來作為 Redis 的 IO 多路復(fù)用程序的底層實(shí)現(xiàn),這保證了 Redis 在各個(gè)平臺的兼容性和高性能。對應(yīng)源碼如下:

/* Include the best multiplexing layer supported by this system.
 * The following should be ordered by performances, descending. */
#ifdef HAVE_EVPORT
#include "ae_evport.c"
#else
    #ifdef HAVE_EPOLL
    #include "ae_epoll.c"
    #else
        #ifdef HAVE_KQUEUE
        #include "ae_kqueue.c"
        #else
        #include "ae_select.c"
        #endif
    #endif
#endif
1.3 事件的類型

IO 多路復(fù)用程序可以監(jiān)聽多個(gè)套接字的 ae.h/AE_READABLEae.h/AE_WRITABLE 事件,這兩類事件和套接字操作之間有以下對應(yīng)關(guān)系:

當(dāng)服務(wù)器套接字變得可讀時(shí),套接字會產(chǎn)生 AE_READABLE 事件。此處的套接字可讀,是指客戶端對套接字執(zhí)行 write、close 操作,或者有新的可應(yīng)答(acceptable)套接字出現(xiàn)時(shí)(客戶端對服務(wù)器的監(jiān)聽套接字執(zhí)行 connect 操作),套接字會產(chǎn)生 AE_READABLE 事件。

當(dāng)服務(wù)器套接字變得可寫時(shí),套接字會產(chǎn)生 AE_WRITABLE 事件。

IO 多路復(fù)用程序允許服務(wù)器同時(shí)監(jiān)聽套接字的 AR_READABLE 事件和 AE_WRITABLE 事件。如果一個(gè)套接字同時(shí)產(chǎn)生了兩個(gè)事件,那么文件分派器會優(yōu)先處理 AE_READABLE 事件,然后再處理 AE_WRITABLE 事件。簡單來說,如果一個(gè)套接字既可讀又可寫,那么服務(wù)器將先讀套接字,后寫套接字。

1.4 文件事件處理器

Redis 為文件事件編寫了多個(gè)處理器,這些事件處理器分別用于實(shí)現(xiàn)不同的網(wǎng)絡(luò)通信需求。比如說:

為了對連接服務(wù)器的各個(gè)客戶端進(jìn)行應(yīng)答,服務(wù)器要為監(jiān)聽套接字關(guān)聯(lián)連接應(yīng)答處理器。

為了接收客戶端傳了的命令請求,服務(wù)器要為客戶端套接字關(guān)聯(lián)命令請求處理器。

為了向客戶端返回命令執(zhí)行結(jié)果,服務(wù)器要為客戶端套接字關(guān)聯(lián)命令回復(fù)處理器。

當(dāng)主服務(wù)器和從服務(wù)器進(jìn)行復(fù)制操作時(shí),主從服務(wù)器都需要關(guān)聯(lián)復(fù)制處理器。

在這些事件處理器中,服務(wù)器最常用的是與客戶端進(jìn)行通信的連接應(yīng)答處理器、命令請求處理器和命令回復(fù)處理器。

1)連接應(yīng)答處理器

networking.c/acceptTcpHandle 函數(shù)是 Redis 的連接應(yīng)答處理器,這個(gè)處理器用于對連接服務(wù)器監(jiān)聽套接字的客戶端進(jìn)行應(yīng)答,具體實(shí)現(xiàn)為 sys/socket.h/accept 函數(shù)的包裝。

當(dāng) Redis 服務(wù)器進(jìn)行初始化的時(shí)候,程序會將這個(gè)連接應(yīng)答處理器和服務(wù)器監(jiān)聽套接字的 AE_READABLE 事件關(guān)聯(lián)。對應(yīng)源碼如下

# server.c/initServer
...
/* Create an event handler for accepting new connections in TCP and Unix
 * domain sockets. */
for (j = 0; j < server.ipfd_count; j++) {
    if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
        acceptTcpHandler,NULL) == AE_ERR)
        {
            serverPanic(
                "Unrecoverable error creating server.ipfd file event.");
        }
}
...

當(dāng)有客戶端用 sys/scoket.h/connect 函數(shù)連接服務(wù)器監(jiān)聽套接字時(shí),套接字就會產(chǎn)生 AE_READABLE 事件,引發(fā)連接應(yīng)答處理器執(zhí)行,并執(zhí)行相應(yīng)的套接字應(yīng)答操作。如圖 4 所示:

2)命令請求處理器
networking.c/readQueryFromClient 函數(shù)是 Redis 的命令請求處理器,這個(gè)處理器負(fù)責(zé)從套接字中讀入客戶端發(fā)送的命令請求內(nèi)容,具體實(shí)現(xiàn)為 unistd.h/read 函數(shù)的包裝。

當(dāng)一個(gè)客戶端通過連接應(yīng)答處理器成功連接到服務(wù)器之后,服務(wù)器會將客戶端套接字的 AE_READABLE 事件和命令請求處理器關(guān)聯(lián)起來(networking.c/acceptCommonHandler 函數(shù))。

當(dāng)客戶端向服務(wù)器發(fā)送命令請求的時(shí)候,套接字就會產(chǎn)生 AR_READABLE 事件,引發(fā)命令請求處理器執(zhí)行,并執(zhí)行相應(yīng)的套接字讀入操作,如圖 5 所示:

在客戶端連接服務(wù)器的整個(gè)過程中,服務(wù)器都會一直為客戶端套接字的 AE_READABLE 事件關(guān)聯(lián)命令請求處理器。

3)命令回復(fù)處理器
networking.c/sendReplToClient 函數(shù)是 Redis 的命令回復(fù)處理器,這個(gè)處理器負(fù)責(zé)將服務(wù)器執(zhí)行命令后得到的命令回復(fù)通過套接字返回給客戶端。

當(dāng)服務(wù)器有命令回復(fù)需要發(fā)給客戶端時(shí),服務(wù)器會將客戶端套接字的 AE_WRITABLE 事件和命令回復(fù)處理器關(guān)聯(lián)(networking.c/handleClientsWithPendingWrites 函數(shù))。

當(dāng)客戶端準(zhǔn)備好接收服務(wù)器傳回的命令回復(fù)時(shí),就會產(chǎn)生 AE_WRITABLE 事件,引發(fā)命令回復(fù)處理器執(zhí)行,并執(zhí)行相應(yīng)的套接字寫入操作。如圖 6 所示:

當(dāng)命令回復(fù)發(fā)送完畢之后,服務(wù)器就會解除命令回復(fù)處理器與客戶端套接字的 AE_WRITABLE 事件的關(guān)聯(lián)。對應(yīng)源碼如下:

# networking.c/writeToClient
...
if (!clientHasPendingReplies(c)) {
    c->sentlen = 0;
    # buffer 緩沖區(qū)命令回復(fù)已發(fā)送,刪除套接字和事件的關(guān)聯(lián)
    if (handler_installed) aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);

    /* Close connection after entire reply has been sent. */
    if (c->flags & CLIENT_CLOSE_AFTER_REPLY) {
        freeClient(c);
        return C_ERR;
    }
}
...
1.5 客戶端與服務(wù)器連接事件

之前我們通過 debug 的形式大致認(rèn)識了客戶端與服務(wù)器的連接過程?,F(xiàn)在,我們站在文件事件的角度,再一次來追蹤 Redis 客戶端與服務(wù)器進(jìn)行連接并發(fā)送命令的整個(gè)過程,看看在過程中會產(chǎn)生什么事件,這些事件又是如何被處理的。

先來看客戶端與服務(wù)器建立連接的過程:

先啟動我們的 Redis 服務(wù)器(127.0.0.1-8379)。成功啟動后,服務(wù)器套接字(127.0.0.1-8379) AE_READABLE 事件正處于被監(jiān)聽狀態(tài),而該事件對應(yīng)連接應(yīng)答處理器。(server.c/initServer())。

使用 redis-cli 連接服務(wù)器。這是,服務(wù)器套接字(127.0.0.1-8379)將產(chǎn)生 AR_READABLE 事件,觸發(fā)連接應(yīng)答處理器執(zhí)行(networking.c/acceptTcpHandler())。

對客戶端的連接請求進(jìn)行應(yīng)答,創(chuàng)建客戶端套接字,保存客戶端狀態(tài)信息,并將客戶端套接字的 AE_READABLE 事件與命令請求處理器(networking.c/acceptCommonHandler())進(jìn)行關(guān)聯(lián),使得服務(wù)器可以接收該客戶端發(fā)來的命令請求。

此時(shí),客戶端已成功與服務(wù)器建立連接了。上述過程,我們?nèi)匀豢梢杂?gdb 調(diào)試,查看函數(shù)的執(zhí)行過程。具體調(diào)試過程如下:

gdb ./src/redis-server
(gdb) b acceptCommonHandler    # 給 acceptCommonHandler 函數(shù)設(shè)置斷點(diǎn)
(gdb) r redis-conf --port 8379 # 啟動服務(wù)器

另外開一個(gè)窗口,使用 redis-cli 連接服務(wù)器:redis-cli -p 8379

回到服務(wù)器窗口,我們會看到已進(jìn)入 gdb 調(diào)試模式,輸入:info stack,可以看到如圖 6 所示的堆棧信息。

現(xiàn)在,我們再來認(rèn)識命令的執(zhí)行過程:

客戶端向服務(wù)器發(fā)送一個(gè)命令請求,客戶端套接字產(chǎn)生 AE_READABLE 事件,引發(fā)命令請求處理器(readQueryFromClient)執(zhí)行,讀取客戶端的命令內(nèi)容;

根據(jù)客戶端發(fā)送命令內(nèi)容,格式化客戶端 argc、argv 等相關(guān)值屬性值;

根據(jù)命令名稱查找對應(yīng)函數(shù)。server.c/processCommad()lookupCommand 函數(shù)調(diào)用;

執(zhí)行與命令名關(guān)聯(lián)的函數(shù),獲得返回結(jié)果,客戶端套接字產(chǎn)生 。server.c/processCommad()call 函數(shù)調(diào)用。

返回命令回復(fù),刪除客戶端套接字與 AE_WRITABLE 事件的關(guān)聯(lián)。network.c/writeToClient() 函數(shù)。

圖 7 展示了命令執(zhí)行過程的堆棧信息。圖 8 則展示了命令回復(fù)過程的堆棧信息。

上一節(jié)我們一起認(rèn)識了文件事件。接下來,讓我們再來認(rèn)識下時(shí)間事件。

2 時(shí)間事件

Redis 的時(shí)間時(shí)間分為以下兩類:

定時(shí)時(shí)間:讓一段程序在指定的時(shí)間之后執(zhí)行一次。比如,讓程序 M 在當(dāng)前時(shí)間的 60 毫秒后執(zhí)行一次。

周期性事件:讓一段程序每隔指定時(shí)間就執(zhí)行一次。比如,讓程序 N 每隔 30 毫秒執(zhí)行一次。

對于時(shí)間事件,數(shù)據(jù)結(jié)構(gòu)源碼(ae.h/aeTimeEvent):

/* Time event structure */
typedef struct aeTimeEvent {
    long long id; /* time event identifier. */
    long when_sec; /* seconds */
    long when_ms; /* milliseconds */
    aeTimeProc *timeProc;
    aeEventFinalizerProc *finalizerProc;
    void *clientData;
    struct aeTimeEvent *next;
} aeTimeEvent;

主要屬性說明:

id:服務(wù)器為時(shí)間事件創(chuàng)建的全局唯一 ID。ID 號按從小到大的順序遞增。

when_sec:秒精度的 UNIX 時(shí)間戳,記錄了時(shí)間事件的到達(dá)時(shí)間。

when_ms:毫秒精度的 UNIX 時(shí)間戳,記錄了時(shí)間事件的到達(dá)時(shí)間。

timeProc:時(shí)間事件處理器,對應(yīng)一個(gè)函數(shù)。當(dāng)時(shí)間事件發(fā)生時(shí),服務(wù)器就會調(diào)用相應(yīng)的處理器來處理事件。

時(shí)間事件進(jìn)程執(zhí)行的函數(shù)為 ae.c/processTimeEvents()。

此外,對于時(shí)間事件的類型區(qū)分,取決于時(shí)間事件處理器的返回值:

返回值是 ae.h/AE_NOMORE,為定時(shí)事件。該事件在到達(dá)一次后就會被刪除;

返回值不是 ae.h/AE_NOMORE,為周期事件。當(dāng)一個(gè)周期時(shí)間事件到達(dá)后,服務(wù)器會根據(jù)事件處理器返回的值,對時(shí)間事件的 when_sec 和 when_ms 屬性進(jìn)行更新,讓這個(gè)事件在一段時(shí)間之后再次到達(dá),并以這種方式一致更新運(yùn)行。比如,如果一個(gè)時(shí)間事件處理器返回 30,那么服務(wù)器應(yīng)該對這個(gè)時(shí)間事件進(jìn)行更新,讓這個(gè)事件在 30 毫秒后再次執(zhí)行。

2.1 時(shí)間事件之 serverCron 函數(shù)

持續(xù)運(yùn)行的 Redis 服務(wù)器需要定期對自身的資源和狀態(tài)進(jìn)行檢查和調(diào)整,從而確保服務(wù)可以長期、穩(wěn)定的運(yùn)行。這些定期操作由 server.c/serverCron() 函數(shù)負(fù)責(zé)執(zhí)行。主要操作包括:

更新服務(wù)器的各類統(tǒng)計(jì)信息。比如時(shí)間、內(nèi)存占用、數(shù)據(jù)庫占用情況等。

清理數(shù)據(jù)庫中的過期鍵值對。

關(guān)閉和清理連接失效的客戶端。

嘗試進(jìn)行 AOF 或 RDB 持久化操作。

如果服務(wù)器是主服務(wù)器,對從 服務(wù)器進(jìn)行定期同步。

如果處于集群模式,對集群進(jìn)行定期同步和連接測試。

Redis 服務(wù)器以周期性事件的方式來運(yùn)行 serverCron 函數(shù),在服務(wù)器運(yùn)行期間,每隔一段時(shí)間,serverCron 就會執(zhí)行一次,直到服務(wù)器關(guān)閉為止。

關(guān)于執(zhí)行次數(shù),可參見 redis.conf 文件中的 hz 選項(xiàng)。默認(rèn)為 10,表示每秒運(yùn)行 10 次。

3 事件調(diào)度與執(zhí)行

由于服務(wù)器同時(shí)存在文件事件和時(shí)間事件,所以服務(wù)器必須對這兩種事件進(jìn)行調(diào)度,來決定何時(shí)處理文件事件,何時(shí)處理時(shí)間事件,以及花多少時(shí)間來處理它們等等。

事件的調(diào)度和執(zhí)行有 ae.c/aeProcessEvents() 函數(shù)負(fù)責(zé)。源碼如下:

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    int processed = 0, numevents;
    /* Nothing to do? return ASAP */
    if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;

    /* 首先判斷是否存在需要監(jiān)聽的文件事件,如果存在需要監(jiān)聽的文件事件,那么通過IO多路復(fù)用程序獲取
     * 準(zhǔn)備就緒的文件事件,至于IO多路復(fù)用程序是否等待以及等待多久的時(shí)間,依發(fā)生時(shí)間距離現(xiàn)在最近的時(shí)間事件確定;
     * 如果eventLoop->maxfd == -1表示沒有需要監(jiān)聽的文件事件,但是時(shí)間事件肯定是存在的(serverCron()),
     * 如果此時(shí)沒有設(shè)置 AE_DONT_WAIT 標(biāo)志位,此時(shí)調(diào)用IO多路復(fù)用,其目的不是為了監(jiān)聽文件事件是否準(zhǔn)備就緒,
     * 而是為了使線程休眠到發(fā)生時(shí)間距離現(xiàn)在最近的時(shí)間事件的發(fā)生時(shí)間(作用類似于unix中的sleep函數(shù)),
     * 這種休眠操作的目的是為了避免線程一直不停的遍歷時(shí)間事件形成的無序鏈表,造成不必要的資源浪費(fèi) */
    if (eventLoop->maxfd != -1 ||
        ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
        int j;
        aeTimeEvent *shortest = NULL;
        struct timeval tv, *tvp;

        /* 尋找發(fā)生時(shí)間距離現(xiàn)在最近的時(shí)間事件,該時(shí)間事件的發(fā)生時(shí)間與當(dāng)前時(shí)間之差就是IO多路復(fù)用程序應(yīng)該等待的時(shí)間 */
        if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
            shortest = aeSearchNearestTimer(eventLoop);
        if (shortest) {
            long now_sec, now_ms;

            // 創(chuàng)建 timeval 結(jié)構(gòu)
            aeGetTime(&now_sec, &now_ms);
            tvp = &tv;

            /* How many milliseconds we need to wait for the next
             * time event to fire? */
            long long ms =
                (shortest->when_sec - now_sec)*1000 +
                shortest->when_ms - now_ms;

            /* 如果時(shí)間之差大于0,說明時(shí)間事件到時(shí)時(shí)間未到,則等待對應(yīng)的時(shí)間;
             * 如果時(shí)間間隔小于0,說明時(shí)間事件已經(jīng)到時(shí),此時(shí)如果沒有
             * 文件事件準(zhǔn)備就緒,那么IO多路復(fù)用程序應(yīng)該立即返回,以免
             * 耽誤處理時(shí)間事件*/
            if (ms > 0) {
                tvp->tv_sec = ms/1000;
                tvp->tv_usec = (ms % 1000)*1000;
            } else {
                tvp->tv_sec = 0;
                tvp->tv_usec = 0;
            }
        } else {
            /* If we have to check for events but need to return
             * ASAP because of AE_DONT_WAIT we need to set the timeout
             * to zero */
            if (flags & AE_DONT_WAIT) {
                tv.tv_sec = tv.tv_usec = 0;
                tvp = &tv;
            } else {
                /* Otherwise we can block */
                tvp = NULL; /* wait forever */
            }
        }

        // 阻塞并等等文件事件產(chǎn)生,最大阻塞事件由 timeval 結(jié)構(gòu)決定
        numevents = aeApiPoll(eventLoop, tvp);
        for (j = 0; j < numevents; j++) {
            // 處理所有已產(chǎn)生的文件事件
            aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
            int mask = eventLoop->fired[j].mask;
            int fd = eventLoop->fired[j].fd;
            int fired = 0; /* Number of events fired for current fd. */
            int invert = fe->mask & AE_BARRIER;

            if (!invert && fe->mask & mask & AE_READABLE) {
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                fired++;
            }

            /* Fire the writable event. */
            if (fe->mask & mask & AE_WRITABLE) {
                if (!fired || fe->wfileProc != fe->rfileProc) {
                    fe->wfileProc(eventLoop,fd,fe->clientData,mask);
                    fired++;
                }
            }

            /* If we have to invert the call, fire the readable event now
             * after the writable one. */
            if (invert && fe->mask & mask & AE_READABLE) {
                if (!fired || fe->wfileProc != fe->rfileProc) {
                    fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                    fired++;
                }
            }

            processed++;
        }
    }
    /* Check time events */
    if (flags & AE_TIME_EVENTS)
        // 處理所有已到達(dá)的時(shí)間事件
        processed += processTimeEvents(eventLoop);

    return processed; /* return the number of processed file/time events */
}

aeProcessEvents 函數(shù)置于一個(gè)循環(huán)里面,加上初始化和清理函數(shù),就構(gòu)成了 Redis 服務(wù)器的主函數(shù) server.c/main()。以下是主函數(shù)的偽代碼:

def main():
    // 初始化服務(wù)器
    init_server();
    // 一直處理事件,直到服務(wù)器關(guān)閉為止
    while server_is_not_shutdown():
        aeProcessEvents();
    // 服務(wù)器關(guān)閉,執(zhí)行清理操作
    clear_server()

從事件處理的角度來看,Redis 服務(wù)器的運(yùn)行流程可以用流程圖 1 來概括:

以下是事件的調(diào)度和執(zhí)行規(guī)則:

aeApiPoll 函數(shù)的最大阻塞事件由到達(dá)時(shí)間最接近當(dāng)前時(shí)間的時(shí)間事件決定。這個(gè)方法既可以避免服務(wù)器對時(shí)間事件進(jìn)行頻繁的輪詢,也可以確保 aeApiPoll 函數(shù)不會阻塞過長時(shí)間。

因?yàn)槲募录请S機(jī)出現(xiàn)的,如果等待并處理完一次文件事件之后,仍未有任何時(shí)間事件到達(dá),那么服務(wù)器將再次等待并處理文件事件。隨著文件事件的不斷執(zhí)行,時(shí)間會逐漸向時(shí)間事件所設(shè)置的到達(dá)時(shí)間逼近,并最終來到,這時(shí)服務(wù)器就可以開始處理到達(dá)的時(shí)間事件了。

對文件事件和時(shí)間事件的處理都是同步、有序、原子地執(zhí)行。服務(wù)器不會中途中斷事件處理,也不會對事件進(jìn)行搶占。因此,不管是文件事件的處理器,還是時(shí)間事件的處理器,它們斗毆盡可能的減少程序的阻塞事件,并在有需要時(shí)主動讓出執(zhí)行權(quán),從而降低事件饑餓的可能性。舉個(gè)栗子,在命令回復(fù)處理器將一個(gè)命令回復(fù)寫入到客戶端套接字時(shí),如果寫入字節(jié)數(shù)超過了一個(gè)預(yù)設(shè)常量,命令回復(fù)處理器就會主動用 break 跳出寫入循環(huán),將余下的數(shù)據(jù)留到下次再寫。另外,時(shí)間事件也會將非常耗時(shí)的持久化操作放到子線程或者子進(jìn)程中執(zhí)行。

因?yàn)闀r(shí)間事件在文件事件之后執(zhí)行,并且事件之間不會出現(xiàn)搶占,所以時(shí)間事件的實(shí)際處理時(shí)間,通常會比時(shí)間事件設(shè)定的時(shí)間稍晚一些。

總結(jié)

Redis 服務(wù)器是一個(gè)事件驅(qū)動程序,服務(wù)器處理的事件分為時(shí)間事件文件事件兩類。

文件事件是對套接字操作的抽象。**每次套接字變得可應(yīng)答(acceptable)、可寫(writable)或者可讀(readable)時(shí),相應(yīng)的文件事件就會產(chǎn)生。

文件事件分為 AE_READABLE 事件(讀事件)和 AE_WRITABLE 事件(寫事件)兩類。

時(shí)間事件分為定時(shí)事件和周期事件。定時(shí)事件只在指定時(shí)間執(zhí)行一次,而周期事件則每隔指定時(shí)間執(zhí)行一次。

服務(wù)器一般情況下只執(zhí)行 serverCron 函數(shù)這一個(gè)周期性時(shí)間事件。

時(shí)間事件和文件事件之間是合作關(guān)系。服務(wù)器會輪流處理這兩種事件,并且處理事件的過程中不會進(jìn)行搶占。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/62141.html

相關(guān)文章

  • 跟著彬讀源碼 - Redis 1 - 啟動服務(wù),程序都干了什么?

    摘要:此時(shí)服務(wù)器處于休眠狀態(tài),并使用進(jìn)行事件輪詢,等待監(jiān)聽事件的發(fā)生。繼續(xù)執(zhí)行被調(diào)試程序,直至下一個(gè)斷點(diǎn)或程序結(jié)束縮寫。服務(wù)啟動包括初始化基礎(chǔ)配置數(shù)據(jù)結(jié)構(gòu)對外提供服務(wù)的準(zhǔn)備工作還原數(shù)據(jù)庫執(zhí)行事件循環(huán)等。 一直很羨慕那些能讀 Redis 源碼的童鞋,也一直想自己解讀一遍,但迫于 C 大魔王的壓力,解讀日期遙遙無期。 相信很多小伙伴應(yīng)該也都對或曾對源碼感興趣,但一來覺得自己不會 C 語言,二來也...

    sewerganger 評論0 收藏0
  • 跟著彬讀源碼 - Redis 5 - 對象和數(shù)據(jù)類型(上)

    摘要:對象源碼結(jié)構(gòu)如下對象類型對象編碼引用統(tǒng)計(jì)指向底層實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)的指針字段對象類型,就是我們常說的。。對象編碼對應(yīng)跳躍表壓縮列表集合動態(tài)字符串等八種底層數(shù)據(jù)結(jié)構(gòu)。 相信很多人應(yīng)該都知道 Redis 有五種數(shù)據(jù)類型:字符串、列表、哈希、集合和有序集合。但這五種數(shù)據(jù)類型是什么含義?Redis 的數(shù)據(jù)又是怎樣存儲的?今天我們一起來認(rèn)識下 Redis 這五種數(shù)據(jù)結(jié)構(gòu)的含義及其底層實(shí)現(xiàn)。 首先要明確...

    antz 評論0 收藏0

發(fā)表評論

0條評論

姘存按

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<