【tomcat】03 Web服務器機制 之 服務器模型

一、介紹

1、I/O:可以分成阻塞I/O與非阻塞I/O兩大類型。阻塞I/O在做I/O讀寫操作時會使當前線程進入阻塞狀態,而非阻塞I/O則不進入阻塞狀態。

2、線程:單線程情況下由一條線程負責所有客戶端連接的I/O操作,而多線程情況下則由若干線程共同處理所有客戶端連接的I/O操作。

二、單線程阻塞I/O模型

1、單線程阻塞I/O模型是最簡單的一種服務器模型,只能同時處理一個客戶端訪問,並且在I/O操作上是阻塞的,線程會一直在等待,而不會做其他事情。對於多個客戶端訪問,必須要等到前一個客戶端訪問結束才能進行下一個訪問的處理,請求一個一個排隊,只提供一問一答服務。

2、流程

  1. 服務器必須初始化一個套接字服務器,並綁定某個端口號並使之監聽客戶端的訪問。
  1. 客戶端1調用服務器的服務,服務器接收到請求後對其進行處理,處理完後寫數據回客戶端1,整個過程都是在一個線程裏面完成的。
  1. 處理客戶端2的請求並寫數據回客戶端2,期間就算客戶端2在服務器處理完客戶端1之前就進行請求,也要等服務器對客戶端1響應完後纔會對客戶端2進行響應處理。

3、模型特點:它是最簡單的服務器模型,整個運行過程都只有一個線程,只能支持同時處理一個客戶端的請求(如果有多個客戶端訪問,就必須排隊等待),服務器系統資源消耗較小,但併發能力低,容錯能力差。

  1. 單線程即服務器端只有一個線程處理客戶端的所有請求,客戶端連接與服務器端的處理線程比是n:1,它無法同時處理多個連接,只能串行處理連接。
  1. 而阻塞I/O是指服務器在讀寫數據時是阻塞的,讀取客戶端數據時要等待客戶端發送數據並且把操作系統內核複製到用戶進程中,這時才解除阻塞狀態。
  1. 寫數據回客戶端時要等待用戶進程將數據寫入內核併發送到客戶端後才解除阻塞狀態。這種阻塞給網絡編程帶來了一個問題,服務器必須要等到客戶端成功接收才能繼續往下處理另外一個客戶端的請求,在此期間線程將無法響應任何客戶端請求。

三、多線程阻塞I/O模型

1、多線程模型的核心就是利用多線程機制爲每個客戶端分配一個線程。

服務器端開始監聽客戶端的訪問,假如有兩個客戶端發送請求過來,服務器端在接收到客戶端請求後分別創建兩個線程對它們進行處理,每條線程負責一個客戶端連接,直到響應完成。期間兩個線程併發地爲各自對應的客戶端處理請求,包括讀取客戶端數據、處理客戶端數據、寫數據回客戶端等操作。

2、每個線程執行到讀取或寫入操作時都將進入阻塞狀態,直到讀取到客戶端的數據或數據成功寫入客戶端後才解除阻塞狀態。這種模式比單線程處理的性能明顯高了,它不用等到第一個請求處理完才處理第二個,而是併發地處理客戶端請求,客戶端連接與服務器端處理線程的比例是1:1。

3、模型特點:I/O操作也是阻塞的,支持對多個客戶端併發響應,處理能力得到大幅提高,有較大的併發量,但服務器系統資源消耗量較大,而且多線程之間會產生線程切換成本,同時擁有較複雜的結構。

通過引入多線程確實提高了服務器端的併發處理能力,但每個連接都需要一個線程負責I/O操作。當連接數量較多時可能導致機器線程數量太多,而這些線程大多數時間卻處於等待狀態,造成極大的資源浪費。

四、單線程非阻塞I/O模型

1、單線程非阻塞I/O模型在調用讀取或寫入接口後立即返回,而不會進入阻塞狀態。

2、非阻塞情況下套接字事件的檢測機制

  1. 應用程序遍歷套接字的事件檢測

a:當多個客戶端向服務器請求時,服務器端會保存一個套接字連接列表中,應用層線程對套接字列表輪詢嘗試讀取或寫入。

b: 對於讀取操作,如果成功讀取到若干數據,則對讀取到的數據進行處理;如果讀取失敗,則下一個循環再繼續嘗試。

c: 對於寫入操作,先嚐試將數據寫入指定的某個套接字,寫入失敗則下一個循環再繼續嘗試。

d: 這種模型需要在應用程序中遍歷所有的套接字列表,同時需要處理數據的拼接,連接空閒時可能也會佔用較多CPU資源,不適合實際使用。

  1. 內核遍歷套接字的事件檢測

a: 將套接字的遍歷工作交給了操作系統內核,把對套接字遍歷的結果組織成一系列的事件列表並返回應用層處理。對於應用層,它們需要處理的對象就是這些事件,這就是一種事件驅動的非阻塞方式的實現。

b: 服務器端有多個客戶端連接,應用層向內核請求讀寫事件列表。內核遍歷所有套接字並生成對應的可讀列表readList和可寫列表writeList。readList標明瞭每個套接字是否可讀,如套接字1的值爲1,表示可讀,socket2的值爲0,表示不可讀。writeList則標明瞭每個套接字是否可寫。應用層遍歷讀寫事件列表readList和writeList,做相應的讀寫操作。

c: 需要將所有連接的可讀事件列表和可寫事件列表傳到應用層假如套接字連接數量變大,列表從內核複製到應用層也有不小的開銷。當活躍連接較少時,內核與應用層之間存在很多無效的數據副本,因爲將活躍和不活躍的連接狀態都複製到應用層中。

  1. 內核基於回調的事件檢測

a:內核中的套接字都對應一個回調函數,當客戶端往套接字發送數據時,內核從網卡接收數據後就會調用回調函數,在回調函數中維護事件列表,應用層獲取此事件列表即可得到所有感興趣的事件。

  1. 內核基於回調的事件檢測方式有兩種

方式一:

a: 用可讀列表readList和可寫列表writeList標記讀寫事件,套接字的數量與readList和writeList兩個列表的長度一樣,readList第一個元素標爲1則表示套接字1可讀,同理,writeList第二個元素標爲1則表示套接字2可寫。

b: 多個客戶端連接服務器端,當客戶端發送數據過來時,內核從網卡複製數據成功後調用回調函數將readList第一個元素置爲1,應用層發送請求讀、寫事件列表,返回內核包含了事件標識的readList和writeList事件列表,進而分表遍歷讀事件列表readList和寫事件列表writeList,對置爲1的元素對應的套接字進行讀或寫操作。避免了遍歷套接字的操作。

c: 仍然有大量無用的數據(狀態爲0的元素)從內核複製到應用層中。

方式二:

a: 服務器端有多個客戶端套接字連接。首先,應用層告訴內核每個套接字感興趣的事件。

b: 當客戶端發送數據過來時,對應會有一個回調函數,內核從網卡複製數據成功後即調回調函數將套接字1作爲可讀事件event1加入到事件列表。內核發現網卡可寫時就將套接字2作爲可寫事件event2添加到事件列表中。最後,應用層向內核請求讀、寫事件列表,內核將包含了event1和event2的事件列表返回應用層,應用層通過遍歷事件列表得知套接字1有數據待讀取,於是進行讀操作,而套接字2則可以寫入數據。

  1. java 角度

a: 對於Java來說,非阻塞I/O的實現完全是基於操作系統內核的非阻塞I/O,它將操作系統的非阻塞I/O的差異屏蔽並提供統一的API,讓我們不必關心操作系統。JDK會幫我們選擇非阻塞I/O的實現方式,

b: 如對於Linux系統,在支持epoll的情況下JDK會優先選擇用epoll實現Java的非阻塞I/O =>“內核基於回調的事件檢測”

3、一個線程:它通過把非阻塞讀寫操作與上面幾種檢測機制配合就可以實現對多個連接的及時處理,而不會因爲某個連接的阻塞操作導致其他連接無法處理。在客戶端連接大多數都保持活躍的情況下,這個線程會一直循環處理這些連接,很好地利用了阻塞的時間,大大提高了這個線程的執行效率。

4、主要優勢:在對多個連接的管理,一般在同時需要處理多個連接的發場景中會使用非阻塞NIO模式,此模型下只通過一個線程去維護和處理連接,這樣大大提高了機器的效率。一般服務器端纔會使用NIO模式,客戶端,可使用阻塞模式的套接字進行通信。

五、多線程非阻塞I/O模型

1、在多核的機器上可以通過多線程繼續提高機器效率。

2、做法就是將客戶端連接按組分配給若干線程,每個線程負責處理對應組內的連接。

3、有4個客戶端訪問服務器,服務器將套接字1和套接字2交由線程1管理,而線程2則管理套接字3和套接字4,通過事件檢測及非阻塞讀寫就可以讓每個線程都能高效處理。

A1
B
A2
A3
A4
線程一
線程二

4、多線程非阻塞I/O模型方式是Reactor模式。

  1. 單線程下的Reactor, Reactor將服務器端的整個處理過程分成若干個事件,如分爲接收事件、讀事件、寫事件、執行事件等。Reactor通過事件檢測機制將這些事件分發給不同處理器去處理。
  1. 若干客戶端連接訪問服務器端,Reactor負責檢測各種事件並分發到處理器,這些處理器包括接收連接的accept處理器、讀數據的read處理器、寫數據的write處理器以及執行邏輯的process處理器。在整個過程中只要有待處理的事件存在,即可以讓Reactor線程不斷往下執行,而不會阻塞在某處,所以處理效率很高。
客戶端1
Reactor 分發
客戶端2
客戶端3
accept處理器
read處理器
write處理器
process處理器
  1. 基於單線程Reactor模型,根據實際使用場景,改進成多線程模式。常見的有兩種方式:

a: 在耗時的process處理器中引入多線程,如使用線程池;
引入了一個線程池。由於對連接的接收、對數據的讀取和對數據的寫入等操作基本上都耗時較少,因此把它們都放到Reactor線程中處理。然而,對於邏輯處理可能比較耗時的工作,可以在process處理器中引入線程池,process處理器自己不執行任務,而是交給線程池,從而在Reactor線程中避免了耗時的操作。將耗時的操作轉移到線程池中後,儘管Reactor只有一個線程,也能保證Reactor的高效。

客戶端1
Reactor 分發
客戶端2
客戶端3
accept處理器
read處理器
write處理器
process處理器
線程池

b:直接使用多個Reactor實例,每個Reactor實例對應一個線程。
其中有多個Reactor實例,每個Reactor實例對應一個線程。因爲接收事件是相對於服務器端而言的,所以客戶端的連接接收工作統一由一個accept處理器負責,accept處理器會將接收的客戶端連接均勻分配給所有Reactor實例,每個Reactor實例負責處理分配到該Reactor上的客戶端連接,包括連接的讀數據、寫數據和邏輯處理。這就是多Reactor實例的原理。

客戶端1
accept處理器
客戶端2
客戶端3
Reactor 分發
read處理器
write處理器
process處理器
Reactor 分發
read處理器
write處理器
process處理器
  1. 多線程非阻塞I/O模式讓服務器端處理能力得到很大提高,它充分利用機器的CPU,適合用於處理高併發的場景,但它也讓程序更復雜,更容易出現問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章