在BIO線程模型中,為了解決同步阻塞的問題,采用了多線程的方式處理并發,即經典的connection per thread,每一個連接配接用一個線程處理。雖然在單個線程内仍然是阻塞的,但在整體上看是可以同時處理多個連接配接請求的,原理圖如下:
但這種方式的缺點在于資源要求太高,系統中建立線程是需要比較高的系統資源的,如果連接配接數太多,系統無法承受,而且,線程的反複建立和銷毀也需要代價。
為了解決這個問題,出現了Reactor線程模型。簡單來說,Reactor線程模型就是多路I/O複用結合線程池的思想。
- I/O多路複用:多個連接配接共用一個阻塞對象(即下圖中的ServiceHandler),應用程式隻需要在一個阻塞對象等待,無需阻塞等待所有連接配接,當某個連接配接有新的資料可以處理時,作業系統通知應用程式線程從阻塞狀态傳回,并将資料分發給對應的線程處理
- 基于線程池複用線程資源:不必再為每個連接配接建立線程,将連接配接完成後的業務處理任務交給線程池中的線程處理,處理完成後歸還線程,同一個線程可以處理多個連接配接的業務,達到線程複用
在Reactor線程模型的發展過程中,出現了不同的實作方式,具體有:
- 單Reactor單線程
- 單Reactor多線程
- 主從Reactor多線程
1.單Reactor單線程模式 (Reactor和handler都在同一個線程中)
方案說明:
- Select是IO複用模型介紹的标準網絡程式設計API,可以實作應用程式通過一個阻塞對象監聽多路連接配接請求
- Reactor對象通過Select監控用戶端請求事件,收到事件後通過Dispatch進行分發
- 如果是建立連接配接請求事件,則由Acceptor通過Accept處理連接配接請求,然後建立一個Handler對象處理連接配接完成後的後續業務
- 如果不是建立連接配接事件,則Reactor會分發調用連接配接對應的Handler來處理業務
- Handler會完成Read->業務處理->Send的完整業務流程
這種模式的缺點在于:
- 伺服器端用一個線程通過多路複用搞定所有的IO操作,包括連接配接、讀、寫等,但是如果用戶端連接配接數較多,将無法支撐,比如處理一個用戶端的業務時,别的用戶端的業務請求隻能阻塞等待
- 單線程無法發揮多核CPU的性能
2.單Reactor多線程 方案說明:
- Reactor對象通過select監控用戶端請求事件,收到事件後,通過dispatcher進行分發
- 如果是建立連接配接的請求,則由Acceptor通過accept處理連接配接請求,然後建立一個Handler對象處理完成連接配接後的各種事件
- 如果不是連接配接請求,則由Reactor分發調用連接配接對應的handler進行處理
- handler隻負責響應事件,不做具體的業務處理,通過read讀取資料後,會分發給後面的worker線程池的某個線程處理業務
- worker線程池會分發獨立的線程完成真正的業務,并将結果傳回給handler
- handler收到響應的結果後,再通過send将結果傳回給client
優點:
- 多線程可以充分利用多核CPU的處理能力
- 采用線程池複用線程,減少建立和銷毀線程帶來的性能開銷
缺點:
- reactor處理所有事件的監聽和響應,在單線程運作,高并發場景下容易出現性能瓶頸
- 多線程資料共享和通路比較複雜
3.主從Reactor多線程
方案說明:
- Reactor主線程MainReactor對象通過select監聽連接配接事件,收到事件後,通過Acceptor處理連接配接事件
- 當Acceptor處理連接配接事件後,MainReactor将連接配接配置設定給SubReactor
- SubReactor将連接配接加入到連接配接隊列進行監聽,并建立handler進行各種事件處理
- 當有新事件發生時,SubReactor就會調用對應的handler進行處理
- handler通過Read讀取資料,分發給後面的worker線程處理
- worker線程池會配置設定獨立的worker線程進行業務處理,并傳回結果
- handler收到響應的結果後,再通過send将結果傳回給client
- MainReactor主線程可以關聯多個SubReactor子線程
優點:
- 主線程與子線程的資料互動簡單職責明确,主線程隻需要接收新連接配接,子線程完成後續的業務處理
- 可以通過擴充多個Reactor子線程的方式來減小單個子線程的壓力,提高并發處理能力
Netty就是在主從Reactor多線程模型的基礎上進行了一定的改進,同時,Kafka的網絡架構設計也采用了這種主從Reactor多線程的模型。