IO分別表示輸入(input)和輸出(output)。它描述的是計算機的數據流動的過程,所以IO第一大特徵是有數據的流動;那麼對於IO的整個過程大致上分爲2個部分,第一個部分爲IO的調用,第二個過程爲IO的執行。IO的調用指的就是系統調用,IO的執行指的是在內核中相關數據的處理過程,這個過程是由操做系統完成的,與程序員無關。php
IO多路複用是指內核一旦發現進程指定的一個或者多個IO條件準備讀取,它就通知該進程,目前支持I/O多路複用的系統調用有select
、poll
、epoll
,I/O多路複用就是經過一種機制,一個進程能夠監視多個描述符(socket),一旦某個描述符就緒(通常是讀就緒或者寫就緒),可以通知程序進行相應的讀寫操做。java
描述符(socket)在windows中能夠叫作句柄。咱們能夠理解成一個文件對應的ID。IO其實就是對
能夠先看之前寫的文章:對同步 異步 阻塞 非阻塞在網絡中的理解linux
阻塞IO:請求進程一直等待IO準備就緒。
非阻塞IO:請求進程不會等待IO準備就緒。
同步IO操做:致使請求進程阻塞,直到IO操做完成。
異步IO操做:不致使請求進程阻塞。nginx
這裏的程序就是一次完整的IO,一個函數爲IO在執行過程當中的一個獨立的小片斷。程序員
咱們知道在Linux操做系統中內存分爲內核空間
和用戶空間
,而全部的IO操做都得得到內核的支持,可是因爲用戶態的進程沒法直接進行內核的IO操做,因此內核空間提供了系統調用,使得處於用戶態的進程能夠間接執行IO操做,IO調用的目的是將進程的內部數據遷移到外部即輸出,或將外部數據遷移到進程內部即輸入。而在這裏討論的數據一般是socket進程內部的數據。web
在上圖中,每個客戶端會與服務端創建一次socket鏈接,而服務端獲取鏈接後,對於全部的數據的讀取都得通過操做系統的內核,經過系統調用內核將數據複製到用戶進程的緩衝區,而後才完成客戶端的進程與客戶端的交互。那麼根據系統調用的方式的不一樣分爲阻塞和非阻塞,根據系統處理應用進程的方式不一樣分爲同步和異步。segmentfault
每一次客戶端產生的socket鏈接其實是一個文件描述符fd,而每個用戶進程讀取的實際上也是一個個文件描述符fd,在該時期的系統調用函數會等待網絡請求的數據的到達和數據從內核空間複製到用戶進程空間,也就是說,不管是第一階段的IO調用仍是第二階段的IO執行都會阻塞,那麼就像圖中所畫的同樣,對於多個客戶端鏈接,只能開闢多個線程來處理。windows
對於阻塞IO模型來講最大的問題就體如今阻塞2字上,那麼爲了解決這個問題,系統的內核所以發生了改變。在內核中socket支持了非阻塞狀態。既然這個socket是不阻塞的了,那麼就可使用一個進程處理客戶端的鏈接,該進程內部寫一個死循環,不斷的詢問每個鏈接的網絡數據是否已經到達。此時輪詢發生在用戶空間,可是該進程依然須要本身處理全部的鏈接,因此該時期爲同步非阻塞IO時期,也即爲NIO。後端
在非阻塞IO模型中,雖然解決了IO調用阻塞的問題,可是產生了新的問題,若是如今有1萬個鏈接,那麼用戶線程會調用1萬次的系統調用read來進行處理,在用戶空間這種開銷太大,那麼如今須要解決這個問題,思路就是讓用戶進程減小系統調用,可是用戶本身是實現不了的,因此這就致使了內核發生了進一步變化。在內核空間中幫助用戶進程遍歷全部的文件描述符,將數據準備好的文件描述符返回給用戶進程。該方式是同步阻塞IO,由於在第一階段的IO調用會阻塞進程。數組
IO多路複用是指內核一旦發現進程指定的一個或者多個IO條件準備讀取,它就通知該進程,目前支持I/O多路複用的系統調用有select
、poll
、epoll
。
,I/O多路複用就是經過一種機制,一個進程能夠監視多個描述符(socket),一旦某個描述符就緒(通常是讀就緒或者寫就緒),可以通知程序進行相應的讀
寫操做。
爲了讓內核幫助用戶進程完成文件描述符的遍歷,內核增長了系統調用select/poll(select與poll本質上沒有什麼不一樣,就是poll減小了文件描述符的個數限制),如今用戶進程只須要調用select系統調用函數,而且將文件描述符所有傳遞給select就可讓內核幫助用戶進程完成全部的查詢,而後將數據準備好的文件描述符再返回給用戶進程,最後用戶進程依次調用其餘系統調用函數完成IO的執行過程。
在select實現的多路複用中依然存在一些問題。
一、用戶進程須要傳遞全部的文件描述符,而後內核將數據準備好的文件描述符再次傳遞回去,這種數據的拷貝下降了IO的速度。 二、內核依然會執行復雜度爲O(n)的主動遍歷操做。
對於第一個問題,提出了一個共享空間的概念,這個空間爲用戶進程和內核進程所共享,而且提供了mmap系統調用,實現用戶空間和內核空間到共享空間的映射,這樣用戶進程就能夠將1萬個文件描述符寫到共享空間中的紅黑樹上,而後內核將準備就緒的文件描述符寫入共享空間的鏈表中,而用戶進程發現鏈表中有數據了就直接讀取而後調用read執行IO便可。
對於第二個問題,內核引入了事件驅動機制(相似於中斷),再也不主動遍歷全部的文件描述符,而是經過事件驅動的方式主動通知內核該文件描述符的數據準備完畢了,而後內核就將其寫入鏈表中便可。
對於epoll來講在第一階段的epoll_wait依然是阻塞的,故也是同步阻塞式IO。
在IO執行的數據準備階段,不會阻塞用戶進程。當用戶進程須要等待數據的時候,會向內核發送一個信號,告訴內核須要數據,而後用戶進程就繼續作別的事情去了,而當內核中的數據準備好以後,內核立馬發給用戶進程一個信號,用戶進程收到信號以後,立馬調用recvfrom,去查收數據。該IO模型使用的較少。
應用進程經過 aio_read 告知內核啓動某個操做,而且在整個操做完成以後再通知應用進程,包括把數據從內核空間拷貝到用戶空間。信號驅動 IO 是內核通知咱們什麼時候能夠啓動一個 IO 操做,而異步 IO 模型是由內核通知咱們 IO 操做什麼時候完成。是真正意義上的無阻塞的IO操做,可是目前只有windows支持AIO,linux內核暫時不支持。
前四種模型的主要區別於第一階段,由於他們的第二階段都是同樣的:在數據從內核拷貝到應用進程的緩衝區期間,進程都會阻塞。相反,異步 IO 模型在這兩個階段都不會阻塞,從而不一樣於其餘四種模型。
直接內存並非虛擬機運行時數據區的一部分,也不是Java 虛擬機規範中農定義的內存區域。直接內存申請空間耗費更高的性能,直接內存IO讀寫的性能要優於普通的堆內存,對於java程序來講,系統內核讀取堆類的對象須要根據代碼段計算其偏移量來獲取對象地址,效率較慢,不太適合網絡IO的場景,對於直接內存來講更加適合IO操做,內核讀取存放在直接內存中的對象較爲方便,由於其地址就是裸露的進程虛擬地址,不須要jvm翻譯。那麼就可使用mmap開闢一塊直接內存mapbuffer和內核空間共享,而且該直接內存能夠直接映射到磁盤上的文件,這樣就能夠經過調用本地的put而不用調用系統調用write就能夠將數據直接寫入磁盤,RandomAccessFile類就是經過開闢mapbuffer實現的讀寫磁盤。
以消息隊列Kafka來講,有生產者和消費者,對於生產者,從網絡發來一個消息msg而且被拷貝到內核緩衝區,該消息經過Kafka調用recvfrom將內核中的msg讀到隊列中,而後加上消息頭head,再將該消息寫入磁盤。若是沒有mmap的話,就會調用一個write系統調用將該消息寫入內核緩衝區,而後內核將該消息再寫入磁盤。在此過程當中出現一次80中斷和2次拷貝。但實際上Kafka使用的是mmap開闢了直接內存到磁盤的映射,直接使用put將消息寫入磁盤。實際上也是經過內核訪問該共享區域將該消息寫入的磁盤。同時在Kafka中有一個概念叫segment,通常爲1G大小。它會充分利用磁盤的順序性,只追加數據,不修改數據。而mmap會直接開闢1G的直接內存,而且直接與segment造成映射關係,在segment滿了的時候再開闢一個新的segment,清空直接內存而後在與新的segment造成映射關係。
零拷貝描述的是CPU不執行拷貝數據從一個存儲區域到另外一個存儲區域的任務,這一般用於經過網絡傳輸一個文件時以減小CPU週期和內存帶寬。
在Kafka的消費者讀取數據的時候,若是當前消費者想讀取的數據是否是當前直接內存所映射的segment怎麼辦?若是沒有零拷貝的話,進程會先去調用read讀取,而後數據會從磁盤被拷貝到內核,而後內核再拷貝到Kafka隊列,進程再調用write將數據拷貝到內核緩衝區,最後再發送給消費者。實際上能夠發現,數據沒有必要讀到Kafka隊列,直接讀到內核的緩衝區的時候發送給消費者就好了。實際上,linux內核中有一個系統調用就是實現了這種方式讀取數據——sendfile,它有2個參數,一個是infd(讀取數據的文件描述符),一個是outfd(客戶端的socket文件描述符).消費者只需調用該函數,告訴它須要讀取那個文件就能夠不通過Kafka直接將數據讀到內核,而後由內核寫到消費者進程的緩衝區中。
Nginx 是一款自由的、開源的、高性能的HTTP服務器和反向代理服務器;同時也是一個IMAP、POP三、SMTP代理服務器; Nginx 能夠做爲一個HTTP服務器進行網站的發佈處理,另外 Nginx 能夠做爲反向代理進行負載均衡的實現。
一、靜態資源服務(經過本地文件系統提供服務)
二、緩存、負載均衡服務器
三、API服務(OpenResty)
一、阿里巴巴Tengine Tengine是由淘寶網發起的Web服務器項目。它在Nginx的基礎上,針對大訪問量網站的需求,添加了不少高級功能和特性。
二、openresty OpenResty® 是一個基於 Nginx 與 Lua 的高性能 Web 平臺,其內部集成了大量精良的 Lua 庫、第三方模塊以及大多數的依賴項。用於方便地搭建可以處理超高併發、擴展性極高的動態 Web 應用、Web 服務和動態網關。
web服務器和客戶端是一對多的關係,Web服務器必須有能力同時爲多個客戶端提供服務。通常來講完成並行處理請求工做有三種方式:
說明:
一、建立一個socket,綁定服務器端口(bind),監聽端口(listen),在PHP中用stream_socket_server一個函數就能完成上面3個步驟 二、進入while循環,阻塞在accept操做上,等待客戶端鏈接進入。此時程序會進入睡眠狀態,直到有新的客戶端發起connect到服務器,操做系統會喚 醒此進程。accept函數返回客戶端鏈接的socket 三、利用fread讀取客戶端socket當中的數據收到數據後服務器程序進行處理而後使用fwrite向客戶端發送響應。長鏈接的服務會持續與客戶端交互, 而短鏈接服務通常收到響應就會close。
缺點: 一次只能處理一個鏈接,不支持多個長鏈接同時處理
多進程方式指,服務器每當收到一個客戶端請求時,就有服務器主進程生成一個子進程出來和客戶端創建鏈接進行交互,直到鏈接斷開該子進程就結束了。
說明:
前面流程一致就不補充了 一、程序啓動後就會建立N個進程。每一個子進程進入 Accept,等待新的鏈接進入。當客戶端鏈接到服務器時,其中一個子進程會被喚醒,開始處理客戶端請求,而且再也不接受新的TCP鏈接。 當鏈接關閉時,子進程會釋放,從新進入 Accept,參與處理新的鏈接。 這個模型的優點是徹底能夠複用進程,不須要太多的上下文切換,好比php-fpm基於此模型來處理解析php.
多進程方式的優勢是設計簡單,各個子進程相對獨立,處理客戶端請求時彼此不受干擾;缺點是操做系統生成一個子進程須要進行內存複製等操做,在資源和時間上會產生必定的開銷;當有大量請求時,會致使系統性能降低;
例如:即時聊天程序,一臺服務器可能要維持數十萬的鏈接,那麼就要啓動數十萬的進程來維持。這顯然不可能
多線程方式指每當服務器接收到一個請求後,會由服務器主進程派生出一個線程出來和客戶端進行交互。因爲操做系統產生出一個線程的開銷遠遠小於一個進程的開銷。故多線程方式在很大程度上減輕了Web服務器對系統資源的要求。
缺點:穩定性!假設某個進程忽然關閉會形成整個進程中的全部線程都崩潰。
基於上面的模式咱們發現單個進程每次只能經過每次(accept)處理單個請求,有沒有辦法一次性鏈接多個請求,須要的時候再處理呢?
說明:
一、保存全部的socket,經過select模型,監聽socket描述符的可讀事件 二、Select會在內核空間監聽一旦發現socket可讀,會從內核空間傳遞至用戶空間,在用戶空間經過邏輯判斷是服務端socket可讀,仍是客戶端 的socket可讀 三、若是是服務端的socket可讀,說明有新的客戶端創建,將socket保留到監聽數組當中 四、若是是客戶端的socket可讀,說明當前已經能夠去讀取客戶端發送過來的內容了,經過fread讀取socket內容,而後fwrite響應給客戶端。
優勢:性能最好!一個進程或線程處理多個請求,不須要額外開銷,性能最好,資源佔用最低。缺點:穩定性!某個進程或線程出錯,可能致使大量請求沒法處理,甚至致使整個服務宕機,單進程對於大量任務處理乏力。
1.Nginx啓動後,會產生一個主進程,主進程執行一系列的工做後會產生一個或者多個工做進程 2.在客戶端請求動態站點的過程當中,Nginx服務器還涉及和後端服務器的通訊。Nginx將接收到的Web請求經過代理轉發到後端服務器,由後端服務器進行 數據處理和組織; 3.Nginx爲了提升對請求的響應效率,下降網絡壓力,採用了緩存機制,將歷史應答數據緩存到本地。保障對緩存文件的快速訪問
master進程主要用來管理 worker 進程,具體包括如下主要功能:
(1)接收來自外界的信號。 (2)處理配置文件讀取。 (3)建立,綁定和關閉套接字 (4)啓動,終止和維護配置的工做(worker)進程數 (5)當woker進程退出後(異常狀況下),會自動從新啓動新的woker進程
worker
進程的主要任務是完成具體的任務邏輯。其主要關注點是與客戶端或後端真實服務器(此時 worker
做爲中間代理)之間的數據可讀/可寫等I/O交互事件。
(1)接收客戶端請求; (2)將請求一次送入各個功能模塊進行過濾處理; (3)與後端服務器通訊,接收後端服務器處理結果; (4)數據緩存; (5)響應客戶端請求;
資料來源:
[1] https://segmentfault.com/a/11...[2] 六星教育