前言
高性能是每個程序員的追求,無論寫一行代碼還是做一個系統,都希望能夠達到高性能的效果。高性能架構設計主要集中在兩方面:
- 盡量提升單服務器的性能,將單服務器的性能發揮到極致
- 如果單服務器無法支撐性能,設計服務器集群方案
單服務器高性能的關鍵之一就是服務器采取的網絡編程模型。服務器如何管理連接,如何處理請求等。這兩個設計點最終都和操作系統的I/O模型及進程模型相關。
- I/O模型:阻塞、非阻塞、同步、異步
- 進程模型:單進程、多進程、多線程。
我們所說的I/O模型是指網絡I/O模型,就是服務端如何管理連接,如何請求連接的措施,是用一個進程管理一個連接(PPC),還是一個線程管理一個連接(TPC),亦或者一個進程管理多個連接(Reactor)。
因此IO多路復用中多路就是多個TCP連接(或多個Channel),復用就是指復用一個或少量線程,理解起來就是多個網路IO復用一個或少量線程來處理這些連接。
常見I/O模型
- 同步阻塞IO(Blocking IO):即傳統IO模型
- 同步非阻塞IO(Non-blocking IO):默認常見的socket都是阻塞的,非阻塞IO要求socket被設置成NONBLOCK
- IO多路復用(IO Multiplexing):即經典的Reactor設計模式,也被稱為異步阻塞IO,Java中的selector和linux中的epoll都是這種模型
- 異步IO(Asychronous IO):即Proactor設計模式,也被稱為異步非阻塞IO
同步和異步的概念描述的是用戶線程與內核的交互方式,這里所說的用戶進程/線程和內核是以傳輸層為分割線的,傳輸層以上是指用戶進程,傳輸層以下(包括傳輸層)是指內核(處理所有通信細節,發送數據,等待確認,給無序到達的數據排序等,這四層是操作系統內核的一部分)。同步是指用戶線程發起IO請求后需要等待或者輪詢內核IO操作,完成后才能繼續執行。異步是指用戶線程發起IO請求后仍繼續執行,當內核IO操作完成后回通知用戶線程,或者調用用戶線程注冊的回調函數。
阻塞和非阻塞的概念描述的是用戶線程調用內核IO操作的方式,阻塞時指IO操作需要徹底完成后才能返回用戶空間,非阻塞時指IO操作被調用后立即返回給用戶一個狀態值,無需等待IO操作徹底完成。
同步阻塞IO
同步阻塞IO是最簡單的IO模型,用戶線程在內核進行IO操作時被阻塞。用戶線程通過調用系統調用read發起IO讀操作,由用戶空間轉到內核空間。內核等到數據包到達后,然后將接受的數據拷貝到用戶空間,完成read操作。整個IO請求過程,用戶線程都是被阻塞的,對CPU利用率不夠
同步非阻塞IO
在同步基礎上,將socket設置為NONBLOCK,這樣用戶線程可以在發起IO請求后立即返回。雖說可以立即返回,但並未讀到任何數據,用戶線程需要不斷的發起IO請求,直到數據到達后才能真正讀到數據,然后去處理。
整個IO請求中,雖然可以立即返回,但是因為是同步的,為了等到數據,需要不斷的輪詢、重復請求,消耗了大量的CPU資源。因此,這種模型很少使用,實際用處不大。
IO多路復用
不管是同步阻塞還是同步非阻塞,對系統性能的提升都是很小的。而通過復用可以使一個或一組線程(線程池)處理多個TCP連接。IO多路復用使用兩個系統調用(select/poll/epoll和recvfrom),blocking IO只調用了recvfrom。select/poll/epoll核心是可以同時處理多個connection,而不是更快,所以連接數不高的話,性能不一定比多線程+阻塞IO好。
select是內核提供的多路分離函數,使用它可以避免同步非阻塞IO中輪詢等待問題。
用戶首先將需要進行IO操作的socket添加到select中,然后阻塞等待select系統調用返回。當數據到達時,socket被激活,select函數返回,用戶線程正式發起read請求,讀取數據並繼續執行。
這么一看,這種方式和同步阻塞IO並沒有太大區別,甚至還多了添加監視socket以及調用select函數的額外操作,效率更差。但是使用select以后,用戶可以在一個線程內同時處理多個socket的IO請求,這就是它的最大優勢。用戶可以注冊多個socket,然后不斷調用select讀取被激活的socket,即可達到同一個線程同時處理多個IO請求的目的。而在同步阻塞模型中,必須通過多線程方式才能達到這個目的。所以IO多路復用設計目的其實不是為了快,而是為了解決線程/進程數量過多對服務器開銷造成的壓力。
select(socket); #向select注冊socket
while(true){
sockets = select(); #獲取被激活的socket
for(socket in sockets){
if(can_read(socket)){ #socket可讀,調用read讀取數據
read(socket,buffer);
process(buffer);
}
}
}
雖然這種方式允許單線程內處理多個IO請求,但是每個IO請求的過程還是阻塞的(在select函數上阻塞),平均時間甚至比同步阻塞IO模型還要長。如果用戶線程只注冊自己感興趣的socket,然后去做自己的事情,等到數據到來時在進行處理,則可以提高CPU利用率。
通過Reactor方式,用戶線程輪詢IO操作狀態的工作統一交給handle_events事件循環處理。用戶線程注冊事件處理器之后可以繼續執行做其他的工作(異步),而Reactor線程負責調用內核的select函數檢查socket狀態。當有socket被激活時,則通知相應的用戶線程(或執行用戶線程的回調函數),執行handel_envent進行數據的讀取、處理工作。
由於select函數是阻塞的,因此多路IO復用模型就被稱為異步阻塞IO模型,這里阻塞不是指socket。因為使用IO多路復用時,socket都設置NONBLOCK,不過不影響,因為用戶發起IO請求時,數據已經到達了,用戶線程一定不會被阻塞。
IO多路復用是最常用的IO模型,但其異步程度還不徹底,因為它使用了回阻塞線程的select系統調用。因此IO多路復用只能稱為異步阻塞IO,而非真正的異步IO。
附:Reactor設計模式
異步非阻塞IO
在IO多路復用模型中,事件循環文件句柄的狀態事件通知給用戶線程,由用戶線程自行讀取數據、處理數據。而異步IO中,當用戶線程收到通知時候,數據已經被內核讀取完畢,並放在了用戶線程指定的緩沖區內,內核在IO完成后通知用戶線程直接使用就行了。因此這種模型需要操作系統更強的支持,把read操作從用戶線程轉移到了內核。
相比於IO多路復用模型,異步IO並不十分常用,不少高性能並發服務程序使用IO多路復用+多線程任務處理的架構基本可以滿足需求。不過最主要原因還是操作系統對異步IO的支持並非特別完善,更多的采用IO多路復用模擬異步IO方式(IO事件觸發時不直接通知用戶線程,而是將數據讀寫完畢后放到用戶指定的緩沖區)。
select、poll、epoll詳解
select,poll,epoll都是IO多路復用的機制。I/O多路復用就是通過一種機制,一個進程可以監視多個描述符(socket),一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。雖說IO多路復用被稱為異步阻塞IO,但select,poll,epoll本質上都是同步IO,因為它們都需要在續寫事件就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而真正意義上的異步IO無需自己負責進行讀寫。
select
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select函數監視的文件描述符有三類,readfds,writefds,exceptfds。調用后函數會阻塞,直到有描述符就緒(有數據讀、寫、或者有except),或者超時(timeout指定時間,如果立即返回設置null),函數返回。當select函數返回后,可以通過便利fdset,來找到就緒的描述符。
優點:良好的跨平台性。
缺點:單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上為1024,可以通過修改宏定義甚至重新編譯內核的方式提升這一限制,但這樣會造成效率的降低。
poll
int poll(struct poll *fds, unsigned int nfds, int timeout);
struct pollfd{
int fd;
short events;
short revents;
};
與select使用三個位圖來表示fdset,poll使用一個pollfd的指針實現。pollfd結構包含了要監視的event和發生的event,不在使用select參數傳值的方式。同時pollfd並沒有最大數量的限制(但數量過大性能也會下降)。和select一樣,poll返回后,需要輪詢pollfd來或許就緒的描述符。
epoll
epoll是select和poll的增強版本,相比於前兩者,它更加的靈活,沒有描述符的限制。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需要一次。
參考鏈接:
https://blog.csdn.net/sehanlingfeng/article/details/78920423
https://www.cnblogs.com/wlwl/p/10293057.html
https://www.cnblogs.com/natian-ws/p/10785649.html
https://segmentfault.com/a/1190000003063859
《架構修煉之道》
《從零開始學架構》