Netty Reactor 模型之主從多線程模型
1. 前言
通過(guò)上節(jié)的分析,我們知道單 Reactor 多線程模型它的性能瓶頸在于單個(gè) Reactor,本節(jié)主要講解如何進(jìn)行優(yōu)化單個(gè) Reactor 帶來(lái)的性能瓶頸問(wèn)題。
2. 單 Reactor 性能瓶頸
單 Reactor 主要存在的性能瓶頸如下:
- 壓力問(wèn)題: 客戶端數(shù)量比較多的情況,單個(gè) Reactor 負(fù)責(zé)監(jiān)聽和轉(zhuǎn)發(fā),那么 Reactor 壓力非常的大;
- 單點(diǎn)故障問(wèn)題: 如果 Reactor 發(fā)生故障,則即使后面的 Handler 和 Worker 正常工作,但是整個(gè)應(yīng)用程序無(wú)法正常對(duì)外提供服務(wù)。
3. 如何進(jìn)行優(yōu)化
思考:如何解決單 Reactor 性能問(wèn)題呢?
- 以 Tomcat 作為案例來(lái)進(jìn)行分析:
1.1 問(wèn)題: 我們平時(shí)把項(xiàng)目打包成 war 部署到單個(gè) Tomcat 來(lái)進(jìn)行運(yùn)行,在并發(fā)量很小的情況下是正常運(yùn)行的,但是一旦并發(fā)量達(dá)到 1k 以上,單個(gè) Tomcat 就會(huì)很吃力了,那怎么辦呢?
1.2 解決: 很簡(jiǎn)單,只需要在 Tomcat 前面加 Nginx 做負(fù)載轉(zhuǎn)發(fā),這樣的話,多個(gè) Tomcat 同時(shí)對(duì)外提供服務(wù),不但整體的性能得到提高,即使其中一個(gè) Tomcat 宕機(jī),但是整個(gè) Tomcat 集群還是能正常對(duì)外提供服務(wù)。 - 生活中飯館的案例進(jìn)行說(shuō)明:
還是以飯館經(jīng)營(yíng)模型說(shuō)明,方便大家更好的理解。
2.1 一個(gè)飯館只有一個(gè)老板,老板即兼職服務(wù)員和廚師的工作,整體效率很低,這就是單 Reactor 單線程模型;
2.2 一個(gè)負(fù)責(zé)迎接客戶、點(diǎn)菜、上菜的服務(wù)員(Reactor 線程),幾個(gè)廚師負(fù)責(zé)炒菜(Worker 線程),廚師輕松了,但是服務(wù)員依然忙不過(guò)來(lái),這就是單 Reactor 多線程模型;
2.3 一個(gè)負(fù)責(zé)迎接在門口迎接小妹妹(好比:Reactor 主線程),幾個(gè)專門負(fù)責(zé)點(diǎn)菜和上菜的服務(wù)員(好比:Reactor 從線程),幾個(gè)負(fù)責(zé)超出廚師(Worker 線程),那么每個(gè)崗位都會(huì)很輕松,并且還能服務(wù)更多的客戶進(jìn)行就餐,這就是主從 Reactor 多線程模型。
其實(shí),Reactor 模型也是類似道理,哪個(gè)環(huán)節(jié)性能存在瓶頸,那么將其功能再細(xì)分,并且增加執(zhí)行數(shù)量(集群)即可。
4. 主從多線程模型
架構(gòu)圖分析:
- 主要分為三個(gè)模塊,分別為 Reactor 主線程、Reactor 子線程、Worker 線程池。其中 Reactor 主線程可以對(duì)應(yīng)多個(gè) Reactor 子線程,也就是說(shuō),一個(gè) MainReactor 對(duì)應(yīng)多個(gè) SubReactor;
- Reactor 主線程的 MainReactor 對(duì)象通過(guò) select 監(jiān)聽客戶端連接事件,收到事件之后,通過(guò) Acceptor 處理連接事件;
- 當(dāng) Acceptor 處理連接事件之后,MainReactor 將連接事件分配給 Reactor 子線程的 SubReactor 進(jìn)行處理;
- SubReactor 將連接加入到連接隊(duì)列進(jìn)行監(jiān)聽,并且創(chuàng)建 Handler 處理對(duì)應(yīng)的事件。一旦有新的事件(非連接)則分配給 Handler 進(jìn)行處理;
- Handler 通過(guò) read () 方法讀取數(shù)據(jù),并且分發(fā)給 Worker 線程池去做業(yè)務(wù)處理;
- Worker 線程池分配線程去處理業(yè)務(wù),處理完成之后把結(jié)果返回給 Handler;
- Handler 收到 Worker 線程返回的結(jié)果之后,再通過(guò) send () 方法返回給客戶端。
方案的優(yōu)點(diǎn):
- 責(zé)任明確,單一功能拆分的更細(xì),Reactor 主線程負(fù)責(zé)接收請(qǐng)求,不負(fù)責(zé)處理請(qǐng)求;Reactor 子線程負(fù)責(zé)處理請(qǐng)求。并發(fā)量很高的情況,可以減輕單個(gè) Reactor 的壓力,并且提高處理速度;
- Reactor 子線程只負(fù)責(zé)讀取數(shù)據(jù)和響應(yīng)數(shù)據(jù),耗時(shí)的業(yè)務(wù)處理則丟給 Worker 線程池去處理。這種通過(guò)把完整任務(wù)層層分發(fā)下去,每個(gè)組件需要處理的內(nèi)容就會(huì)變的很簡(jiǎn)單,處理起來(lái)效率自然會(huì)很高。
方案的缺點(diǎn):
- 編程復(fù)雜度非常的高;
- 即使一個(gè) Reactor 主線程對(duì)應(yīng)多個(gè) Reactor 子線程,Reactor 主線程還是會(huì)存在單節(jié)點(diǎn)故障問(wèn)題,不過(guò)真實(shí)業(yè)務(wù)場(chǎng)景當(dāng)中,如果考慮單節(jié)點(diǎn)故障問(wèn)題的話,一般都是通過(guò)分布式集群(Netty 集群)的方式去解決,而不是靠單節(jié)點(diǎn)的線程模型去解決,這里大家了解一下即可。
總的來(lái)說(shuō),主從多線程模型是應(yīng)用比較多的一種線程模型,包括 Nginx 主從 Reactor 多線程模型、Memcached 主從多線程模型、Netty 主從多線程模型等知名開源框架的。
5. 模型對(duì)比
Reactor 模型和傳統(tǒng)的 IO 模型對(duì)比
傳統(tǒng) IO 模型 | Reactor 模型 | |
---|---|---|
線程分配 | 為每個(gè)客戶端都分配獨(dú)立的線程,該線程負(fù)責(zé)全部的工作(包括:監(jiān)聽、讀取、處理、響應(yīng)) | 統(tǒng)一的監(jiān)聽客戶端請(qǐng)求,并且把功能細(xì)分,并且分配給不同的子線程去處理 |
堵塞點(diǎn) | 在每個(gè)子線程的 read () 方法進(jìn)行堵塞 | 只在 select () 堵塞,select () 是所有客戶端共用的入口點(diǎn) |
整體性能 | 并發(fā)量相對(duì)有限 | 可以處理高并發(fā) |
Reactor 的整體優(yōu)點(diǎn)如下:
- 性能好,Reactor 本身雖然是同步的,但是是非堵塞的,可以快速的響應(yīng);
- 擴(kuò)展性好,可以根據(jù) CPU 的核數(shù)來(lái)調(diào)整 Reactor 的實(shí)例個(gè)數(shù),充分的利用 CPU 資源;
- 復(fù)用性好,它是一種思想,可以靈活的運(yùn)用到不同的中間件、底層框架上。
三種 Reactor 線程模型對(duì)比
單 Reactor 單線程 | 單 Reactor 多線程 | 主從多線程 | |
---|---|---|---|
功能 | 一個(gè)線程負(fù)責(zé)所有業(yè)務(wù) | 一個(gè)線程服務(wù)監(jiān)聽、事件處理、轉(zhuǎn)發(fā),多個(gè)線程負(fù)責(zé)邏輯處理 | 一個(gè)線程負(fù)責(zé)監(jiān)聽,多個(gè)線程負(fù)責(zé)事件處理、轉(zhuǎn)發(fā),多個(gè)線程負(fù)責(zé)邏輯處理 |
線程 | 一個(gè)線程 | 一個(gè)線程,一個(gè)線程組 | 一個(gè)線程,兩個(gè)線程組 |
性能 | 低 | 中 | 高 |
高可用 | 否 | 否 | 是 |
6. 小結(jié)
通過(guò)這幾個(gè)小節(jié)的講解,相信大家對(duì) Reactor 線程模型都已經(jīng)有了一定的了解了,其實(shí)我們只需要了解這幾種模型的架構(gòu)思想即可。Reactor 它是一種思想,而并非是 Netty 所特有的,常見的中間件 Nginx、Redis 等底層通訊也都是基于 Reactor 思想去實(shí)現(xiàn)。只有把 Reactor 模型理解了,后期在閱讀源碼時(shí)才能更好的理解 Netty。