現代大型高性能網站諸如淘寶,京東,微博,FB,知乎等等,網站架構涉及不少知識。像業務分層,軟件分割模塊化,分佈式部署,集羣服務器,負載均衡等技術能夠幫助架構師將一個大的複雜的問題切分紅小的簡單的問題。這篇文章着眼於解決這些切好的小問題上,單機上有哪些編程實踐或者模型能夠很好的作到高併發。本人web開發小白一枚,寫文章是想梳理本身的思路,求得大牛斧正,但願各位多多批判。文章的內容大多來自網上的閱讀加上些本身的理解,文末附上參考閱讀的文章。html
由於有數年的嵌入式領域的經驗,先說一下我認爲的比較高效的處理模型。linux
硬件環境:單機30core, 1G Hz。nginx
軟件環境:6Wind fastpath,每一個core上都是run-to-complete的endless loop.沒有操做系統。git
功能:一個超級簡單的reverse proxy,具備load balance的簡單功能。github
衡量併發性能,咱們看一下一個IP包從網口緩衝區收上來處理到發出去大約須要多長時間呢?web
圖spring
收+處理+發大概是500+1000+500=2000 cycles,時間也就是2us。單機1s內能夠支持30*(1s/2us)=15,000,000 request/s的併發。屌炸天的併發能力了吧!緣由有兩個:apache
沒有操做系統overhead。編程
包處理簡單,IP層的處理,直接c函數調用,總共1000 cycle。服務器
固然這是從嵌入式得來的經驗,web開發中不可能這樣,沒有Nginx,沒有web框架,沒有lib沒有各類open source,甚至沒有linux。回到原始社會造出飛機大炮來,這不把web開發者逼瘋了。軟件也是一個社會化協做的過程,os,framework,lib,opensource給開發者帶來極大方便的同時,也伴隨着性能的開銷。如何在性能和可擴展性、維護性等其餘指標找到一個平衡點,如何選擇合適的編程模型,合適的第三方模塊達到最小的overhead,這是成長爲高手的開發者都會不斷思考的問題。
High Performance architecture,這篇文章總結了四個性能殺手:
數據複製
上下文切換
動態內存分配
鎖競爭
上面的編程模型之因此高效,就是將CPU用到極致,儘可能避免這4種狀況發生。心中有這麼一個極簡的高效模型,後面學習其餘模式的時候能夠暗作對比看一下到底會有哪些額外的開銷。
大名鼎鼎的Nginx使用了多進程模型,主進程啓動時初始化,bind,監聽一組sockets,而後fork一堆child processes(workers),workers共享socket descriptor。workers競爭accept_mutex,獲勝的worker經過IO multiplex(select/poll/epoll/kqueue/...)來處理成千上萬的併發請求。爲了得到高性能,Nginx還大量使用了異步,事件驅動,non-blocking IO等技術。"What resulted is a modular, event-driven, asynchronous, single-threaded, non-blocking architecture which became the foundation of nginx code."
Nginx 架構
對比着看一下Apache的兩種經常使用運行模式,詳見 Apache Modules
1. Apache MPM prefork模式
主進程經過進程池維護必定數量(可配置)的worker進程,每一個worker進程負責一個connection。worker進程之間經過競爭mpm-accept mutex實現併發和連接處理隔離。 因爲進程內存開銷和切換開銷,該模式相對來講是比較低效的併發。
2. Apache MPM worker模式
因爲進程開銷較大,MPM worker模式作了改進,處理每一個connection的實體改成thread。主進程啓動可配數量的子進程,每一個進程啓動可配數量的server threads和listen thread。listen threads經過競爭mpm-accept mutex獲取到新進的connection request經過queue傳遞給本身進程所在的server threads處理。因爲調度的實體變成了開銷較小的thread,worker模式相對prefork具備更好的併發性能。
小結兩種webserver,能夠發現Nginx使用了更高效的編程模型,worker進程通常跟CPU的core數量至關,每一個worker駐留在一個core上,合理編程能夠作到最小程度的進程切換,並且內存的使用也比較經濟,基本上沒有浪費在進程狀態的存儲上。而Apache的模式是每一個connection對應一個進程/線程,進程/線程間的切換開銷,大量進程/線程的內存開銷,cache miss的機率增大,都限制了系統所能支持的併發數。
因爲IO的處理速度要遠遠低於CPU的速度,運行在CPU上的程序不得不考慮IO在準備暑假的過程當中該乾點什麼,讓出CPU給別人仍是本身去幹點別的有意義的事情,這就涉及到了採用什麼樣的IO策略。通常IO策略的選用跟進程線程編程模型要同時考慮,二者是有聯繫的。
同步阻塞IO
同步阻塞IO是比較常見的IO模型,網絡編程中若是建立的socket的描述符屬性設置爲阻塞的,當socket對應的用戶空間緩衝區內尚無可讀數據時,該進程/線程在系統調用read/recv socket時,會將本身掛起阻塞等待socket ready。
同步非阻塞IO和非阻塞IO同步複用
同步非阻塞IO
非阻塞IO同步複用
對比着同步阻塞IO,若是socket數據沒有ready,系統調用read/recv會直接返回,進程能夠繼續執行不會掛起讓出CPU。固然這樣作對單個socket來講沒有多大的意義,若是要支持大量socket的併發就頗有用了,也就是IO複用。select/poll/epoll就是這樣的應用,IO的read是非阻塞式調用,select是阻塞式的,同步發生在select上。程序經過select調用同時監控一組sockets,任何一個socket發生註冊過的事件時,select由阻塞變爲ready,函數調用返回後程序能夠讀取IO了。前面提到的Nginx(使用epoll)和apache(使用select)都有使用這一IO策略。select/epoll這種IO策略還有另一個名字叫Reactor,具體他們之間的細節區別再另開一文。
異步非阻塞IO
對比同步非阻塞IO,異步非阻塞IO也有個名字--Proactor。這種策略是真正的異步,使用註冊callback/hook函數來實現異步。程序註冊本身感興趣的socket 事件時,同時將處理各類事件的handler也就是對應的函數也註冊給內核,不會有任何阻塞式調用。事件發生後內核之間調用對應的handler完成處理。這裏暫且理解爲內核作了event的調度和handler調用,具體究竟是異步IO庫如何作的,如何跟內核通訊的,後續繼續研究。
High Performance architecture
Threads vs. processes for program parallelization
WebServerArchitectures
Concurrent Programming for Scalable Web Architectures
Apache Architecture
Apache Modules
Ngnix Architecture
epoll編程,如何實現高併發服務器開發