Redis服務端對於命令的處理是單線程的,但是在I/O層面卻可以同時面對多個客戶端並發的提供服務,並發到內部單線程的轉化通過多路復用框架實現
一個IO操作的完整流程是數據請求先從用戶態到內核態,也就是操作系統層面,然后再調用操作系統提供的API,調用相應的設備去獲取相應的數據。
當相應的設備准備好數據后,會將數據復制到內核態。數據從相應的設備到內核態的處理方式分為:阻塞和非阻塞。
阻塞:用戶請求會等待數據准備好從操作系統調用相應的設備返回到內核態,如果沒有返回處於阻塞狀態。
非阻塞:非阻塞是操作系統接收到一組文件描述符,然后操作系統批量處理這些個文件描述符,然后不管有沒有准備好數據都立即返回,
如果沒有對應的准備好的文件描述符,則繼續輪詢獲取准備好數據的文件描述符。
數據從內核態到用戶態的復制數據的處理方式的不同可分為:同步和異步
同步:用戶請求等待數據從內核態向用戶態復制數據,在此期間不做其他的事情,次稱為同步
異步:在數據從內核態向用戶態復制的過程中,用戶請求不會一直處於等待狀態而是做其他事情,稱為異步
redis的多路復用框架使用的非阻塞的數據返回模式
使用模型有:select、poll、epoll
首先select、poll都是傳遞一個 fd的集合調用操作系統的API。其中select 模式有大小的限制 最大只能是1024的fd
而 poll是傳遞一個pollfd數組,pollfd中的events字段和revents分別用於標示關注的事件和發生的事件,所以沒有大小限制
select/poll的幾大缺點:
1.每次調用select/poll,都需要把fd集合從用戶態copy到內核態,如果是fd集合很大的時候,開銷會很大
2.每次調用select/poll,都要在內核態中循環遍歷fd集合,去判斷哪些fd准備好了數據,這個是在fd集合很大的情況下面,開銷會很大
3.針對select支持的文件描述符數量太小了,默認是1024
4.select返回的是整個有句柄的數組,需要全部遍歷一遍才知道那些fd有事件的產生
相比於poll,因為poll使用的是鏈表保存fd,所以沒有fd大小數量的限制,但是其他的缺點依然存在
epoll的多路復用機制,使用了select和poll都不同的方式,所以就規避了select和poll的缺點
epoll 支持的fd上限為文件打開數。這個數字一般遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左右。
設想一下如下場景:有100萬個客戶端同時與一個服務器進程保持着TCP連接。而每一時刻,通常只有幾百上千個TCP連接是活躍的(事實上大部分場景都是這種情況)。如何實現這樣的高並發?
在select/poll時代,會把這些fd從用戶態復制到內核態,然后讓操作系統去查詢這些套接字上是否有事件發生,輪詢完以后,在將這些句柄集合返回,從內核態復制會用戶態,讓用戶應用程序
遍歷輪詢去獲取有哪些句柄有事件的發上,這個過程大量的浪費了性能。因此,select和poll 一般只能處理幾千的並發。但是epoll的設計和select/poll的設計完全不同。他不需要用戶應用程序
循環遍歷所有的句柄數組才能知道有哪些句柄有事件的發生,因此epoll能支持更大的並發。
epoll 在linux系統內部維護了一個文件系統結構,將select/poll分為了3個部分。
01)調用epoll_create在linux系統中維護一個epoll對象
02)調用epoll_ctl向epoll對象中添加100萬個fd 套接字
03)調用epoll_wait收集事件發生的fd
底層實現
1.當調用epoll_create時候,會在linux內核維護一個eventpoll結構體,在這個結構體中有兩個成員,與epoll的使用方式密切相關
結構體如下:
struct eventpoll{
...........
/**紅黑樹的根節點,存儲了所有添加到epoll中所需要監控的事件**/
struct rb_root rb;
/** 雙向鏈表中存儲着所有的通過epoll_wait返回給用戶滿足條件的事件 **/
struct list_head rdlist;
..............
};
每一個epoll對象都有一個獨立的eventpoll結構體,用於存放需要監控的fd事件,這些事件都會掛載在結構體重的紅黑樹中。而所有添加到eventpoll結構體中,都會與設備(網卡)建立回調
關系,也就是說,當有事件發生時候,相應的事件會回調這個回調方法。這個回調方法就是ep_poll_callback,他講發生的事件添加到eventpoll結構體中的rdlist雙向鏈表中
在epoll中,對於每一個事件,都會建立一個epitem結構體,如下所示:
struct epitem{
struct rb_node rbn;//紅黑樹節點
struct list_head rdllink;//雙向鏈表節點
struct epoll_field ffd;//事件句柄信息
struct eventpoll *ep;//指向所屬的eventpoll對象
struct epoll_event event;//期待發生的事件類型
};
當調用event_wait判斷是否有事件發生時候,只需要判斷 eventpoll 雙向鏈表中,rdlist 有沒有epitem對象即可。如果rdlist不為空,則直接將發生的事件復制到用戶態。同時將事件數量返回給用戶.