Web APP編程模型和IO策略
現(xiàn)代大型高性能網(wǎng)站諸如淘寶,京東,微博,F(xiàn)B,知乎等等,網(wǎng)站架構(gòu)涉及很多知識。像業(yè)務(wù)分層,軟件分割模塊化,分布式部署,集群服務(wù)器,負載均衡等技術(shù)可以幫助架構(gòu)師將一個大的復(fù)雜的問題切分成小的簡單的問題。這篇文章著眼于解決這些切好的小問題上,單機上有哪些編程實踐或者模型可以很好的做到高并發(fā)。本人web開發(fā)小白一枚,寫文章是想梳理自己的思路,求得大牛斧正,希望各位多多批判。文章的內(nèi)容大多來自網(wǎng)上的閱讀加上些自己的理解,文末附上參考閱讀的文章。
一個極簡高并發(fā)模型
因為有數(shù)年的嵌入式領(lǐng)域的經(jīng)驗,先說一下我認為的比較高效的處理模型。
- 硬件環(huán)境:單機30core, 1G Hz。
- 軟件環(huán)境:6Wind fastpath,每個core上都是run-to-complete的endless loop.沒有操作系統(tǒng)。
- 功能:一個超級簡單的reverse proxy,具有l(wèi)oad balance的簡單功能。
衡量并發(fā)性能,我們看一下一個IP包從網(wǎng)口緩沖區(qū)收上來處理到發(fā)出去大約需要多長時間呢?
收+處理+發(fā)大概是500+1000+500=2000 cycles,時間也就是2us。單機1s內(nèi)可以支持30*(1s/2us)=15,000,000 request/s的并發(fā)。屌炸天的并發(fā)能力了吧!原因有兩個:
- 沒有操作系統(tǒng)overhead。
- 包處理簡單,IP層的處理,直接c函數(shù)調(diào)用,總共1000 cycle。
當(dāng)然這是從嵌入式得來的經(jīng)驗,web開發(fā)中不可能這樣,沒有Nginx,沒有web框架,沒有l(wèi)ib沒有各種open source,甚至沒有Linux?;氐皆忌鐣斐鲲w機大炮來,這不把web開發(fā)者逼瘋了。軟件也是一個社會化協(xié)作的過程,os,framework,lib,opensource給開發(fā)者帶來極大方便的同時,也伴隨著性能的開銷。如何在性能和可擴展性、維護性等其他指標找到一個平衡點,如何選擇合適的編程模型,合適的第三方模塊達到最小的overhead,這是成長為高手的開發(fā)者都會不斷思考的問題。
High Performance architecture,這篇文章總結(jié)了四個性能殺手:
- 數(shù)據(jù)復(fù)制
- 上下文切換
- 動態(tài)內(nèi)存分配
- 鎖競爭
上面的編程模型之所以高效,就是將CPU用到極致,盡量避免這4種情況發(fā)生。心中有這么一個極簡的高效模型,后面學(xué)習(xí)其他模式的時候可以暗做對比看一下到底會有哪些額外的開銷。
常用的server端Linux高并發(fā)編程模型
Nginx Vs Apache
大名鼎鼎的Nginx使用了多進程模型,主進程啟動時初始化,bind,監(jiān)聽一組sockets,然后fork一堆child processes(workers),workers共享socket descriptor。workers競爭accept_mutex,獲勝的worker通過IO multiplex(select/poll/epoll/kqueue/…)來處理成千上萬的并發(fā)請求。為了獲得高性能,Nginx還大量使用了異步,事件驅(qū)動,non-blocking IO等技術(shù)。”What resulted is a modular, event-driven, asynchronous, single-threaded, non-blocking architecture which became the foundation of nginx code.”

Nginx 架構(gòu)
對比著看一下Apache的兩種常用運行模式,詳見 Apache Modules
- 1. Apache MPM prefork模式
主進程通過進程池維護一定數(shù)量(可配置)的worker進程,每個worker進程負責(zé)一個connection。worker進程之間通過競爭mpm-accept mutex實現(xiàn)并發(fā)和鏈接處理隔離。 由于進程內(nèi)存開銷和切換開銷,該模式相對來說是比較低效的并發(fā)。

- 2. Apache MPM worker模式
由于進程開銷較大,MPM worker模式做了改進,處理每個connection的實體改為thread。主進程啟動可配數(shù)量的子進程,每個進程啟動可配數(shù)量的server threads和listen thread。listen threads通過競爭mpm-accept mutex獲取到新進的connection request通過queue傳遞給自己進程所在的server threads處理。由于調(diào)度的實體變成了開銷較小的thread,worker模式相對prefork具有更好的并發(fā)性能。
小結(jié)兩種webserver,可以發(fā)現(xiàn)Nginx使用了更高效的編程模型,worker進程一般跟CPU的core數(shù)量相當(dāng),每個worker駐留在一個core上,合理編程可以做到最小程度的進程切換,而且內(nèi)存的使用也比較經(jīng)濟,基本上沒有浪費在進程狀態(tài)的存儲上。而Apache的模式是每個connection對應(yīng)一個進程/線程,進程/線程間的切換開銷,大量進程/線程的內(nèi)存開銷,cache miss的概率增大,都限制了系統(tǒng)所能支持的并發(fā)數(shù)。
IO策略
由于IO的處理速度要遠遠低于CPU的速度,運行在CPU上的程序不得不考慮IO在準備暑假的過程中該干點什么,讓出CPU給別人還是自己去干點別的有意義的事情,這就涉及到了采用什么樣的IO策略。一般IO策略的選用跟進程線程編程模型要同時考慮,兩者是有聯(lián)系的。
同步阻塞IO

同步阻塞IO是比較常見的IO模型,網(wǎng)絡(luò)編程中如果創(chuàng)建的socket的描述符屬性設(shè)置為阻塞的,當(dāng)socket對應(yīng)的用戶空間緩沖區(qū)內(nèi)尚無可讀數(shù)據(jù)時,該進程/線程在系統(tǒng)調(diào)用read/recv socket時,會將自己掛起阻塞等待socket ready。
同步非阻塞IO和非阻塞IO同步復(fù)用
對比著同步阻塞IO,如果socket數(shù)據(jù)沒有ready,系統(tǒng)調(diào)用read/recv會直接返回,進程可以繼續(xù)執(zhí)行不會掛起讓出CPU。當(dāng)然這樣做對單個socket來說沒有多大的意義,如果要支持大量socket的并發(fā)就很有用了,也就是IO復(fù)用。select/poll/epoll就是這樣的應(yīng)用,IO的read是非阻塞式調(diào)用,select是阻塞式的,同步發(fā)生在select上。程序通過select調(diào)用同時監(jiān)控一組sockets,任何一個socket發(fā)生注冊過的事件時,select由阻塞變?yōu)閞eady,函數(shù)調(diào)用返回后程序可以讀取IO了。前面提到的Nginx(使用epoll)和apache(使用select)都有使用這一IO策略。select/epoll這種IO策略還有另外一個名字叫Reactor,具體他們之間的細節(jié)區(qū)別再另開一文。
異步非阻塞IO

對比同步非阻塞IO,異步非阻塞IO也有個名字—Proactor。這種策略是真正的異步,使用注冊callback/hook函數(shù)來實現(xiàn)異步。程序注冊自己感興趣的socket 事件時,同時將處理各種事件的handler也就是對應(yīng)的函數(shù)也注冊給內(nèi)核,不會有任何阻塞式調(diào)用。事件發(fā)生后內(nèi)核之間調(diào)用對應(yīng)的handler完成處理。這里暫且理解為內(nèi)核做了event的調(diào)度和handler調(diào)用,具體到底是異步IO庫如何做的,如何跟內(nèi)核通信的,后續(xù)繼續(xù)研究。
原創(chuàng)作者招募ing
歡迎各位朋友加入馬幫傳道者寫作團,秀出你的好身材,秀出你的好文章,讓大家發(fā)現(xiàn)你的美,美在文章中,美在不斷的自我挑戰(zhàn)中!
馬哥Linux運維微信公眾號致力于發(fā)送好文章,也會不斷發(fā)現(xiàn)身邊的好文章,發(fā)現(xiàn)優(yōu)秀的你,惠及更多的運維朋友們!
歡迎一起交流成長,一起愉快的玩耍!
原創(chuàng)投稿聯(lián)系magedu-小助手QQ:152260971