進程是具備必定獨立功能的,在計算機中已經運行的程序的實體。在早期系統中(如linux 2.4之前),進程是基本運做單位,在支持線程的系統中(如windows,linux2.6)中,線程纔是基本的運做單位,而進程只是線程的容器。程序 自己只是指令、數據及其組織形式的描述,進程纔是程序(那些指令和數據)的真正運行實例。若干進程有可能與同一個程序相關係,且每一個進程皆能夠同步(循 序)或異步(平行)的方式獨立運行。現代計算機系統可在同一段時間內以進程的形式將多個程序加載到存儲器中,並藉由時間共享(或稱時分複用),以在一個處 理器上表現出同時(平行性)運行的感受。一樣的,使用多線程技術(多線程即每個線程都表明一個進程內的一個獨立執行上下文)的操做系統或計算機架構,同 樣程序的平行線程,可在多 CPU 主機或網絡上真正同時運行(在不一樣的CPU上)。html
Web服務器要爲用戶提供服務,必須以某種方式,工做在某個套接字上。通常Web服務器在處理用戶請求是,通常有以下三種方式可選擇:多進程方式、多線程方式、異步方式。linux
優勢: 穩定性!因爲採用獨立進程處理獨立請求,而進程之間是獨立的,單個進程問題不會影響其餘進程,所以穩定性最好。nginx
缺點: 資源佔用!當請求過大時,須要大量的進程處理請求,進程生成、切換開銷很大,並且進程間資源是獨立的,形成內存重複利用。web
優勢:開銷較小!線程間部分數據是共享的,且線程生成與線程間的切換所需資源開銷比進程間切換小得多。apache
缺點:穩定性!線程切換過快可能形成線程抖動,且線程過多會形成服務器不穩定。windows
優勢:性能最好!一個進程或線程處理多個請求,不須要額外開銷,性能最好,資源佔用最低。後端
缺點:穩定性!某個進程或線程出錯,可能致使大量請求沒法處理,甚至致使整個服務宕機。緩存
經過這樣的一個複雜過程,一次請求就完成了。服務器
簡單來講就是:用戶請求-->送達到用戶空間-->系統調用-->內核空間-->內核到磁盤上讀取網頁資源->返回到用戶空間->響應給用戶。上述簡單的說明了一下,客戶端向Web服務請求過程,在這個過程當中,有兩個I/O過程,一個就是客戶端請求的網絡I/O,另外一個就是Web服務器請求頁面的磁盤I/O。 下面咱們就來講說Linux的I/O模型。網絡
經過上面的對鏈接的處理分析,咱們知道工做在用戶空間的web服務器進程是沒法直接操做IO的,須要經過系統調用進行,其關係以下:
即進程向內核進行系統調用申請IO,內核將資源從IO調度到內核的buffer中(wait階段),內核還需將數據從內核buffer中複製(copy階段)到web服務器進程所在的用戶空間,纔算完成一次IO調度。這幾個階段都是須要時間的。根據wait和copy階段的處理等待的機制不一樣,可將I/O動做分爲以下五種模式:
這裏有必要先解釋一下阻塞、非阻塞,同步、異步、I/O的概念。
阻塞和非阻塞指的是執行一個操做是等操做結束再返回,仍是立刻返回。
好比餐館的服務員爲用戶點菜,當有用戶點完菜後,服務員將菜單給後臺廚師,此時有兩種方式:
第一種就是阻塞方式,第二種則是非阻塞的。
同步和異步又是另一個概念,它是事件自己的一個屬性。還拿前面點菜爲例,服務員直接跟廚師打交道,菜出來沒出來,服務員直接指導,但只有當廚師將 菜送到服務員手上,這個過程纔算正常完成,這就是同步的事件。一樣是點菜,有些餐館有專門的傳菜人員,當廚師炒好菜後,傳菜員將菜送到傳菜窗口,並通知服 務員,這就變成異步的了。其實異步還能夠分爲兩種:帶通知的和不帶通知的。前面說的那種屬於帶通知的。有些傳菜員幹活可能主動性不是很夠,不會主動通知 你,你就須要時不時的去關注一下狀態。這種就是不帶通知的異步。
對於同步的事件,你只能以阻塞的方式去作。而對於異步的事件,阻塞和非阻塞都是能夠的。非阻塞又有兩種方式:主動查詢和被動接收消息。被動不意味着 必定很差,在這裏它偏偏是效率更高的,由於在主動查詢裏絕大部分的查詢是在作無用功。對於帶通知的異步事件,二者皆可。而對於不帶通知的,則只能用主動查 詢。
回到I/O,無論是I仍是O,對外設(磁盤)的訪問均可以分紅請求和執行兩個階段。請求就是看外設的狀態信息(好比是否準備好了),執行纔是真正的 I/O操做。在Linux 2.6以前,只有「請求」是異步事件,2.6以後才引入AIO(asynchronous I/O )把「執行」異步化。別看Linux/Unix是用來作服務器的,這點上比Windows落後了好多,IOCP(Windows上的AIO,效率極高)在Win2000上就有了。因此學linux的別老以爲Windows這裏很差那裏很差(Windows的多線程機制也因爲linux)。
根據以上分析,I/O可分爲五種模型:
Linux上的前四種I/O模型的「執行」階段都是同步的,只有最後一種才作到了真正的全異步。第一種阻塞式是最原始的方法,也是最累的辦法。固然 累與不累要看針對誰。應用程序是和內核打交道的。對應用程序來講,這種方式是最累的,但對內核來講這種方式偏偏是最省事的。還拿點菜這事爲例,你就是應用 程序,廚師就是內核,若是你去了一直等着,廚師就省事了(不用同時處理其餘服務員的菜)。固然如今計算機的設計,包括操做系統,愈來愈爲終端用戶考慮了, 爲了讓用戶滿意,內核慢慢的承擔起愈來愈多的工做,IO模型的演化也是如此。
非阻塞I/O ,I/O複用,信號驅動式I/O其實都是非阻塞的,固然是針對「請求」這個階段。非阻塞式是主動查詢外設狀態。I/O複用裏的select,poll也是 主動查詢,不一樣的是select和poll能夠同時查詢多個fd(文件句柄)的狀態,另外select有fd個數的限制。epoll是基於回調函數的。信 號驅動式I/O則是基於信號消息的。這兩個應該能夠歸到「被動接收消息」那一類中。最後就是偉大的AIO的出現,內核把什麼事都幹了,對上層應用實現了全 異步,性能最好,固然複雜度也最高。
說明:應用程序調用一個IO函數,致使應用程序阻塞,等待數據準備好。 若是數據沒有準備好,一直等待數據準備好了,從內核拷貝到用戶空間,IO函數返回成功指示。這個不用多解釋吧,阻塞套接字。下圖是它調用過程的圖示: (注,通常網絡I/O都是阻塞I/O,客戶端發出請求,Web服務器進程響應,在進程沒有返回頁面以前,這個請求會處於一直等待狀態)
咱們把一個套接口設置爲非阻塞就是告訴內核,當所請求的I/O操做沒法完成時,不要將進程睡眠,而是返回一個錯誤。這樣咱們的I/O操做函數將不斷 的測試數據是否已經準備好,若是沒有準備好,繼續測試,直到數據準備好爲止。在這個不斷測試的過程當中,會大量的佔用CPU的時間,全部通常Web服務器都 不使用這種I/O模型。具體過程以下圖:
I/O複用模型會用到select或poll函數或epoll函數(Linux2.6之後的內核開始支持),這兩個函數也會使進程阻塞,可是和阻塞 I/O所不一樣的的,這兩個函數能夠同時阻塞多個I/O操做。並且能夠同時對多個讀操做,多個寫操做的I/O函數進行檢測,直到有數據可讀或可寫時,才真正 調用I/O操做函數。具體過程以下圖:
首先,咱們容許套接口進行信號驅動I/O,並安裝一個信號處理函數,進程繼續運行並不阻塞。當數據準備好時,進程會收到一個SIGIO信號,能夠在信號處理函數中調用I/O操做函數處理數據。具體過程以下圖:
當一個異步過程調用發出後,調用者不能馬上獲得結果。實際處理這個調用的部件在完成後,經過狀態、通知和回調來通知調用者的輸入輸出操做。具體過程以下圖:
從上圖中咱們能夠看出,能夠看出,越日後,阻塞越少,理論上效率也是最優。其五種I/O模型中,前三種屬於同步I/O,後二者屬於異步I/O。
阻塞I/O
非阻塞I/O
I/O複用(select和poll)
信號驅動I/O(SIGIO) (半異步)
異步I/O(aio) (真正的異步)
信號驅動 I/O 模式下,內核能夠複製的時候通知給咱們的應用程序發送SIGIO 消息。
異步 I/O 模式下,內核在全部的操做都已經被內核操做結束以後纔會通知咱們的應用程序。
注,其中iocp是Windows實現的,select、poll、epoll是Linux實現的,kqueue是FreeBSD實現的,/dev /poll是SUN的Solaris實現的。select、poll對應第3種(I/O複用)模型,iocp對應第5種(異步I/O)模型,那麼 epoll、kqueue、/dev/poll呢?其實也同select屬於同一種模型,只是更高級一些,能夠看做有了第4種(信號驅動I/O)模型的某 些特性,如callback機制。
答案是,他們無輪詢。由於他們用callback取代了。想一想看,當套接字比較多的時候,每次select()都要經過遍歷FD_SETSIZE個 Socket來完成調度,無論哪一個Socket是活躍的,都遍歷一遍。這會浪費不少CPU時間。若是能給套接字註冊某個回調函數,當他們活躍時,自動完成 相關操做,那就避免了輪詢,這正是epoll、kqueue、/dev/poll作的。這樣子說可能很差理解,那麼我說一個現實中的例子,假設你在大學讀 書,住的宿舍樓有不少間房間,你的朋友要來找你。select版宿管大媽就會帶着你的朋友挨個房間去找,直到找到你爲止。而epoll版宿管大媽會先記下 每位同窗的房間號,你的朋友來時,只需告訴你的朋友你住在哪一個房間便可,不用親自帶着你的朋友滿大樓找人。若是來了10000我的,都要找本身住這棟樓的 同窗時,select版和epoll版宿管大媽,誰的效率更高,不言自明。同理,在高併發服務器中,輪詢I/O是最耗時間的操做之一,select、 epoll、/dev/poll的性能誰的性能更高,一樣十分明瞭。
誠然,Windows的IOCP很是出色,目前不多有支持asynchronous I/O的系統,可是因爲其系統自己的侷限性,大型服務器仍是在UNIX下。並且正如上面所述,kqueue、epoll、/dev/poll 與 IOCP相比,就是多了一層從內核copy數據到應用層的阻塞,從而不能算做asynchronous I/O類。可是,這層小小的阻塞無足輕重,kqueue、epoll、/dev/poll 已經作得很優秀了。
只有IOCP(windows實現)是asynchronous I/O,其餘機制或多或少都會有一點阻塞。
select(Linux實現)低效是由於每次它都須要輪詢。但低效也是相對的,視狀況而定,也可經過良好的設計改善
epoll(Linux實現)、kqueue(FreeBSD實現)、/dev/poll(Solaris實現)是Reacor模式,IOCP是Proactor模式。
Apache 2.2.9以前只支持select模型,2.2.9以後支持epoll模型
Nginx 支持epoll模型
Java nio包是select模型
咱們都知道Apache有三種工做模塊,分別爲prefork、worker、event。
若是不用「--with-mpm」顯式指定某種MPM,prefork就是Unix平臺上缺省的MPM.它所採用的預派生子進程方式也是 Apache1.3中採用的模式。prefork自己並無使用到線程,2.0版使用它是爲了與1.3版保持兼容性;另外一方面,prefork用單獨的子 進程來處理不一樣的請求,進程之間是彼此獨立的,這也使其成爲最穩定的MPM之一。
相對於prefork,worker是2.0版中全新的支持多線程和多進程混合模型的MPM。因爲使用線程來處理,因此能夠處理相對海量的請求,而 系統資源的開銷要小於基於進程的服務器。可是,worker也使用了多進程,每一個進程又生成多個線程,以得到基於進程服務器的穩定性,這種MPM的工做方 式將是Apache2.0的發展趨勢。
一個進程響應多個用戶請求,利用callback機制,讓套接字複用,請求過來後進程並不處理請求,而是直接交由其餘機制來處理,經過epoll機 制來通知請求是否完成;在這個過程當中,進程自己一直處於空閒狀態,能夠一直接收用戶請求。能夠實現一個進程程響應多個用戶請求。支持持海量併發鏈接數,消 耗更少的資源。
有幾個基本條件:
恰好,Nginx 支持以上全部特性。因此Nginx官網上說,Nginx支持50000併發,是有依據的。
傳統上基於進程或線程模型架構的web服務經過每進程或每線程處理併發鏈接請求,這勢必會在網絡和I/O操做時產生阻塞,其另外一個必然結果則是對內 存或CPU的利用率低下。生成一個新的進程/線程須要事先備好其運行時環境,這包括爲其分配堆內存和棧內存,以及爲其建立新的執行上下文等。這些操做都需 要佔用CPU,並且過多的進程/線程還會帶來線程抖動或頻繁的上下文切換,系統性能也會由此進一步降低。另外一種高性能web服務器/web服務器反向代 理:Nginx(Engine X),nginx的主要着眼點就是其高性能以及對物理計算資源的高密度利用,所以其採用了不一樣的架構模型。受啓發於多種操做系統設計中基於「事件」的高級 處理機制,nginx採用了模塊化、事件驅動、異步、單線程及非阻塞的架構,並大量採用了多路複用及事件通知機制。在nginx中,鏈接請求由爲數很少的 幾個僅包含一個線程的進程worker以高效的迴環(run-loop)機制進行處理,而每一個worker能夠並行處理數千個的併發鏈接及請求。
Nginx會按需同時運行多個進程:一個主進程(master)和幾個工做進程(worker),配置了緩存時還會有緩存加載器進程(cache loader)和緩存管理器進程(cache manager)等。全部進程均是僅含有一個線程,並主要經過「共享內存」的機制實現進程間通訊。主進程以root用戶身份運行,而worker、 cache loader和cache manager均應以非特權用戶身份運行。
注:若是負載以CPU密集型應用爲主,如SSL或壓縮應用,則worker數應與CPU數相同;若是負載以IO密集型爲主,如響應大量內容給客戶端,則worker數應該爲CPU個數的1.5或2倍。
Nginx的代碼是由一個核心和一系列的模塊組成, 核心主要用於提供Web Server的基本功能,以及Web和Mail反向代理的功能;還用於啓用網絡協議,建立必要的運行時環境以及確保不一樣的模塊之間平滑地進行交互。不過, 大多跟協議相關的功能和某應用特有的功能都是由nginx的模塊實現的。這些功能模塊大體能夠分爲事件模塊、階段性處理器、輸出過濾器、變量處理器、協 議、upstream和負載均衡幾個類別,這些共同組成了nginx的http功能。事件模塊主要用於提供OS獨立的(不一樣操做系統的事件機制有所不一樣) 事件通知機制如kqueue或epoll等。協議模塊則負責實現nginx經過http、tls/ssl、smtp、pop3以及imap與對應的客戶端 創建會話。在Nginx內部,進程間的通訊是經過模塊的pipeline或chain實現的;換句話說,每個功能或操做都由一個模塊來實現。例如,壓 縮、經過FastCGI或uwsgi協議與upstream服務器通訊,以及與memcached創建會話等。