I/O多路複用技術(multiplexing)

TuxedoLinux發表於2018-06-03
計算機
Redis
C / C++
Multiplexing

I/O多路複用技術(multiplexing)是



antirez/redis · GitHub

關於I/O多路複用(又被稱為“事件驅動”),首先要理解的是,作業系統為你提供了一個功能,當你的某個socket可讀或者可寫的時候,它可以給你一個通知。這樣當配合非阻塞的socket使用時,只有當系統通知我哪個描述符可讀了,我才去執行read操作,可以保證每次read都能讀到有效資料而不做純返回-1和EAGAIN的無用功。寫操作類似。作業系統的這個功能通過select/poll/epoll/kqueue之類的系統呼叫函式來使用,這些函式都可以同時監視多個描述符的讀寫就緒狀況,這樣,多個描述符的I/O操作都能在一個執行緒內併發交替地順序完成,這就叫I/O多路複用,這裡的“複用”指的是複用同一個執行緒。

以select和tcp socket為例,所謂可讀事件,具體的說是指以下事件:
1 socket核心接收緩衝區中的可用位元組數大於或等於其低水位SO_RCVLOWAT;
2 socket通訊的對方關閉了連線,這個時候在緩衝區裡有個檔案結束符EOF,此時讀操作將返回0;
3 監聽socket的backlog佇列有已經完成三次握手的連線請求,可以呼叫accept;
4 socket上有未處理的錯誤,此時可以用getsockopt來讀取和清除該錯誤。

所謂可寫事件,則是指:
1 socket的核心傳送緩衝區的可用位元組數大於或等於其低水位SO_SNDLOWAIT;
2 socket的寫端被關閉,繼續寫會收到SIGPIPE訊號;
3 非阻塞模式下,connect返回之後,發起連線成功或失敗;
4 socket上有未處理的錯誤,此時可以用getsockopt來讀取和清除該錯誤。

Linux環境下,Redis資料庫伺服器大部分時間以單程式單執行緒模式執行(執行持久化BGSAVE任務時會開啟子程式),網路部分屬於Reactor模式,同步非阻塞模型,即非阻塞的socket檔案描述符號加上監控這些描述符的I/O多路複用機制(在Linux下可以使用select/poll/epoll)。伺服器執行時主要關注兩大型別事件:檔案事件和時間事件。檔案事件指的是socket檔案描述符的讀寫就緒情況,時間事件分為一次性定時器和週期性定時器。相比nginx和haproxy內建的高精度高效能定時器,redis的定時器機制並不那麼先進複雜,它只用了一個連結串列來管理時間事件,而且目前連結串列也沒有對各個事件的到點時間進行排序,也就是說,每次都要遍歷連結串列檢查每個事件是否需要到點執行。個人猜想是因為redis目前並沒有太多的定時事件需要管理,redis以資料庫伺服器角色執行時,定時任務回撥函式只有位於redis/src/redis.c下的serverCron函式,所有的定時任務都在這個函式下執行,也就是說,連結串列裡面其實目前就一個節點元素,所以目前也無需實現高效能定時器。

Redis網路事件驅動模型程式碼:redis/src/目錄下的ae.c, ae.h, ae_epoll.c, ae_evport.c, ae_select.c, ae_kqueue.c , ae_evport.c。其中ae.c/ae.h:標頭檔案裡定義了描述檔案事件和事件時間的結構體, 即aeFileEvent和aeTimeEvent;事件驅動狀態結構體aeEventLoop, 這個結構體只有一個名為eventloop的全域性變數在整個伺服器程式中;事件就緒回撥函式指標aeFileProc和aeTimeProc;以及操作事件驅動模型的各種API(aeCreateEventLoop以及之後全部的函式宣告)。ae_epoll.c, ae_select.c, ae_keque.c和ae_evport.c封裝了select/epoll/kqueue等系統呼叫,Linux下當然不支援kqueue和evport。至於究竟選擇哪一種I/O多路複用技術,在ae.c裡有預處理控制,也就是說,這些原始檔只有一個能最後被編譯。優先選擇epoll或者kqueue(FREEBSD和Mac OSX可用),其次是select。

redis事件驅動整體流程:redis伺服器main函式位於檔案redis/src/redis.c, 事件驅動入口函式位於main函式的倒數第三行:

相關文章