1. 程式人生 > >《Redis官方文件》Redis事件庫

《Redis官方文件》Redis事件庫

Redis實現了自己的事件庫,程式碼在ae.c中。想要理解Redis事件庫的工作原理,最好的方法就是去理解Redis如何使用它。

事件迴圈初始化

redis.c中的initServer函式初始化了redisServer結構體變數的眾多成員,其中一個就是Redis事件迴圈(event loop)el

aeEventLoop *el

initServer呼叫aeCreateEventLoop(定義在ae.c)初始化server.el的成員。aeEventLoop的定義如下:

typedef struct aeEventLoop
{
    int maxfd;
    long long timeEventNextId;
    aeFileEvent events[AE_SETSIZE]; /* 已經註冊的事件 */
    aeFiredEvent fired[AE_SETSIZE]; /* 已經就緒的事件 */
    aeTimeEvent *timeEventHead;
    int stop;
    void *apidata; /* 這是polling API使用的專有資料 */
    aeBeforeSleepProc *beforesleep;
} aeEventLoop;

aeCreateEventLoop

aeCreateEventLoop首先為aeEventLoop結構體分配記憶體,然後呼叫ae_epoll.c:aeApiCreate
aeApiCreate分配aeApiState的空間,它有兩個成員:epfd儲存epoll_create呼叫返回的epoll檔案描述符,events是Linux epoll庫中定義的epoll_event結構型別。後面會再介紹events的使用。
接下來是ae.c:aeCreateTimeEvent,但是在那之前,initServer會先呼叫anet.c:anetTcpServer建立一個監聽描述符(listening descriptor)

,預設監聽6379埠。返回的監聽描述符儲存在server.fd

aeCreateTimeEvent

aeCreateTimeEvent接收如下引數:

  • eventLoop:即redis.c中的 server.el
  • milliseconds:從當前時間開始距離定時器過期的毫秒數。
  • proc:函式指標,儲存了定時器過期後呼叫的函式地址。
  • clientData: 通常是NULL
  • finalizerProc:指向定時事件被移除前要呼叫的函式。

initServer呼叫aeCreateTimeEventserver.el中的timeEventHead成員新增一個定時事件,timeEventHead

是指向定時事件連結串列的指標。如下是 redis.c:initServer函式中呼叫aeCreateTimeEvent的程式碼。

aeCreateTimeEvent(server.el /*eventLoop*/, 1 /*milliseconds*/, serverCron /*proc*/, NULL /*clientData*/, NULL /*finalizerProc*/);

redis.c:serverCron執行很多後臺操作來保持Redis正常運轉。

aeCreateFileEvent

aeCreateFileEvent函式實質就是執行epoll_ctl系統呼叫,以將anetTcpServer建立的監聽描述符增加到EPOLLIN事件佇列,並將它和aeCreateEventLoop建立的epoll描述符相關聯。
下面解釋了 redis.c:initServer中呼叫aeCreateFileEvent具體做的工作。
initServer傳遞了如下引數給aeCreateFileEvent

  • server.elaeCreateEventLoop建立的事件迴圈,epoll描述符是從server.el裡獲取的。
  • server.fd監聽描述符,作為從eventLoop->events中獲取相關檔案事件結構體的索引,結構體中儲存了回撥函式等資訊。
  • AE_READABLE:表示必須監視server.fdEPOLLIN事件。
  • acceptHandler:當被監聽的事件就緒時執行的函式,函式指標儲存在eventLoop->events[server.fd]->rfileProc

以上完成了Redis事件迴圈的初始化。

事件迴圈的處理

redis.c:main通過呼叫ae.c:aeMain來處理前一階段初始化好的事件迴圈。
ae.c:aeMain在一個while迴圈中呼叫ae.c:aeProcessEvents來處理就緒的定時事件和檔案事件。

aeProcessEvents

ae.c:aeProcessEvents在事件迴圈上呼叫ae.c:aeSearchNearestTimer尋找最先要過期的定時事件。我們的示例中,事件迴圈裡只有ae.c:aeCreateTimeEvent建立的一個定時事件(譯者注:即前面呼叫ae.c:aeCreateTimeEvent使用的回撥redis.c:serverCron
請記住,aeCreateTimeEvent建立的定時事件很可能已經過期了,因為過期時間只有1毫秒。定時器過期後,timeval結構體的tvp變數會把成員變數秒和毫秒都重置為0。
tvp結構體變數和事件迴圈變數作為引數傳給了ae_epoll.c:aeApiPoll
aeApiPoll函式在epoll描述符上呼叫了epoll_wait,然後用下面內容填充 eventLoop->fired陣列。

  • fd:準備好做讀/寫操作的描述符,操作型別取決於mask值。
  • mask:標識讀/寫事件可以在對應描述符上執行。(譯者注:有讀事件mask |= AE_READABLE,有寫事件mask |= AE_WRITABLE

aeApiPoll返回已就緒事件的個數。來看個實際的例子,假設有客戶端發起了連線請求,那麼aeApiPoll將會注意到,使用監聽描述符填充eventLoop->fired陣列的描述符成員,把mask設為AE_READABLE
現在,aeProcessEvents呼叫redis.c:acceptHandler回撥函式。acceptHandler監聽描述符上執行accept,返回一個客戶端連線描述符redis.c:createClient通過下面這樣呼叫ae.c:aeCreateFileEvent,向連線描述符新增一個檔案事件。

if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
    readQueryFromClient, c) == AE_ERR) {
    freeClient(c);
    return NULL;
}

credisClient結構體變數,c->fd是連線描述符。
然後,ae.c:aeProcessEvent呼叫ae.c:processTimeEvents

processTimeEvents

ae.processTimeEventseventLoop->timeEventHead開始,依次遍歷連結串列上的定時事件。
對每個過期的定時事件,processTimeEvents呼叫相應的回撥函式。這個示例只會呼叫唯一註冊的定時事件回撥函式redis.c:serverCron,回撥函式返回的時間表示多少毫秒後它將被再次呼叫。返回的時間被ae.c:aeAddMilliSeconds記錄下來,ae.c:aeMain中的while迴圈會在下次迭代中繼續處理定時事件。
就這些了。