首頁 >資料庫 >Redis >一文聊聊Redis中的epoll和檔案事件

一文聊聊Redis中的epoll和檔案事件

青灯夜游
青灯夜游轉載
2021-11-03 10:55:141908瀏覽

這篇文章跟大家介紹一下Redis中的文件事件,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

一文聊聊Redis中的epoll和檔案事件

事件驅動

Redis 伺服器是事件驅動程序,分為檔案事件時間事件

  • 檔案事件:socket 的可讀可寫事件
  • #定時任務

【相關推薦:Redis視頻教學

它們都被封裝到aeEventLoop結構體中

typedef struct aeEventLoop {
	int stop; // 标识事件是否结束
	aeFileEvent *events; // 文件事件数组,存储已注册的文件事件
	aeFireEvent *fired; // 存储被触发的文件事件
	aeTimeEvent *timteEventHead; // 多个时间事件形成的链表
	void *apidata; // I/O模型的封装
	aeBeforeSleepProc *beforesleep; // 进程阻塞前执行
	aeBeforeSleepProc *aftersleep; // 进程被唤醒后执行
} aeEventLoop;

事件驅動程式其實也是透過while/for循環,循環等待事件的發生

while (! eventLoop->stop) {
	if (eventLoop->beforesleep != NULL)
		eventLoop->beforesleep(eventLoop)
	aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
}

aeProcessEvents為事件處理主函數

##epoll

Redis 用戶端透過TCP socket與服務端交互,文件事件指的就是socket 的可讀可寫事件。一般使用非阻塞模式,相關的 I/O 多重化有

select/epoll/kqueue等,不同的作業系統不同的實作。

epoll為例,它是 Linux 核心為處理大量並發網路連線而提出解決方案。 epoll提供3個API

  • epoll_create 建立一個epoll 專用的檔案描述符,用於後續epoll 相關API 呼叫

  • #
    int epoll_create(int size)
    // size 告知内核程序期望注册的网络连接数目,Linux 2.6.8后改为内核动态分配
    // 返回参数是 epoll 专用的文件描述符
  • epoll_ctl 函數向epoll 註冊、修改或刪除需要監控的事件

  • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
    // epfd 函数 epoll_create 返回的 epoll 文件描述符
    // op 操作类型 EPOLL_CTL_ADD:注册事件; EPOLL_CTL_MOD:修改网络连接事件; EPOLL_CTL_DEL:删除事件
    // fd 网络连接的 socket 文件描述符
    // event 需要监控的事件
  • epoll_wait 函數會阻塞進程,直到監控的若干網路連線有事件發生

  • int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout)
    // epfd 函数epoll_create返回的epoll文件描述符
    // epoll_event 作为输出参数使用,用于回传已触发的事件数组
    // maxevents 每次能处理的最大事件数目
    // timeout epoll_wait 函数阻塞超时时间,如果超过 timeout 时间还没有事件发生,函数就不再阻塞直接返回;当 timeout 等于0是函数立即返回,timeout 等于-1时函数一直阻塞到有事件发生

檔案事件

Reids 沒有直接使用epoll 的API,而是同時支援4種I/ O多路復用模型,對這些模型的API 進行了封裝。然後在編譯階段檢查作業系統支援的I/O多路復用模型,並依照策略決定複用那張模型。

還是以epoll 為例,Redis 進行如下封裝

// 对应 epoll_create
static int aeApiCreate(aeEventLoop *eventLoop)

// 对应 epoll_ctl 添加事件
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
// 对应 epoll_ctl 删除事件
static int aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask)

// 对应 epoll_wait
static int aeApiPool(aeEventLoop *eventLoop, struct timeval *tvp)

回想一下上面提到的

eventLoop結構體,其成員apidata 指向4種I/O多路復用模型物件;events 儲存需要監控的事件數組,以socket 檔案描述符作為數組索引存取元素;fired 儲存已觸發的事件數組。

檔案事件的結構體定義如下:

typedef struct aeFileEvent {
	int mask; // 文件事件类型 AE_READABLE 可读事件;AE_WRITEABLE 可写事件
	aeFileProc *rfileProc; // 读事件处理函数指针
	aeFileProc *wfileProc; // 写事件处理函数指针
	void *clientData; // 指向对应的客户端对象
} aeFileEvent;

看看建立檔案事件aeCreateFileEvent 的實作

int aeCreateFileEvent (aeEventLoop *eventLoop, int fd, int mask, aeFileProc *proc, void *clientData) {
	aeFileEvent *fe = &eventLoop->evnts[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;
	return AE_OK;
}

Redis 伺服器會透過建立各類檔案事件來處理事務,例如:

    啟動時建立socket 並監聽,等待客戶端連線
  • aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler, NULL);
    客戶端與伺服器建立socket 連線之後,伺服器會等待客戶端的指令請求
  • aeCreateFileEvent(server.el, fd, AE_READABLLE, readQueryFromClient, c);
    伺服器處理完客戶端的命令請求之後,命令回復會暫時緩存在client結構體的buf緩衝區,待客戶端檔案描述符的可寫事件發生時,才會真正往客戶端發送命令回應
  • aeCreateFileEvent(server.el, c->fd, AE_READABLLE, sendReplyToClient, c);
Redis 所有事件的執行都是透過

aeProcessEvents函數來控制。在其中,執行文件事件會出現阻塞情況(epoll_wait),如果阻塞事件太長了,會妨礙到時間事件(定時)的執行,為避免這種情況,在實現文件事件時傳入的等待時間,是計算最早發生的時間事件所得到的

int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
	shortest = aeSearchNearestTimer(eventLoop);
	long long ms = (shortest->when_sec - now_sec) * 1000 + \
		shortest->when_ms - now_ms;

	// 阻塞事件发生
	numevents = aeApiPoll(eventLoop, ms);

	for (j=0; j < numevents; j++) {
		aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j]].fd];
		// 处理文件事件,即根据类型执行rfileProc或wfileProc
	}

	// 处理时间事件
	processed += processTimeEvents(eventLoop);
}

總結

現在我們來整體看一下Redis 伺服器對應指令的流程

一文聊聊Redis中的epoll和檔案事件

aeMain 函數透過呼叫aeProcessEvents 函數來進行檔案事件和時間事件的調度和執行。 aeEventLoop 中記錄了事件相關的資訊。首先透過aeSearchNearestTimer 函數取得最短的時間事件的執行時間間隔n,然後呼叫aeApiPoll 函數取得監聽到的套接字,最後執行與套接字向對應的事件處理函數rfileProc 和wfileProc,最後再執行時間事件函數processTimeEvents 。

一次完整的客戶端與服務端連接事件:

  • 器監聽套件字的AE_READABLE 事件,當客戶端發送連線請求產生AE_READABLE  事件,服務端會對客戶端的連線請求進行應答,將客戶端套接字的AE_READABLE 事件與命令請求處理函數(aeFileProc),客戶端可以向服務端發送命令請求了

  • 端向服務端發送命令請求,客戶端套接字將產生AE_READABLE 事件,引發命令處理器去執行,執行命令將產生相應的命令回复,服務端將客戶端套接字的AE_WRITABLE 事件與命令回復處理函數( aeFileProc)關聯

  • 端嘗試讀取命令回复時,客戶端套接字將產生AE_WRITABLE 事件,觸發命令回復處理器執行,當命令回復處理器將命令回復全部寫入套接字之後,伺服器就會接觸到客戶端套接字的AE_WRITABLE 事件與指令回覆處理函數(aeFileProc)之間的關聯

更多程式相關知識,請造訪:程式設計影片! !

以上是一文聊聊Redis中的epoll和檔案事件的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除