目錄html
原本想對netty的源碼進行學習和探究,可是在寫netty以前許多底層的知識和原理性的東西理解清楚,那麼對學習網絡通信框架的效果則會事半功倍。react
本篇主要探討高性能網絡通信框架的一些必要知識和底層操做系統相關的原理。在探討如何作以前,咱們先討論下爲何要作。linux
隨着互聯網的高速發展,用戶量呈指數形式遞增,從原來的PC普及到如今的移動設備普及。用戶量都是千萬甚至億爲單位計算,尤爲是實時通信軟件,在線實時互動的應用出現,在線用戶數從原來的幾十上百到後來的上萬甚至上千萬。單臺服務的性能瓶頸和網絡通信瓶頸慢慢呈現。應用架構從單應用到應用數據分離,再到分佈式集羣高可用架構。單臺服務的性能不足能夠經過構建服務集羣的方式水平擴展,應用性能瓶頸被很好的解決。可是橫向擴展帶來了直接的經濟成本。編程
一個高性能的網絡通信框架從硬件設備到操做系統內核以及用戶模式都須要精心設計。從底層的I/O訪問,到操做系統內核的I/O模型,線程調度以及用戶框架都須要精心設計,只要有任何地方有疏漏都會出現短板效應。windows
當咱們在讀取socket數據時,雖然咱們在代碼僅僅是調用了一個Read
操做,可是實際操做系統層面作了許多事情。首先操做系統須要從用戶模式轉換爲內核模式,處理器會經過網卡驅動對網卡控制器進行操做,網卡控制器則控制網卡。數組
處理器不會直接操控硬件。緩存
爲了提升CPU利用率,I/O訪問方式也發生了很大變化。服務器
I/O訪問的發展趨勢是儘量減小處理器干涉I/O操做,讓CPU從I/O任務中解脫出來,讓處理器能夠去作其餘事情,從而提升性能。網絡
對於I/O訪問感興趣的同窗能夠看《操做系統精髓與設計原理(第5版)》第十一章I/O管理相關內容和《WINDOWS內核原理與實現》第六章I/O論述相關內容架構
在討論I/O模型以前,首先引出一個叫作C10K的問題。在早期的I/O模型使用的是同步阻塞模型,當接收到一個新的TCP鏈接時,就須要分配一個線程。所以隨着鏈接增長線程增多,頻繁的內存複製,上下文切換帶來的性能損耗致使性能不佳。所以如何使得單機網絡併發鏈接數達到10K成爲通信開發者熱門的討論話題。
前面提到,在最原始的I/O模型中,對文件設備數據的讀寫須要同步等待操做系統內核,即便文件設備並無數據可讀,線程也會被阻塞住,雖然阻塞時不佔用CPU始終週期,可是若須要支持併發鏈接,則必須啓用大量的線程,即每一個鏈接一個線程。這樣必不可少的會形成線程大量的上下文切換,隨着併發量的增高,性能愈來愈差。
爲了解決同步阻塞帶來線程過多致使的性能問題,同步非阻塞方案產生。經過一個線程不斷的判斷文件句柄數組是否有準備就緒的文件設備,這樣就不須要每一個線程同步等待,減小了大量線程,下降了線程上下文切換帶來的性能損失,提升了線程利用率。這種方式也稱爲I/O多路複用技術。可是因爲數組是有數組長度上限的(linux默認是1024),並且select模型須要對數組進行遍歷,所以時間複雜度是\(O_{(n)}\)所以當高併發量的時候,select模型性能會愈來愈差。
poll模型和select模型相似,可是它使用鏈表存儲而非數組存儲,解決了併發上限的限制,可是並無解決select模型的高併發性能底下的根本問題。
在linux2.6支持了epoll模型,epoll模型解決了select模型的性能瓶頸問題。它經過註冊回調事件的方式,當數據可讀寫時,將其加入到經過回調方式,將其加入到一個可讀寫事件的隊列中。這樣每次用戶獲取時不須要遍歷全部句柄,時間複雜度下降爲\(O_{(1)}\)。所以epoll不會隨着併發量的增長而性能下降。隨着epoll模型的出現C10K的問題已經完美解決。
前面講的幾種模型都是同步I/O模型,異步I/O模型指的是發生數據讀寫時徹底不一樣步阻塞等待,換句話來講就是數據從網卡傳輸到用戶空間的過程時徹底異步的,不用阻塞CPU。爲了更詳細的說明同步I/O與異步I/O的區別,接下來舉一個實際例子。
當應用程序須要從網卡讀取數據時,首先須要分配一個用戶內存空間用來保存須要讀取的數據。操做系統內核會調用網卡緩衝區讀取數據到內核空間的緩衝區,而後再複製到用戶空間。在這個過程當中,同步阻塞I/O在數據讀取到用戶空間以前都會被阻塞,同步非阻塞I/O只知道數據已就緒,可是從內核空間緩衝區拷貝到用戶空間時,線程依然會被阻塞。而異步I/O模型在接收到I/O完成通知時,數據已經傳輸到用戶空間。所以整個I/O操做都是徹底異步的,所以異步I/O模型的性能是最佳的。
在個人另外一篇文章《Windows內核原理-同步IO與異步IO》對windows操做系統I/O原理作了簡要的敘述,感興趣的同窗能夠看下。
從線程模型上常見的線程模型有Reactor模型和Proactor模型,不管是哪一種線程模型都使用I/O多路複用技術,使用一個線程將I/O讀寫操做轉變爲讀寫事件,咱們將這個線程稱之爲多路分離器。
對應上I/O模型,Reacor模型屬於同步I/O模型,Proactor模型屬於異步I/O模型。
在Reactor中,須要先註冊事件就緒事件,網卡接收到數據時,DMA將數據從網卡緩衝區傳輸到內核緩衝區時,就會通知多路分離器讀事件就緒,此時咱們須要從內核空間讀取到用戶空間。
同步I/O採用緩衝I/O的方式,首先內核會從申請一個內存空間用於存放輸入或輸出緩衝區,數據都會先緩存在該緩衝區。
Proactor模型,須要先註冊I/O完成事件,同時申請一片用戶空間用於存儲待接收的數據。調用讀操做,當網卡接收到數據時,DMA將數據從網卡緩衝區直接傳輸到用戶緩衝區,而後產生完成通知,讀操做即完成。
異步I/O採用直接輸入I/O或直接輸出I/O,用戶緩存地址會傳遞給設備驅動程序,數據會直接從用戶緩衝區讀取或直接寫入用戶緩衝區,相比緩衝I/O減小內存複製。
本文經過I/O訪問方式,I/O模型,線程模型三個方面解釋了操做系統爲實現高性能I/O作了哪些事情,經過提升CPU使用效率,減小內存複製是提升性能的關鍵點。
出處:http://www.javashuo.com/article/p-swxpigmz-cr.html 做者:傑哥很忙 本文使用「CC BY 4.0」創做共享協議。歡迎轉載,請在明顯位置給出出處及連接。