tomcat中的NIO發展
前言
Tomcat目前支持BIO(阻塞 I/O)、NIO(非阻塞 I/O)、AIO(異步非阻塞式IO,NIO的升級版)、APR(Apache可移植運行庫)模型,本文主要介紹NIO模型,目前NIO模型在各種分布式、通信、Java系統中有廣泛應用,如Dubbo、Jetty、Zookeeper等框架中間件中,都使用NIO的方式實現了基礎通信組件
BIO
傳統的BIO模型,每個請求都會創建一個線程,當線程向內核發起讀取數據申請時,在內核數據沒有準備好之前,線程會一直處于等待數據狀態,直到內核把數據準備好并返回
在Tomcat中,由Http11Protocol實現阻塞式的Http協議請求,通過傳統的ServerSocket的操作,根據傳入的參數設置監聽端口,如果端口合法且沒有被占用則服務監聽成功,再通過一個無限循環來監聽客戶端的連接,如果沒有客戶端接入,則主線程阻塞在ServerSocket的accept操作上(在Tomcat8、9的版本中已經不支持BIO)
第一次阻塞 connect調用:等待客戶端的連接請求,如果沒有客戶端連接,服務端將一直阻塞等待
第二次阻塞 accept調用:客戶端連接后,服務器會等待客戶端發送數據,如果客戶端沒有發送數據,那么服務端將會一直阻塞等待客戶端發送數據
在Tomcat中,維護了一個worker線程池來處理socket請求,如果worker線程池沒有空閑線程,則Acceptor將會阻塞,所以在有大量請求連接到服務器卻不發送消息(占用線程,阻塞與accept的調用)的情況下,會導致服務器壓力極大
NIO
NIO模型彌補了BIO模型的不足,它基于選擇器檢測連接(Socket)的就緒狀態通知線程處理,從而達到非阻塞的目的,Tomcat NIO基于I/O復用(select/poll/epoll)模型實現,在NIO中有以下幾個概念:
Channel
Chnnel是一個通道,網絡數據通過Channel讀取和寫入,通道和流的不同之處在于流是單向的,而通道是雙向的
Selector
多路復用器Selector會不斷輪詢注冊在其上的Channel,如果某個Channel上面發生讀或者寫,就表明這個Channel處于就緒狀態,會被Selector選擇,通過SelectionKey(通道監聽關鍵字)可以獲取就緒Channel的集合,再進行后續IO操作。那么只要有一個線程負責Selector輪詢,那么就可以接入成千上萬個客戶端
在Tomcat中,由NioEndpoint處理非阻塞 IO 的 HTTP/1.1 協議的請求
bind()的作用在于:開啟 ServerSocketChannel ,通過ServerSocketChannel 綁定地址、端口
startInternal()主要作用在于初始化連接,啟動工作線程池poller 線程組、acceptor 線程組。
acceptor用于監聽Socket連接請求,每個acceptor啟動以后就開始循環調用 ServerSocketChannel 的 accept() 方法獲取新的連接,然后調用 endpoint.setSocketOptions(socket) 處理新的連接,在endpoint.setSocketOptions(socket) 中 則會通過getPoller0().register(channel),將當前的NioChannel 注冊到Poller中,此邏輯在Acceptor .run()中處理
調用getPoller0().register(channel)后,請求socket被包裝為一個 PollerEvent,然后添加到 events 中,此過程是由poller線程去做的,poller 的 run() 會循環調用 events() 方法處理注冊到 Selector (每一個poller會開啟一個 Selector)上的channal ,監聽該 channel 的 OP_READ 事件,如果狀態為 readable,那么在 processKey ()中將該任務放到 worker 線程池中執行。整個過程大致如下圖所示
在NIO模型,不是一個連接就要對應一個處理線程了,連接會被注冊到Selector上面,當Selector監聽到有效的請求,才會分發一個對應線程去處理,當連接沒有請求時,是沒有工作線程來處理的
Tomcat中修改
在Tomcat中指定連接器使用的IO協議,可以通過server.xml的《connector》元素中的protocol屬性進行指定,默認值是HTTP/1.1,表明當前版本的默認協議,可以通過把HTTP/1.1修改為以下指定使用的IO協議
org.apache.coyote.http11.Http11Protocol:BIO
org.apache.coyote.http11.Http11NioProtocol:NIO
org.apache.coyote.http11.Http11Nio2Protocol:NIO2
org.apache.coyote.http11.Http11AprProtocol:APR
總結
BIO每個連接都會創建一個線程,對性能開銷大,不適合高并發場景。
NIO基于多路復用選擇器監測連接狀態在通知線程處理,當監控到連接上有請求時,才會分配一個線程來處理,利用少量的線程來管理了大量的連接,優化了IO的讀寫,但同時也增加CPU的計算,適用于連接數較多的場景