nginx的io複用、阻塞非阻塞、同步非同步、apache與nginx

摘抄自博客園rikewang博客,方便自己查找閱讀!!!!

http://www.cnblogs.com/wxl-dede/p/5134636.html


同步異步,阻塞非阻塞 和nginx的IO模型





同步與異步

同步和異步關注的是消息通信機制 (synchronous communication/ asynchronous communication)。所謂同步,就是在發出一個*調用*時,在沒有得到結果之前,該*調用*就不返回。但是一旦調用返回,就得到返回值了。換句話說,就是由*調用者*主動等待這個*調用*的結果。而異步則是相反,*調用*在發出之後,這個調用就直接返回了,所以沒有返回結果。換句話說,當一個異步過程調用發出後,調用者不會立刻得到結果。而是在*調用*發出後,*被調用者*通過狀態、通知來通知調用者,或通過回調函數處理這個調用。

典型的異步編程模型比如Node.js

舉個通俗的例子:你打電話問書店老闆有沒有《分佈式系統》這本書,如果是同步通信機制,書店老闆會說,你稍等,"我查一下",然後開始查啊查,等查好了(可能是5秒,也可能是一天)告訴你結果(返回結果)。而異步通信機制,書店老闆直接告訴你我查一下啊,查好了打電話給你,然後直接掛電話了(不返回結果)。然後查好了,他會主動打電話給你。在這裏老闆通過"回電"這種方式來回調。


阻塞與非阻塞

阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態.阻塞調用是指調用結果返回之前,當前線程會被掛起。調用線程只有在得到結果之後纔會返回。非阻塞調用指在不能立刻得到結果之前,該調用不會阻塞當前線程。

還是上面的例子,你打電話問書店老闆有沒有《分佈式系統》這本書,你如果是阻塞式調用,你會一直把自己"掛起",直到得到這本書有沒有的結果,如果是非阻塞式調用,你不管老闆有沒有告訴你,你自己先一邊去玩了, 當然你也要偶爾過幾分鐘check一下老闆有沒有返回結果。在這裏阻塞與非阻塞與是否同步異步無關。跟老闆通過什麼方式回答你結果無關。


I/O模型

由於進程是不可直接訪問外部設備的,所以只能調用內核去調用外部的設備(上下文切換),然後外部設備比如磁盤,讀出存儲在設備自身的數據傳送給內核緩衝區,內核緩衝區在copy數據到用戶進程的緩衝區。在外部設備響應的給到用戶進程過程中,包含了兩個階段;由於數據響應方式的不同,所以就有了不同的I/O模型。

一般有五種I/O模型:

阻塞式I/O模型

默認情況下,所有套接字都是阻塞的。進程掛起,內核等待外部IO響應,IO完成傳送數據到kernel buffer,數據再從buffer複製到用戶的進程空間

非阻塞式I/O

在內核請求IO設備響應指令發出後,數據就開始準備,在此期間用戶進程沒有阻塞,也就是沒有掛起,它一值在詢問或者check數據有沒有傳送到kernel buffer中,忙等。但是第二個階段(數據從kernel buffer複製到用戶進程空間)依然是阻塞的。但這種IO模型會大量的佔用CPU的時間,效率很低效,很少使用。

I/O多路複用selectpollepoll...):

在內核請求IO設備響應指令發出後,數據就開始準備,在此期間用戶進程是阻塞的。數據從kernel buffer複製到用戶進程的過程也是阻塞的。但是和阻塞I/O所不同的是,它可以同時阻塞多個I/O操作而且可以同時對多個讀操作,多個寫操作的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操作函數,也就是說一個線程可以響應多個請求。

信號驅動式I/O(事件驅動)

第一階段是非阻塞的,當數據傳送的kernel buffer後,直接用信號的方式通知線程,用的不多

異步I/O:

在整個操作(包括將數據從內核拷貝到用戶空間)完成後才通知用戶進程

整個的彙總

Nginx中的epoll,poll,select

這三種模式都是屬於IO複用模型。

select,poll是主動查詢,它們可以同時查詢多個fd(文件句柄)的狀態,另外select有fd個數的限制,poll沒有限制。select和poll不同的是,他們創建的事件描述符不同,select創建讀、寫、異常三個集合,而poll在一個集合內設定三種描述,由於select和poll每個循環都會檢查事件的發生,而poll的事件比較少,性能上比select要好一些;

epoll是基於回調函數的,無輪詢。如果當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字註冊某個回調函數,當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll(Linux)、kqueue(FreeBSD)、/dev/poll(soloris)做的。舉個經典例子,假設你在大學讀書,住的宿舍樓有很多間房間,你的朋友要來找你。select版宿管大媽就會帶着你的朋友挨個房間去找,直到找到你爲止。而epoll版宿管大媽會先記下每位同學的房間號,你的朋友來時,只需告訴你的朋友你住在哪個房間即可,不用親自帶着你的朋友滿大樓找人。如果來了10000個人,都要找自己住這棟樓的同學時,select版和epoll版宿管大媽,誰的效率更高,不言自明。同理,在高併發服務器中,輪詢I/O是最耗時間的操作之一,select、epoll、/dev/poll的性能誰的性能更高,同樣十分明瞭。

web一般請求流程

  1. 首先我們客戶端發送一個請求到Web服務器,請求首先是到網卡。

  2. 網卡將請求交由內核空間的內核處理,其實就是拆包了,發現請求的是80端口。

  3. 內核便將請求發給了在用戶空間的Web服務器,Web服務器解包發現客戶端請求的index.html頁面、

  4. Web服務器便進行系統調用將請求發給內核

  5. 內核發現在請求的是一頁面,便調用磁盤的驅動程序,連接磁盤

  6. 內核通過驅動調用磁盤取得的頁面文件

  7. 內核將取得的頁面文件保存在自己的緩存區域中便通知Web進程或線程來取相應的頁面文件

  8. Web服務器通過系統調用將內核緩存中的頁面文件複製到進程緩存區域中

  9. Web服務器取得頁面文件來響應用戶,再次通過系統調用將頁面文件發給內核

  10. 內核進程頁面文件的封裝並通過網卡發送出去

  11. 當報文到達網卡時通過網絡響應給客戶端

 

Apache和nginx比較:

由於web服務器是一對多的關係,通常完成並行處理的方式有多進程、多線程、異步三種方式。

多進程:多進程就是每個進程對應一個連接來處理請求,進程獨立響應自己的請求,一個進程掛了,並不會影響到其他的請求;而且設計簡單,不會產生內存泄漏等問題,因此進程比較穩定。但是進程在創建的時候一般是fork機制,會存在內存複製的問題,另外在高併發的情況下,上下文切換將很頻繁,這樣將消耗很多的性能和時間。早期的apache使用的prework模型就多進程方式,但是apache會預先創建幾個進程,等待用戶的響應,請求完畢,進程也不會結束。因此性能上有優化很多。

多線程:每個線程響應一個請求,由於線程之間共享進程的數據,所以線程的開銷較小,性能就會提高。由於線程管理需要程序自己申請和釋放內存,所以當存在內存等問題時,可能會運行很長時間纔會暴露問題,所以在一定程度上還不是很穩定。apache的worker模式就是這種方式

異步的方式:nginx的epoll,apache的event也支持,不多說了

Nginx的IO模型是基於事件驅動的,使得應用程序在多個IO句柄間快速切換,實現所謂的異步IO。事件驅動服務器,最適合做的就是IO密集型工作,如反向代理,它在客戶端與WEB服務器之間起一個數據中轉作用,純粹是IO操作,自身並不涉及到複雜計算。反向代理用事件驅動來做,顯然更好,一個工作進程就可以run了,沒有進程、線程管理的開銷,CPU、內存消耗都小。

Apache這類應用服務器,一般要跑具體的業務應用,如科學計算、圖形圖像等。它們很可能是CPU密集型的服務,事件驅動並不合適。例如一個計算耗時2秒,那麼這2秒就是完全阻塞的,什麼event都沒用。想想MySQL如果改成事件驅動會怎麼樣,一個大型的join或sort就會阻塞住所有客戶端。這個時候多進程或線程就體現出優勢,每個進程各幹各的事,互不阻塞和干擾。當然,現代CPU越來越快,單個計算阻塞的時間可能很小,但只要有阻塞,事件編程就毫無優勢。所以進程、線程這類技術,並不會消失,而是與事件機制相輔相成,長期存在。

總的說來,事件驅動適合於IO密集型服務,多進程或線程適合於CPU密集型服務

其實也就是說nginx比較適合做前端代理,或者處理靜態文件(尤其高併發情況下),而apache適合做後端的應用服務器,功能強大[php, rewrite],穩定性高。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章