298
微軟
windows
《Redis官方文檔》Redis事件庫
Redis實現了自己的事件庫,代碼在ae.c
中。想要理解Redis事件庫的工作原理,最好的方法就是去理解Redis如何使用它。
事件循環初始化
redis.c
中的initServer
函數初始化了redisServer
結構體變量的眾多成員,其中一個就是Redis事件循環(event loop)el
:
initServer
調用aeCreateEventLoop
(定義在ae.c
)初始化server.el
的成員。aeEventLoop
的定義如下:
01 |
typedef struct aeEventLoop
|
04 |
long long timeEventNextId;
|
05 |
aeFileEvent events[AE_SETSIZE]; /* 已經注冊的事件 */
|
06 |
aeFiredEvent fired[AE_SETSIZE]; /* 已經就緒的事件 */
|
07 |
aeTimeEvent *timeEventHead;
|
09 |
void *apidata; /* 這是polling API使用的專有數據 */
|
10 |
aeBeforeSleepProc *beforesleep;
|
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
調用aeCreateTimeEvent
為server.el
中的timeEventHead
成員添加一個定時事件,timeEventHead
是指向定時事件鏈表的指針。如下是 redis.c:initServer
函數中調用aeCreateTimeEvent
的代碼。
1 |
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.el
:aeCreateEventLoop
創建的事件循環,epoll
描述符是從server.el
裏獲取的。
-
server.fd
:監聽描述符,作為從eventLoop->events
中獲取相關文件事件結構體的索引,結構體中存儲了回調函數等信息。
-
AE_READABLE
:表示必須監視server.fd
的EPOLLIN
事件。
-
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
,向連接描述符添加一個文件事件。
1 |
if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
|
2 |
readQueryFromClient, c) == AE_ERR) {
|
c
是redisClient
結構體變量,c->fd
是連接描述符。
然後,ae.c:aeProcessEvent
調用ae.c:processTimeEvents
。
processTimeEvents
ae.processTimeEvents
從 eventLoop->timeEventHead
開始,依次遍曆鏈表上的定時事件。
對每個過期的定時事件,processTimeEvents
調用相應的回調函數。這個示例隻會調用唯一注冊的定時事件回調函數redis.c:serverCron
,回調函數返回的時間表示多少毫秒後它將被再次調用。返回的時間被ae.c:aeAddMilliSeconds
記錄下來,ae.c:aeMain
中的while循環會在下次迭代中繼續處理定時事件。
就這些了。
最後更新:2017-05-19 17:33:30