Netty框架學習之(四):線程模型

轉載自:https://www.cnblogs.com/TomSnail/p/6158249.html

1. Proactor和Reactor

Proactor和Reactor是兩種經典的多路複用I/O模型,主要用於在高併發、高吞吐量的環境中進行I/O處理。
I/O多路複用機制都依賴於一個事件分發器,事件分離器把接收到的客戶事件分發到不同的事件處理器中,如下圖:

這裏寫圖片描述

1.1 select,poll,epoll

在操作系統級別select,poll,epoll是3個常用的I/O多路複用機制,簡單瞭解一下將有助於我們理解Proactor和Reactor。

1.1.1 select

select的原理如下:

這裏寫圖片描述

用戶程序發起讀操作後,將阻塞查詢讀數據是否可用,直到內核準備好數據後,用戶程序纔會真正的讀取數據。
poll與select的原理相似,用戶程序都要阻塞查詢事件是否就緒,但poll沒有最大文件描述符的限制。

1.1.2 epoll

epoll是select和poll的改進,原理圖如下:
這裏寫圖片描述

epoll使用“事件”的方式通知用戶程序數據就緒,並且使用內存拷貝的方式使用戶程序直接讀取內核準備好的數據,不用再讀取數據

1.2 Proactor

Proactor是一個異步I/O的多路複用模型,原理圖如下:
這裏寫圖片描述

  • 用戶發起IO操作到事件分離器
  • 事件分離器通知操作系統進行IO操作
  • 操作系統將數據存放到數據緩存區
  • 操作系統通知分發器IO完成
  • 分離器將事件分發至相應的事件處理器
  • 事件處理器直接讀取數據緩存區內的數據進行處理

1.3 Reactor

Reactor是一個同步的I/O多路複用模型,它沒有Proactor模式那麼複雜,原理圖如下:
這裏寫圖片描述

  • 用戶發起IO操作到事件分離器
  • 事件分離器調用相應的處理器處理事件
  • 事件處理完成,事件分離器獲得控制權,繼續相應處理

1.4 Proactor和Reactor的比較

  • Reactor模型簡單,Proactor複雜
  • Reactor是同步處理方式,Proactor是異步處理方式
  • Proactor的IO事件依賴操作系統,操作系統須支持異步IO
  • 同步與異步是相對於服務端與IO事件來說的,Proactor通過操作系統異步來完成IO操作,當IO完成後通知事件分離器,而Reactor需要自己完成IO操作

2 Reactor多線程模型

前面已經簡單介紹了Proactor和Reactor模型,在實際中Proactor由於需要操作系統的支持,實現的案例不多,有興趣的可以看一下Boost Asio的實現,我們主要說一下Reactor模型,Netty也是使用Reactor實現的。
但單線程的Reactor模型每一個用戶事件都在一個線程中執行:
性能有極限,不能處理成百上千的事件
當負荷達到一定程度時,性能將會下降
單某一個事件處理器發送故障,不能繼續處理其他事件

2.1 多線程Reactor

使用線程池的技術來處理I/O操作,原理圖如下:
這裏寫圖片描述

  • Acceptor專門用來監聽接收客戶端的請求
  • I/O讀寫操作由線程池進行負責
  • 每個線程可以同時處理幾個鏈路請求,但一個鏈路請求只能在一個線程中進行處理

2.2 主從多線程Reactor

在多線程Reactor中只有一個Acceptor,如果出現登錄、認證等耗性能的操作,這時就會有單點性能問題,因此產生了主從Reactor多線程模型,原理如下:
這裏寫圖片描述

  • Acceptor不再是一個單獨的NIO線程,而是一個獨立的NIO線程池
  • Acceptor處理完後,將事件註冊到IO線程池的某個線程上
  • IO線程繼續完成後續的IO操作
  • Acceptor僅僅完成登錄、握手和安全認證等操作,IO操作和業務處理依然在後面的從線程中完成

3 Netty中Reactor模型的實現

Netty同時支持Reactor的單線程、多線程和主從多線程模型,在不同的應用中通過啓動參數的配置來啓動不同的線程模型。
通過線程池的線程個數、是否共享線程池方式來切換不同的模型

3.1 Netty中的Reactor模型

Netty中的Reactor模型如下圖:
這裏寫圖片描述

  • Acceptor中的NioEventLoop用於接收TCP連接,初始化參數
  • I/O線程池中的NioEventLoop異步讀取通信對端的數據報,發送讀事件到channel
  • 異步發送消息到對端,調用channel的消息發送接口
  • 執行系統調用Task
  • 執行定時Task

3.2 NioEventLoop

NioEventLoop是Netty的Reactor線程,它在Netty Reactor線程模型中的職責如下:
- 作爲服務端Acceptor線程,負責處理客戶端的請求接入
- 作爲客戶端Connecor線程,負責註冊監聽連接操作位,用於判斷異步連接結果
- 作爲IO線程,監聽網絡讀操作位,負責從SocketChannel中讀取報文
- 作爲IO線程,負責向SocketChannel寫入報文發送給對方,如果發生寫半包,會自動註冊監聽寫事件,用於後續繼續發送半包數據,直到數據全部發送完成

如下圖,是一個NioEventLoop的處理鏈:
這裏寫圖片描述

  • 處理鏈中的處理方法是串行化執行的
  • 一個客戶端連接只註冊到一個NioEventLoop上,避免了多個IO線程併發操作

3.2.1 Task

Netty Reactor線程模型中有兩種Task:系統Task和定時Task
- 系統Task:創建它們的主要原因是,當IO線程和用戶線程都在操作同一個資源時,爲了防止併發操作時鎖的競爭問題,將用戶線程封裝爲一個Task,在IO線程負責執行,實現局部無鎖化
- 定時Task:主要用於監控和檢查等定時動作
基於以上原因,NioEventLoop不是一個純粹的IO線程,它還會負責用戶線程的調度

3.2.2 IO線程的分配細節

線程池對IO線程進行資源管理,是通過EventLoopGroup實現的。線程池平均分配channel到所有的線程(循環方式實現,不是100%準確),一個線程在同一時間只會處理一個通道的IO操作,這種方式可以確保我們不需要關心同步問題。

3.2.3 Selector

NioEventLoop是Reactor的核心線程,那麼它就就必須實現多路複用。
Selector的過程如下:
這裏寫圖片描述
- 首先oldWakenUp = wakenUp.getAndSet(false)
- 如果隊列中有任務, selectNow()
- 如果沒有select(),直達channel準備就緒,但此過程中循環次數超過限值也將rebuidSelectoror退出循環
- 執行processSelectedKeys和runAllTasks

3.2.4 epoll-bug的處理

在netty中對java nio的epoll bug進行了處理,就是設置一個閥值,如果超過了就rebuidSelector來避免epoll()死循環

3.2.5 NioEevntLoopGroup

EventExecutorGroup:提供管理EevntLoop的能力,他通過next()來爲任務分配執行線程,同時也提供了shutdownGracefully這一優雅下線的接口.

EventLoopGroup繼承了EventExecutorGroup接口,並新添了3個方法
- EventLoop next()
- ChannelFuture register(Channel channel)
- ChannelFuture register(Channel channel, ChannelPromise promise)

EventLoopGroup的實現中使用next().register(channel)來完成channel的註冊,即將channel註冊時就綁定了一個EventLoop,然後EvetLoop將channel註冊到EventLoop的Selector上。

NioEventLoopGroup還有幾點需要注意:
- NioEventLoopGroup下默認的NioEventLoop個數爲cpu核數 * 2,因爲有很多的io處理
- NioEventLoop和java的single線程池在5裏差異變大了,它本身不負責線程的創建銷燬,而是由外部傳入的線程池管理
- channel和EventLoop是綁定的,即一旦連接被分配到EventLoop,其相關的I/O、編解碼、超時處理都在同一個EventLoop中,這樣可以確保這些操作都是線程安全的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章