Redis網絡架構及單線程模型

最近略有閒暇時間,因而對Redis進行了一些學習,學習途徑除了官方文檔還有Redis源代碼,我看的版本是2.8.13,Redis源碼總行數不到5W行,不一樣組件拆分很是細緻,閱讀起來也很清晰。這篇博客主要介紹我對Redis網絡層架構以及線程模型的一些瞭解,但願能對你們有所幫助。linux

 

Redis網絡基礎架構

網絡編程離不開Socket,網絡I/O模型最經常使用的無非是同步阻塞、同步非阻塞、異步阻塞、異步非阻塞,高性能網絡服務器最多見的線程模型也就是基於EventLoop模式的單線程模型。咱們看看Redis的網絡架構是怎麼樣的:編程

Redis基礎組建結構api

這裏解釋下上圖涉及的組件,Redis網絡層基礎組件主要包括四個部分:數組

  1. EventLoop事件輪訓器,這部分實如今AE裏面。
  2. 提供Socket句柄事件的多路複用器,這部分分別對於不一樣平臺提供了不一樣的實現,好比epoll和select能夠用於linux平臺、kqueue能夠用於蘋果平臺、evpoll能夠用於Solaris平臺,這裏並無看到iocp,也就是Redis對於Windows支持並非很好。
  3. 包括網絡事件處理器實現的networking,這部分主要包括兩個重要的今天要講的事件處理器:acceptTcpHandler和acceptCommonHandler。
  4. 處理網絡比較底層的部分,好比網絡句柄建立、網絡的讀寫等。

 

Redis單線程模型

要理解Redis的單線程模型,咱們先拋出一些問題,當咱們有多個客戶端同時去跟Redis Server創建鏈接,以後又同時對某個key進行操做,這個過程當中發生了什麼呢?會不會有併發問題?服務器

網絡初始化

好了,這些問題先丟在這了,咱們看看Redis啓動初始化的過程當中會作什麼事情,這裏儘可能省略了與本文無關的部分:網絡

  1. 初始化Redis Server參數,這部分代碼經過initServerConfig實現。
  2. 初始化Redis Server,這部分代碼在initServer裏面。
  3. 啓動事件輪訓器。

對,這裏咱們就把Redis的啓動部分簡化爲三步,跟網絡操做有關的主要在第二步和第三步裏面,來看看initServer裏面發生了什麼:架構

initServer流程併發

initServer裏面首先建立了一個EventLoop,而後監聽Server的IP對應的端口號,假設咱們監聽的是127.0.0.1:3333這個IP:端口對,咱們獲得的一個Server Socket句柄,最後經過createFileEvent將咱們獲得的Server Socket句柄和咱們關心的網絡事件mask註冊到EventLoop上面。EventLoop是什麼呢,咱們看看它的定義:異步

typedef struct aeEventLoop {
    int maxfd;   /* highest file descriptor currently registered */
    int setsize; /* max number of file descriptors tracked */
    long long timeEventNextId;
    time_t lastTime;     /* Used to detect system clock skew */
    aeFileEvent *events; /* Registered events */
    aeFiredEvent *fired; /* Fired events */
    aeTimeEvent *timeEventHead;
    int stop;
    void *apidata; /* This is used for polling API specific data */
    aeBeforeSleepProc *beforesleep;
} aeEventLoop;

上面咱們關注的主要是兩個東西:events和fired。他們分別是兩個數組,events用於存放被註冊的事件以及相應的句柄,fired用於存放當EventLoop線程從多路複用器輪訓到有事件的句柄的時候,EventLoop線程會把它放入fired數組裏面,而後處理。socket

事件註冊示意圖

我用上面的示意圖描述createFileEvent作的事情,就是將Server Socket句柄和關心的事件mask以及當事件產生的時候的事件處理器accptHandler生成一個aeFileEvent註冊到EventLoop的events的數組裏面,固然在這以前會首先將事件註冊到多路複用器上,也就是epoll、kqueue等這些組件上。事件註冊完以後須要對多路複用器進行輪訓,來分離咱們關心切發生的事件,那就是最後一步,啓動事件輪詢器。

接收網絡鏈接

上面的步驟完成了服務端的網絡初始化,並且事件輪詢器已經開始工做了,事件輪詢器作什麼事情呢,就是不斷輪訓多路複用器,看看以前註冊的事件有沒有發生,若是有發生,則將會將事件分離出來,放入EventLoop的fired數組中,而後處理這些事件。

很顯然,上面註冊的事件是客戶端創建鏈接這個事件,所以當有兩個客戶端同時鏈接Redis服務器的時候,事件輪詢器會從多路複用器上面分離出這個事件,同時調用acceptHandler來處理。acceptHandler作的事情主要是accept客戶端的鏈接,建立socket句柄,而後將socket句柄和讀事件註冊到EventLoop的events數組裏面,不同的是對於客戶端的事件處理器是readQueryClient。

accept客戶端鏈接以及註冊客戶端鏈接句柄示意圖

上面示意圖表示了acceptHandler處理客戶端鏈接,獲得句柄以後再將這個句柄註冊到多路複用器以及EventLoop上的示意圖。以後再一樣再處理下一個客戶端的鏈接,這些都是串行的。

事件輪訓

上面接收客戶端這部分其實都發生在事件輪訓的主循環裏面:

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

Redis會不斷的輪訓多路複用器,將網絡事件分離出來,若是是accept事件,則新接收客戶端鏈接並將其註冊到多路複用器以及EventLoop中,若是是查詢事件,則經過讀取客戶端的命令進行相應的處理,這一切都是單線程,順序的執行的,所以不會發生併發問題。

應用分析

Redis官網對Redis的讀寫性能測試結果達到10左右,這是很是吸引人的。Redis的單線程的行爲主要是對內存的讀寫,這些操做其實用不了多少時間,所以瓶頸在網絡I/O上面,咱們通常提供較好的網絡環境就能夠提高Redis的吞吐量,好比提升網絡帶寬,除此以外還能夠經過合併命令提交批處理請求來代替單條命令一次次請求從而減小網絡開銷,提升吞吐量。

 

參考文獻

《Redis源碼》

相關文章
相關標籤/搜索