Nginx的异步非阻塞
轉(zhuǎn)載地址:https://blog.csdn.net/dutsoft/article/details/55224755
同步與異步
同步與異步的理解
同步與異步的重點(diǎn)在消息通知的方式上,也就是調(diào)用結(jié)果通知的方式。?
同步:當(dāng)一個(gè)同步調(diào)用發(fā)出去后,調(diào)用者要一直等待調(diào)用結(jié)果的通知后,才能進(jìn)行后續(xù)的執(zhí)行。?
異步:當(dāng)一個(gè)異步調(diào)用發(fā)出去后,調(diào)用者不能立即得到調(diào)用結(jié)果的返回。?
異步調(diào)用,要想獲得結(jié)果,一般有兩種方式:?
1、主動(dòng)輪詢異步調(diào)用的結(jié)果;?
2、被調(diào)用方通過callback來通知調(diào)用方調(diào)用結(jié)果。
生活實(shí)例
同步取快遞:小明收到快遞將送達(dá)的短信,在樓下一直等到快遞送達(dá)。?
異步取快遞:小明收到快遞將送達(dá)的短信,快遞到樓下后,小明再下樓去取。?
異步取快遞,小明知道快遞到達(dá)樓下有兩種方式:1、不停的電話問快遞小哥到了沒有,即主動(dòng)輪詢;2、快遞小哥到樓下后,打電話通知小明,然后小明下樓取快遞,即回調(diào)通知。
阻塞和非阻塞
阻塞與非阻塞的理解
阻塞與非阻塞的重點(diǎn)在于進(jìn)/線程等待消息時(shí)候的行為,也就是在等待消息的時(shí)候,當(dāng)前進(jìn)/線程是掛起狀態(tài),還是非掛起狀態(tài)。?
- 阻塞阻塞調(diào)用在發(fā)出去后,在消息返回之前,當(dāng)前進(jìn)/線程會(huì)被掛起,直到有消息返回,當(dāng)前進(jìn)/線程才會(huì)被激活?
- 非阻塞非阻塞調(diào)用在發(fā)出去后,不會(huì)阻塞當(dāng)前進(jìn)/線程,而會(huì)立即返回。
生活實(shí)例
阻塞取快遞:小明收到快遞即將送達(dá)的信息后,什么事都不做,一直專門等快遞。?
非阻塞取快遞:小明收到快遞即將送達(dá)的信息后,等快遞的時(shí)候,還一邊敲代碼、一邊刷微信。
同步與異步,重點(diǎn)在于消息通知的方式;阻塞與非阻塞,重點(diǎn)在于等消息時(shí)候的行為。?
所以,就有了下面4種組合方式
1. 同步阻塞:小明收到信息后,啥都不干,等快遞;
2. 同步非阻塞:小明收到信息后,邊刷微博,邊等著取快遞;
3. 異步阻塞:小明收到信息后,啥都不干,一直等著快遞員通知他取快遞;
4. 異步非阻塞:小明收到信息后,邊刷著微博,邊等快遞員通知他取快遞。
大部分程序的I/O模型都是同步阻塞的,單個(gè)進(jìn)程每次只在一個(gè)文件描述符上執(zhí)行I/O操作,每次I/O系統(tǒng)調(diào)用都會(huì)阻塞,直到完成數(shù)據(jù)傳輸。?
傳統(tǒng)的服務(wù)器采用的就是同步阻塞的多進(jìn)程模型。一個(gè)server采用一個(gè)進(jìn)程負(fù)責(zé)一個(gè)request的方式,一個(gè)進(jìn)程負(fù)責(zé)一個(gè)request,直到會(huì)話結(jié)束。進(jìn)程數(shù)就是并發(fā)數(shù),而操作系統(tǒng)支持的進(jìn)程數(shù)是有限的,且進(jìn)程數(shù)越多,調(diào)度的開銷也越大,因此無法面對(duì)高并發(fā)。
Nginx采用了異步非阻塞的方式工作。那么Nginx是如何實(shí)現(xiàn)異步非阻塞的呢?那得先了解一下I/O多路復(fù)用。
I/O多路復(fù)用
所謂的I/O復(fù)用,就是多個(gè)I/O可以復(fù)用一個(gè)進(jìn)程。I/O多路復(fù)用允許進(jìn)程同時(shí)檢查多個(gè)fd,以找出其中可執(zhí)行I/O操作的fd。?
系統(tǒng)調(diào)用select()和poll()來執(zhí)行I/O多路復(fù)用。在Linux2.6中引入的epoll()是select()的升級(jí)版,提供了更高的性能。通過I/O復(fù)用,我們可以在一個(gè)進(jìn)程處理大量的并發(fā)I/O。
初級(jí)版I/O復(fù)用
比如一個(gè)進(jìn)程接受了10000個(gè)連接,這個(gè)進(jìn)程每次從頭到尾的問一遍這10000個(gè)連接:“有I/O事件沒?有的話就交給我處理,沒有的話我一會(huì)再來問一遍。”然后進(jìn)程就一直從頭到尾問這10000個(gè)連接,如果這10000個(gè)連接都沒有I/O事件,就會(huì)造成CPU的空轉(zhuǎn),并且效率也很低,不好不好。
那么,如果發(fā)明一個(gè)代理,每次能夠知道哪個(gè)連接有了I/O流事件,不就可以避免無意義的空轉(zhuǎn)了嗎?為了避免CPU空轉(zhuǎn),可以引進(jìn)了一個(gè)代理(一開始有一位叫做select的代理,后來又有一位叫做poll的代理,不過兩者的本質(zhì)是一樣的)。
升級(jí)版I/O復(fù)用
select()
select可以同時(shí)觀察許多流的I/O事件,在空閑的時(shí)候,會(huì)把當(dāng)前線程阻塞掉,當(dāng)有一個(gè)或多個(gè)流有I/O事件時(shí),就從阻塞態(tài)中醒來,于是我們的程序就會(huì)輪詢一遍所有的流(于是我們可以把“忙”字去掉了)。
while true {
? select(streams[])
? for i in streams[] {
? ? ? ? ? ? if i has data
? ? ? ? ? ? ? ? ? read until unavailable
}
}
select()采用輪詢的方式來檢查fd是否就緒,當(dāng)fd數(shù)量較多時(shí),性能欠佳。因?yàn)閺膕elect那里僅僅知道了,有I/O事件發(fā)生了,但卻并不知道是那幾個(gè)流(可能有一個(gè),多個(gè),甚至全部),我們只能無差別輪詢所有流,找出能讀出數(shù)據(jù),或者寫入數(shù)據(jù)的流,對(duì)他們進(jìn)行操作。–from 知乎
生活實(shí)例
小明家樓下有一個(gè)收發(fā)室,每次有快遞到了就先代收,但收發(fā)室也不知道那個(gè)是小明的快遞;但小明去取的時(shí)候,要查詢所有代收的快遞。
高級(jí)版I/O復(fù)用
epoll()
epoll能更高效的檢查大量fd,UNIX中提供了類似功能的kqueue調(diào)用。?
epoll可以理解為event poll,不同于忙輪詢和無差別輪詢,當(dāng)連接有I/O流事件產(chǎn)生的時(shí)候,epoll就會(huì)去告訴進(jìn)程哪個(gè)連接有I/O流事件產(chǎn)生,然后進(jìn)程就去處理這個(gè)事件。此時(shí)我們對(duì)這些流的操作都是有意義的。(復(fù)雜度降低到了O(k),k為產(chǎn)生I/O事件的流的個(gè)數(shù),也有認(rèn)為O(1)的)
生活實(shí)例
小明家樓下有一個(gè)收發(fā)室,每次有快遞到了,就先代收并做了標(biāo)記;然后通知小明去取送給小明的快遞。
Nginx的異步非阻塞
Nginx配置use epoll后,以異步非阻塞方式工作,能夠輕松處理百萬級(jí)的并發(fā)連接。
events {
? ? worker_connections ?1024;
? ? use kqueue; ?# 在Linux中配置:use epoll;
}
在一個(gè)Web服務(wù)中,延遲最多的就是等待網(wǎng)絡(luò)傳輸。nginx在啟動(dòng)后,會(huì)有一個(gè)master進(jìn)程和多個(gè)worker進(jìn)程。master進(jìn)程主要用來管理worker進(jìn)程,包含:接收來自外界的信號(hào),向各worker進(jìn)程發(fā)送信號(hào),監(jiān)控worker進(jìn)程的運(yùn)行狀態(tài),當(dāng)worker進(jìn)程退出后(異常情況下),會(huì)自動(dòng)重新啟動(dòng)新的worker進(jìn)程。而基本的網(wǎng)絡(luò)事件,則是放在worker進(jìn)程中來處理了。在一個(gè)請(qǐng)求需要等待的時(shí)候,worker可以空閑出來處理其他的請(qǐng)求,少數(shù)幾個(gè)worker進(jìn)程就能夠處理大量的并發(fā)。
舉例來說:同樣的4個(gè)進(jìn)程,如果采用一個(gè)進(jìn)程負(fù)責(zé)一個(gè)request的方式;那么,同時(shí)進(jìn)來4個(gè)request之后,每個(gè)進(jìn)程就負(fù)責(zé)其中一個(gè),直至?xí)掙P(guān)閉。期間,如果有第5個(gè)request進(jìn)來了。就無法及時(shí)反應(yīng)了,因?yàn)?個(gè)進(jìn)程都沒干完活呢,因此,一般有個(gè)調(diào)度進(jìn)程,每當(dāng)新進(jìn)來了一個(gè)request,就新開個(gè)進(jìn)程來處理。
nginx不這樣,每進(jìn)來一個(gè)request,會(huì)有一個(gè)worker進(jìn)程去處理。但不是全程的處理,處理到什么程度呢?處理到可能發(fā)生阻塞的地方。比如向后端服務(wù)器轉(zhuǎn)發(fā)request,并等待請(qǐng)求返回。那么,這個(gè)處理的worker不會(huì)這么傻等著,他會(huì)在發(fā)送完請(qǐng)求后,注冊(cè)一個(gè)事件:“如果upstream返回了,告訴我一聲,我再接著干”。于是他就休息去了。此時(shí),如果再有request 進(jìn)來,他就可以很快再按這種方式處理。而一旦上游服務(wù)器返回了,就會(huì)觸發(fā)這個(gè)事件,worker才會(huì)來接手,這個(gè)request才會(huì)接著往下走。
總結(jié)
web server剛好屬于網(wǎng)絡(luò)io密集型應(yīng)用,不算是計(jì)算密集型。web server的這種性質(zhì)決定了每個(gè)request的大部份時(shí)間都消耗在網(wǎng)絡(luò)傳輸中,實(shí)際上花費(fèi)在server機(jī)器上的時(shí)間片不多。異步非阻塞,使用epoll,和大量細(xì)節(jié)處的優(yōu)化,這就是Nginx幾個(gè)進(jìn)程就解決高并發(fā)的秘密所在。
總結(jié)
以上是生活随笔為你收集整理的Nginx的异步非阻塞的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vim 打开文件末尾带有^M的解决办法
- 下一篇: Nginx——debug的使用