redisserver事件模型
事件驅動
redis伺服器是一個事件驅動的程式,內部需要處理兩類事件,一類是檔案事件(file event),一類是時間事件(time event),前者對應著處理各種io事件,後者對應著處理各種定時任務。
印象中我們都知道redis是單執行緒服務,其實本質上的確就是這樣子的,上面說的file event 和 time event都是由單個執行緒驅動的,file event 底層其實是通過select/epoll等模型去執行驅動的,time event是通過內部維持一個定時任務列表來實現的。
redis server的事件模型其實就是經典的NIO模型,底層通過select/epoll等機制實現非同步NIO,通過檢測到event到來後for迴圈實現序列處理。
事件註冊
server啟動流程
redis server的main函式在啟動過程中按照以下三個步驟進行初始化並進入執行狀態:
- 初始化伺服器配置
- 載入配置檔案初始化配置(redis的監聽埠在這一步完成配置)
- 建立並初始化伺服器的資料結構監聽埠等(完成埠的監聽,事件註冊等)
- 迴圈處理事件(迴圈處理file event 和 time event)
int main(int argc, char **argv) {
// 初始化伺服器
initServerConfig();
// 載入配置檔案, options 是前面分析出的給定選項
loadServerConfig(configfile,options);
// 建立並初始化伺服器資料結構
initServer();
// 執行事件處理器,一直到伺服器關閉為止
aeSetBeforeSleepProc(server.el,beforeSleep);
aeMain(server.el);
// 伺服器關閉,停止事件迴圈
aeDeleteEventLoop(server.el);
return 0;
}
server初始化流程
整個server初始化過程刪除了不相關的程式碼保留跟這部分文章相關的內容,整個初始化過程核心邏輯如下:
- 建立監聽的socket用於accept連線
- 註冊監聽socket到底層時間驅動如select/epoll當中(aeCreateFileEvent)
- 建立時間事件(aeCreateTimeEvent)
void initServer() {
// 開啟 TCP 監聽埠,用於等待客戶端的命令請求
if (server.port != 0 &&
listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)
exit(1);
// 為 serverCron() 建立時間事件
if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
redisPanic("Can`t create the serverCron time event.");
exit(1);
}
// 為 TCP 連線關聯連線應答(accept)處理器,用於接受並應答客戶端的 connect() 呼叫
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.");
}
}
}
server繫結檔案事件監聽過程
aeCreateFileEvent方法內部關鍵點在於aeApiAddEvent這個方法,將fd繫結到具體的eventLoop當中
/*
* 根據 mask 引數的值,監聽 fd 檔案的狀態,
* 當 fd 可用時,執行 proc 函式
*/
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData)
{
if (fd >= eventLoop->setsize) {
errno = ERANGE;
return AE_ERR;
}
if (fd >= eventLoop->setsize) return AE_ERR;
// 取出檔案事件結構
aeFileEvent *fe = &eventLoop->events[fd];
// 監聽指定 fd 的指定事件
if (aeApiAddEvent(eventLoop, fd, mask) == -1)
return AE_ERR;
// 設定檔案事件型別,以及事件的處理器
fe->mask |= mask;
if (mask & AE_READABLE) fe->rfileProc = proc;
if (mask & AE_WRITABLE) fe->wfileProc = proc;
// 私有資料
fe->clientData = clientData;
// 如果有需要,更新事件處理器的最大 fd
if (fd > eventLoop->maxfd)
eventLoop->maxfd = fd;
return AE_OK;
}
server處理accpet事件過程
整個accept的處理過程按照anetTcpAccept -> acceptCommonHandler -> createClient -> aeCreateFileEvent實現socket的accept並註冊到eventLoop。
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
while(max--) {
// accept 客戶端連線
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
acceptCommonHandler(cfd,0);
}
}
static void acceptCommonHandler(int fd, int flags) {
// 建立客戶端
redisClient *c;
if ((c = createClient(fd)) == NULL) {
redisLog(REDIS_WARNING,
"Error registering fd event for the new client: %s (fd=%d)",
strerror(errno),fd);
close(fd); /* May be already closed, just ignore errors */
return;
}
}
redisClient *createClient(int fd) {
// 分配空間
redisClient *c = zmalloc(sizeof(redisClient));
// 當 fd 不為 -1 時,建立帶網路連線的客戶端
// 如果 fd 為 -1 ,那麼建立無網路連線的偽客戶端
// 因為 Redis 的命令必須在客戶端的上下文中使用,所以在執行 Lua 環境中的命令時
// 需要用到這種偽終端
if (fd != -1) {
// 繫結讀事件到事件 loop (開始接收命令請求)
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
readQueryFromClient, c) == AE_ERR)
{
close(fd);
zfree(c);
return NULL;
}
}
//此處省略很多程式碼
}
事件處理過程
redis server啟動事件處理的主迴圈,會迴圈執行aeProcessEvents方法。
/*
* 事件處理器的主迴圈
*/
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
// 如果有需要在事件處理前執行的函式,那麼執行它
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
// 開始處理事件
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}
aeProcessEvents方法有一個非常巧妙的實現,在內部需要同時處理time event 和 file event,那麼如何處理先後問題呢。思路如下:
- 首先計算time event距離當前的時間最近的時間點得出時間差X
- 其次在處理file event的時候wait的設定超時時間為X
- 如果在超時時間X內有file event發生那麼就處理file event然後處理time event
- 如果在超時時間X內沒有file event那麼剛好可以處理time event事件
- 最後說明的是在處理event的過程中我們其實通過for迴圈的序列讀寫事件
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;
/* Note that we want call select() even if there are no
* file events to process as long as we want to process time
* events, in order to sleep until the next time event is ready
* to fire. */
if (eventLoop->maxfd != -1 ||
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int j;
aeTimeEvent *shortest = NULL;
struct timeval tv, *tvp;
// 獲取最近的時間事件
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
shortest = aeSearchNearestTimer(eventLoop);
if (shortest) {
// 如果時間事件存在的話
// 那麼根據最近可執行時間事件和現在時間的時間差來決定檔案事件的阻塞時間
long now_sec, now_ms;
/* Calculate the time missing for the nearest
* timer to fire. */
// 計算距今最近的時間事件還要多久才能達到
// 並將該時間距儲存在 tv 結構中
aeGetTime(&now_sec, &now_ms);
tvp = &tv;
tvp->tv_sec = shortest->when_sec - now_sec;
if (shortest->when_ms < now_ms) {
tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;
tvp->tv_sec --;
} else {
tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
}
// 時間差小於 0 ,說明事件已經可以執行了,將秒和毫秒設為 0 (不阻塞)
if (tvp->tv_sec < 0) tvp->tv_sec = 0;
if (tvp->tv_usec < 0) tvp->tv_usec = 0;
} else {
// 執行到這一步,說明沒有時間事件
// 那麼根據 AE_DONT_WAIT 是否設定來決定是否阻塞,以及阻塞的時間長度
/* 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 */
}
}
// 處理檔案事件,阻塞時間由 tvp 決定
numevents = aeApiPoll(eventLoop, tvp);
for (j = 0; j < numevents; j++) {
// 從已就緒陣列中獲取事件
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
int mask = eventLoop->fired[j].mask;
int fd = eventLoop->fired[j].fd;
int rfired = 0;
/* note the fe->mask & mask & ... code: maybe an already processed
* event removed an element that fired and we still didn`t
* processed, so we check if the event is still valid. */
// 讀事件
if (fe->mask & mask & AE_READABLE) {
// rfired 確保讀/寫事件只能執行其中一個
rfired = 1;
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
}
// 寫事件
if (fe->mask & mask & AE_WRITABLE) {
if (!rfired || fe->wfileProc != fe->rfileProc)
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
}
processed++;
}
}
/* Check time events */
// 執行時間事件
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop);
return processed; /* return the number of processed file/time events */
}
相關文章
- Javascript事件模型系列(一)事件及事件的三種模型JavaScript事件模型
- 事件模型和事件委託事件模型
- JavaScript事件模型JavaScript事件模型
- .NET 事件模型教程(二)事件模型
- 模型事件幾種用法模型事件
- JS的事件繫結和事件流模型JS事件模型
- 統一監聽所有模型的模型事件模型事件
- DOM事件模型與Internet explorer事件模型之繫結機制薦事件模型
- Laravel 模型事件的應用Laravel模型事件
- JS專題之事件模型JS事件模型
- Laravel 模型事件實現原理Laravel模型事件
- 監聽所有模型的 saved 事件模型事件
- Laravel 中模型事件 Observer 的使用Laravel模型事件Server
- 面試準備之JavaScript事件模型面試JavaScript事件模型
- Redis 中的事件驅動模型Redis事件模型
- Laravel 中的模型事件與 ObserverLaravel模型事件Server
- 說說JavaScript中的事件模型JavaScript事件模型
- Javascript事件模型系列(四)我所理解的javascript自定義事件JavaScript事件模型
- java事件處理模型是什麼Java事件模型
- 透過觀察者監聽模型事件模型事件
- 通過觀察者監聽模型事件模型事件
- 小馬哥Spring事件驅動模型Spring事件模型
- 模型deleted事件監聽報錯解析模型delete事件
- 從setTimeout說事件迴圈模型事件模型
- Javascript事件模型系列(二)事件的捕獲-冒泡機制及事件委託機制JavaScript事件模型
- JavaScript 複習之 事件模型 和 Event物件JavaScript事件模型物件
- 觀察者Observer模型事件,使用注意情況Server模型事件
- Spring中的事件驅動模型(一)Spring事件模型
- 基於python的事件處理模型Python事件模型
- JavaScript之坑了我--事件模型細節JavaScript事件模型
- Javascript事件模型系列(三)jQuery中的事件監聽方式及異同點JavaScript事件模型jQuery
- 記 Laravel 模型 deleted 事件未被觸發領悟Laravel模型delete事件
- 鴻蒙HarmonyOS實戰-Stage模型(開發卡片事件)鴻蒙模型事件
- 從拼多多事件看電商的促銷模型事件模型
- 通過一道題來看React事件模型React事件模型
- 基於SceneKit的3D模型觸碰事件3D模型事件
- ExtJS框架基礎:事件模型及其常用功能JS框架事件模型
- Java反應式事件溯源之第 2 部分:Actor 模型Java事件模型