線程模型簡(jiǎn)介
原聲NIO存在的問(wèn)題
- NIO的類庫(kù)和API繁雜, 使用麻煩: 需要熟練賬號(hào) Selector嗜憔、ServerSocketChannel桅打、SocketChannel晚缩、ByteBuffer等
- 需要具備其他的額外技能: 要熟練Java多線程編程溢谤、因?yàn)镹IO編程涉及到Reactor模式, 你必須對(duì)多線程和網(wǎng)絡(luò)編程非常熟悉, 才能編寫出高質(zhì)量的NIO程序.
- 開發(fā)工作量和難度都非常大 : 例如客戶端面臨斷連重連、網(wǎng)絡(luò)閃斷伶唯、半包讀寫觉既、失敗緩存、網(wǎng)絡(luò)擁塞和異常流的處理等等
- JDK NIO 的bug : Epoll Bug, 它會(huì)導(dǎo)致Selector空輪詢, 最終導(dǎo)致CPU 100%
原因: 在NIO中通過(guò)Selector的輪詢當(dāng)前是否有IO事件乳幸,根據(jù)JDK NIO api描述瞪讼,Selector的select方法會(huì)一直阻塞,直到IO事件達(dá)到或超時(shí)粹断,但是在Linux平臺(tái)上這里有時(shí)會(huì)出現(xiàn)問(wèn)題符欠,在某些場(chǎng)景下select方法會(huì)直接返回,即使沒(méi)有超時(shí)并且也沒(méi)有IO事件到達(dá)瓶埋,這就是著名的epoll bug希柿,這是一個(gè)比較嚴(yán)重的bug诊沪,它會(huì)導(dǎo)致線程陷入死循環(huán),會(huì)讓CPU飆到100%狡汉,極大地影響系統(tǒng)的可靠性娄徊,到目前為止,JDK都沒(méi)有完全解決這個(gè)問(wèn)題盾戴。
線程模型
傳統(tǒng)阻塞I/O服務(wù)模型
Reactor模型
單Reactor 單線程
單Reactor 多線程
主從 Reactor 多線程
傳統(tǒng)阻塞I/O服務(wù)模型
采用阻塞 IO 模式獲取輸入的數(shù)據(jù), 每個(gè)連接都需要獨(dú)立的線程完成數(shù)據(jù)的輸入 , 業(yè)務(wù)處理和數(shù)據(jù)返回工作.
存在問(wèn)題:
當(dāng)并發(fā)數(shù)較大時(shí), 就會(huì)創(chuàng)建大量的線程, 占用很大系統(tǒng)資源
連接創(chuàng)建后, 如果當(dāng)前線程暫時(shí)沒(méi)有數(shù)據(jù)可讀, 該線程會(huì)阻塞在read操作, 造成線程資源浪費(fèi)
Reactor模型
Reactor模式使用IO復(fù)用監(jiān)聽事件, 收到事件后, 分發(fā)給某個(gè)線程(進(jìn)程), 這點(diǎn)就是網(wǎng)絡(luò)服務(wù)器搞并發(fā)處理關(guān)鍵
Reactor模型-單Reactor 單線程
- Selector是可以實(shí)現(xiàn)應(yīng)用程序通過(guò)一個(gè)阻塞對(duì)象監(jiān)聽多路連接請(qǐng)求
- Reactor對(duì)象通過(guò)Selector監(jiān)控客戶端請(qǐng)求事件, 收到事件后通過(guò)DIspatch進(jìn)行分發(fā)
- 建立連接請(qǐng)求事件, 則由Acceptor通過(guò)Accept處理連接請(qǐng)求, 然后創(chuàng)建一個(gè)Handler對(duì)象處理連接完成后的后續(xù)業(yè)務(wù)處理
- Handler會(huì)完成Read->業(yè)務(wù)處理->Send的完整業(yè)務(wù)流程
優(yōu)點(diǎn)
- 模型簡(jiǎn)單, 沒(méi)有多線程、進(jìn)程通信兵多、競(jìng)爭(zhēng)的問(wèn)題, 全部都在一個(gè)線程中完成
缺點(diǎn)
- 性能問(wèn)題 : 只有一個(gè)線程, 無(wú)法完全發(fā)揮多核CPU的性能. Handler在處理某個(gè)連接上的業(yè)務(wù)時(shí), 整個(gè)進(jìn)程無(wú)法處理其他連接事件, 很容易導(dǎo)致性能瓶頸
- 可靠性問(wèn)題 : 線程意外終止或者進(jìn)入死循環(huán), 會(huì)導(dǎo)致整個(gè)系統(tǒng)通信模塊不可用, 不能接受和處理外部消息, 造成節(jié)點(diǎn)故障.
Reactor模型-單Reactor 多線程
- Reactor 對(duì)象通過(guò) selector 監(jiān)控客戶端請(qǐng)求事件, 收到事件后尖啡,通過(guò) dispatch 進(jìn)行分發(fā)
- 如果建立連接請(qǐng)求, 則右 Acceptor 通過(guò)accept 處理連接請(qǐng)求
- 如果不是連接請(qǐng)求,則由 reactor 分發(fā)調(diào)用連接對(duì)應(yīng)的 handler 來(lái)處理
- handler 只負(fù)責(zé)響應(yīng)事件剩膘,不做具體的業(yè)務(wù)處理, 通過(guò) read 讀取數(shù)據(jù)后衅斩,會(huì)分發(fā)給后面的 worker 線程池的某個(gè)線程處理業(yè)務(wù)
- worker 線程池會(huì)分配獨(dú)立線程完成真正的業(yè)務(wù),并將結(jié)果返回給 handler
- handler 收到響應(yīng)后怠褐,通過(guò) send 將結(jié)果返回給 client
優(yōu)點(diǎn)
- 可以充分利用多核CPU的處理能力
缺點(diǎn)
- 多線程數(shù)據(jù)共享和訪問(wèn)比較復(fù)雜, reactor處理所有的事件的監(jiān)聽和響應(yīng), 在單線程運(yùn)行, 在高并發(fā)場(chǎng)景容易出現(xiàn)性能瓶頸
Reactor模型- 主從Reactor 多線程
- Reactor 主線程 MainReactor 對(duì)象通過(guò) select 監(jiān)聽客戶端連接事件畏梆,收到事件后,通過(guò) Acceptor 處理客戶端連接事件
- 當(dāng) Acceptor 處理完客戶端連接事件之后(與客戶端建立好 Socket 連接)奈懒,MainReactor 將 連接分配給 SubReactor奠涌。(即:MainReactor 只負(fù)責(zé)監(jiān)聽客戶端連接請(qǐng)求,和客戶端建立連 接之后將連接交由 SubReactor 監(jiān)聽后面的 IO 事件磷杏。)
- SubReactor 將連接加入到自己的連接隊(duì)列進(jìn)行監(jiān)聽溜畅,并創(chuàng)建 Handler 對(duì)各種事件進(jìn)行處理 當(dāng)連接上有新事件發(fā)生的時(shí)候,SubReactor 就會(huì)調(diào)用對(duì)應(yīng)的 Handler 處理
- Handler 通過(guò) read 從連接上讀取請(qǐng)求數(shù)據(jù)极祸,將請(qǐng)求數(shù)據(jù)分發(fā)給 Worker 線程池進(jìn)行業(yè)務(wù)處理 Worker 線程池會(huì)分配獨(dú)立線程來(lái)完成真正的業(yè)務(wù)處理慈格,并將處理結(jié)果返回給 Handler。 Handler 通過(guò) send 向客戶端發(fā)送響應(yīng)數(shù)據(jù)
- 一個(gè) MainReactor 可以對(duì)應(yīng)多個(gè) SubReactor遥金,即一個(gè) MainReactor 線程可以對(duì)應(yīng)多個(gè) SubReactor 線程
優(yōu)點(diǎn)
- MainReactor線程與SubReactor線程的數(shù)據(jù)交互簡(jiǎn)單職責(zé)明確, MainReactor線程只需要接受新連接, SubReactor線程完成后續(xù)的業(yè)務(wù)處理
- MainReactor 線程與SubReactor線程的數(shù)據(jù)交互簡(jiǎn)單, MainReactor線程只需要把新連接傳給SubReactor線程, SubReactor線程無(wú)需返回?cái)?shù)據(jù)
- 多個(gè)SubReactor線程能夠應(yīng)對(duì)更高的并發(fā)請(qǐng)求
缺點(diǎn)
- 這種模式的缺點(diǎn)是編程復(fù)雜度較高, 但是由于優(yōu)點(diǎn)明顯, 在許多項(xiàng)目中被廣泛使用, 包括Nginx浴捆、Memcached、Netty等, 這種模式也叫做服務(wù)器的1+M+N線程模式, 即使用該模式開發(fā)的服務(wù)器包含一個(gè)(或多個(gè), 1只是表示相對(duì)較少)連接建立線程+M個(gè)IO線程+N個(gè)業(yè)務(wù)處理線程, 這是業(yè)界成熟的服務(wù)器程序設(shè)計(jì)模式.