高性能 IO模型:Reactor vs Proactor ,如何工作?
我們都知道,科技領(lǐng)域的很多思維其實都是來自于現(xiàn)實生活,比如今天要分享的高性能網(wǎng)絡(luò)通信模型 Reactor 和 Proactor,這巧妙的設(shè)計思維相當(dāng)哇塞。
一、為什么要學(xué)習(xí)
萬丈高樓平地起,要想在技術(shù)上有所成就,就一定要修練好內(nèi)功。隨著互聯(lián)網(wǎng)的快速發(fā)展,技術(shù)更迭的速度也是超乎想象,也許花大力氣掌握的技能幾年就過時了(比如 JSP,sturts/sturts2),但是有一些東西卻是歷久彌新,比如:架構(gòu)思想,設(shè)計思維,掌握了這些精髓,可以幫助你快速適應(yīng)技術(shù)更迭。Reactor 和 Proactor 是網(wǎng)絡(luò) IO 處理中兩個經(jīng)典的高性能模型,學(xué)習(xí)它們可以在網(wǎng)絡(luò) IO 處理上獲得不一樣的認(rèn)知,兩個模型可以高度抽象為下圖:
二、Reactor模型
1.定義
Reactor,中文翻譯為”反應(yīng)器”,它是一個被動過程,可以理解為”當(dāng)接收到客戶端事件后,Reactor 會根據(jù)事件類型來調(diào)用相應(yīng)的代碼進(jìn)行處理”。Reactor 模型也叫 Dispatcher 模式,底層是 I/O 多路復(fù)用結(jié)合線程池,主要是用于服務(wù)器端處理高并發(fā)網(wǎng)絡(luò) IO 請求。Reactor 模型的核心思想可以總結(jié)成 2個”3種”:
- 3種事件:連接事件、讀事件、寫事件;
- 3種角色:reactor、acceptor、handler;
2.事件
- 客戶端向服務(wù)器端發(fā)送連接請求,該請求對應(yīng)了服務(wù)器的一個連接事件;
- 連接建立后,客戶端給服務(wù)器端發(fā)送寫請求,服務(wù)器端收到請求,需要從請求中讀取內(nèi)容,這就對應(yīng)了服務(wù)器的讀事件;
- 服務(wù)器經(jīng)過處理后,把結(jié)果值返回給客戶端,這就對應(yīng)了服務(wù)器的寫事件;
事件的描述可以參考下圖:
3.角色
上述描述了 Reactor 的事件,每個事件都需要有一個專門的負(fù)責(zé)人,在 Reactor 模型中,這個負(fù)責(zé)人就是角色,其說明如下:
- 連接事件由 acceptor 來處理,只負(fù)責(zé)接收連接;acceptor 在接收連接后,會創(chuàng)建 handler,用于網(wǎng)絡(luò)連接上對后續(xù)讀寫事件的處理;
- 讀寫事件由 handler 處理,處理完后響應(yīng)客戶端;
- 在連接事件、讀寫事件會同時發(fā)生的高并發(fā)場景中,需要reactor 角色專門監(jiān)聽和分配事件:連接事件交由 acceptor 處理;讀寫請求交由 handler 處理;
4.Reactor 線程模型
Reactor 線程模型有單 Reactor 單線程模型、單 Reactor 多線程模型、多 Reactor 多線程模型 三種。
(1) 單Reactor單線程模型
單 Reactor 單線程模型,很容易理解:接受請求、業(yè)務(wù)處理、響應(yīng)請求都在一個線程中處理。
① 模型抽象
② 工作原理
- Reactor 通過 select函數(shù)監(jiān)聽事件,收到事件后通過 dispatch 分發(fā)給 Acceptor 或 Handler;
- 如果監(jiān)聽到 client 的連接事件,則分發(fā)給 Acceptor 處理,Acceptor 通過 accept 接受連接,并創(chuàng)建一個 Handler 來處理連接后續(xù)的各種事件;
- 如果不是連接建立事件,則 Reactor 會調(diào)用連接對應(yīng)的 Handler(步驟2創(chuàng)建的 Handler)來進(jìn)行響應(yīng);
- Handler 通過:read-> 業(yè)務(wù)處理 ->send 流程完成完整業(yè)務(wù)流程;
③ 優(yōu)缺點
優(yōu)點是簡單,不存在線程競爭,缺點是無法充分利用和發(fā)揮多核 CPU 的性能,當(dāng)業(yè)務(wù)耗時很長時,容易造成阻塞。
④ 案例
- Redis6.0以下的版本使用的是 單 Reactor 單線程模型,因為 Redis使用的是內(nèi)存,CPU不是性能瓶頸,所以單 Reactor 單線程模型可以支持 Redis 單機(jī)服務(wù)的高性能,下篇公眾號文章,我會分享 Redis是如何駕馭 Reactor模型和 IO 多路復(fù)用機(jī)制。
- Netty4 通過參數(shù)配置,可以使用單 Reactor 單線程模型;
(2) 單Reactor多線程模型
鑒于單 Reactor 單線程模式無法充分利用和發(fā)揮多核 CPU 的性能,于是就誕生了單 Reactor 多線程模型。
① 模型抽象圖
② 工作原理
- 在主線程中,Reactor 對象通過 select 監(jiān)控事件,收到事件后通過 dispatch 分發(fā)給 Acceptor 或 Handler;
- 如果監(jiān)聽到 client 的連接事件,則分發(fā)給 Acceptor 處理,Acceptor 通過 accept 接受連接,并創(chuàng)建一個 Handler 來處理連接后續(xù)的各種事件;
- 如果不是連接建立事件,則 Reactor 會調(diào)用連接對應(yīng)的 Handler(步驟2創(chuàng)建的 Handler)來進(jìn)行響應(yīng)。注意,此模型的 Handler 只負(fù)責(zé)響應(yīng)事件,不進(jìn)行業(yè)務(wù)處理;
- Handler 通過 read 讀取到數(shù)據(jù)后,會發(fā)給 Processor 進(jìn)行業(yè)務(wù)處理;
- Processor 會在獨立的子線程中完成真正的業(yè)務(wù)處理,然后將響應(yīng)結(jié)果發(fā)給主線程的 Handler 處理;Handler 收到響應(yīng)后通過 send 將響應(yīng)結(jié)果返回給 client;
③ 優(yōu)點
采用了線程池來處理業(yè)務(wù)邏輯,能夠充分利用多 CPU 的處理能力
④ 缺點
- 多線程數(shù)據(jù)共享和訪問比較復(fù)雜。例如,子線程完成業(yè)務(wù)處理后,要把結(jié)果傳遞給主線程的 Reactor 進(jìn)行發(fā)送,這里涉及共享數(shù)據(jù)的互斥和保護(hù)機(jī)制;
- 盡管引進(jìn)了多線程處理業(yè)務(wù)邏輯,但是事件的監(jiān)聽和響應(yīng)還是需要 Reactor 來處理,因此,瞬間高并發(fā)可能會造成 Reactor 的性能瓶頸;
⑤ 案例
Netty4 通過參數(shù)配置,可以使用單 Reactor 多線程模型;
(3) 多Reactor多線程模型
單 Reactor 多線程模型的性能瓶頸在于單個 Reactor 的處理能力,于是我們很自然的想到:能不能增加多個 Reactor來提升性能?于是,多 Reactor 多線程模型就應(yīng)孕而生。
① 模型抽象圖
② 工作原理
- 父線程中 mainReactor 對象通過 select 監(jiān)控連接建立事件,收到事件后通過 Acceptor 接收,將新的連接分配給某個子線程;
- 子線程的 subReactor 把 mainReactor 分配的連接加入到連接隊列中并進(jìn)行監(jiān)聽,同時創(chuàng)建一個 Handler 用于處理連接的各種事件;
- 當(dāng)有新的事件發(fā)生時,subReactor 會調(diào)用連接對應(yīng)的 Handler(步驟2創(chuàng)建的 Handler)來進(jìn)行響應(yīng);
- Handler 通過:read-> 業(yè)務(wù)處理 ->send 流程完成完整業(yè)務(wù)流程;
③ 優(yōu)點
- 父線程和子線程的職責(zé)明確,父線程只負(fù)責(zé)接收新連接,子線程負(fù)責(zé)完成后續(xù)的業(yè)務(wù)處理;
- 父線程和子線程的交互簡單,父線程只需要把新連接傳給子線程,子線程無須返回數(shù)據(jù);
④ 案例
- Nginx 采用的是多 Reactor 多進(jìn)程模型,但方案與標(biāo)準(zhǔn)的多 Reactor 多進(jìn)程有差異;
- 開源軟件 Memcache 采用的是多 Reactor 多線程模型;
- Netty4 通過參數(shù)參數(shù)配置可以使用多 Reactor 多線程模型;
到此, Reactor模型就分析完了,需要說明的是:上文講述的 Reactor 3種線程模型,同樣可以以進(jìn)程的方式部署,可能在邏輯處理上和線程有些差異。接下來再分析和 Reactor模型很類似的 Proactor 模型。
三、Proactor模型
1.定義
Proactor,中文翻譯為”前攝器”,乍一看,這個翻譯還是挺懵圈的,個人覺得”主動器”更符合 Proactor 模型的本意。Proactor 可以理解為“當(dāng)有連接、讀寫等IO事件時,操作系統(tǒng)內(nèi)核在處理完事件后主動通知我們的程序代碼”。
2.模型抽象圖
3.工作原理
- Proactor Initiator 負(fù)責(zé)創(chuàng)建 Proactor 和 Handler,并將 Proactor 和 Handler 都通過 Asynchronous Operation Processor 注冊到內(nèi)核;
- Asynchronous Operation Processor 負(fù)責(zé)處理注冊請求,并完成 I/O 操作;
- Asynchronous Operation Processor 完成 I/O 操作后通知 Proactor;
- Proactor 根據(jù)不同的事件類型回調(diào)不同的 Handler 進(jìn)行業(yè)務(wù)處理;
- Handler 完成業(yè)務(wù)處理,Handler 也可以注冊新的 Handler 到內(nèi)核進(jìn)程;
4.優(yōu)缺點
- Proactor 在處理高耗時 IO 時的性能要高于 Reactor,但對于低耗時 IO 的執(zhí)行效率提升并不明顯;
- Proactor 的異步性使其并發(fā)處理能力要強于 Reactor;
- Proactor 的實現(xiàn)邏輯復(fù)雜,編碼成本較 Reactor 要高很多;
- Proactor 的異步高度依賴于操作系統(tǒng)對于異步的支持。若操作系統(tǒng)對異步的支持不好,Proactor 的性能還不如 Reactor;
5.案例
Netty5, 它是采用 AIO,其網(wǎng)絡(luò)通信模型就是 Proactor,但該版本已經(jīng)被不再維護(hù),主要原因是 Linux 目前對于異步的支持不完善,導(dǎo)致 Netty5 花了大代價,性能相對 Netty4 不但沒有提升,甚至還會降低。
四、總結(jié)
(1) Reactor 是同步非阻塞網(wǎng)絡(luò)模型,Proactor 是異步非阻塞網(wǎng)絡(luò)模型;
(2) Reactor 是 I/O 多路復(fù)用和線程池的完美結(jié)合;
(3) Reactor模型看似高深,其實是生活中很多真實案例的寫照,比如:
- 夜市一個老板一輛推車的單人炒粉模式,從點菜,出餐,結(jié)算都是老板一人完成,這個就對應(yīng)了 單 Reactor單線程模型;
- 醫(yī)院叫號看病就對應(yīng)了 單 Reactor多線程模型,一個叫號機(jī)負(fù)責(zé)叫號,多名醫(yī)生負(fù)責(zé)接待病人;
- 大型餐飲就餐對應(yīng)了 多 Reactor多線程模型,一個接待員負(fù)責(zé)接客送客,多名服務(wù)員,每名服務(wù)員負(fù)責(zé)幾桌客人,然后有專門的端菜人員負(fù)責(zé)給客人端菜,比如:海底撈;
(4) Reactor思維在日常開發(fā)中也會經(jīng)常使用,最常用的是單線程處理,當(dāng)并發(fā)量比較大時引進(jìn)線程池,把業(yè)務(wù)細(xì)分,專門的線程處理專門的事情,這樣就和 Reactor 模型的演變有異曲同工之妙;
(5) Proactor 主要是采用異步的方式來處理 IO 事件(比如:叫外賣,下單支付后不需要關(guān)注,直接處理自己的事情,等外賣好了之后,外賣小哥會把主動把外賣送到你手上),不過目前 Linux 對 AIO支持的不太友好,使用該模型的 Netty5 最終也為此夭折了;