本文基礎是需要有Netty的使用經驗,如果沒有編碼經驗,可以參考官網給的例子:https://netty.io/wiki/user-guide-for-4.x.html。另外本文也是針對的是Netty 4.1.x版本的。
Reactor模式
本文主要介紹Netty執行緒模型及其實現,介紹Netty執行緒模型前,首先會介紹下經典的Reactor執行緒模型,目前大多數網路框架都是基於Reactor模式進行設計和開發,Reactor模式基於事件驅動,有一個或多個併發輸入源,有一個Service Handler,有多個Request Handlers。這個Service Handler會同步的將輸入的請求(Event)多路複用的分發給相應的Request Handler,非常適合處理海量的I/O事件。下面簡單介紹下Reactor模式及其執行緒模型。
單執行緒模型
如圖所示,由於Reactor模式使用的是非同步非阻塞IO,所有的IO操作都不會導致阻塞。通常Reactor執行緒中聚合了多路複用器負責監聽網路事件,當有新連線到來時,觸發連線事件,Disdatcher負責使用Acceptor接受客戶端連線,建立通訊鏈路。當I/O事件就緒後,Disdatcher負責將事件分發到對應的event handler上負責處理。
該模型的缺點很明顯,不適用於高負載、高併發的應用場景;由於只有一個Reactor執行緒,一旦故障,整個系統通訊模組將不可用。
多執行緒模型
該模型的特點:
- 專門由一個Reactor執行緒-Acceptor執行緒用於監聽服務端,接收客戶端連線請求;
- 網路I/O操作讀、寫等由Reactor執行緒池負責處理;
- 一個Reactor執行緒可同時處理多條鏈路,但一條鏈路只能對應一個Reactor執行緒,這樣可避免併發操作問題。
絕大多數場景下,Reactor多執行緒模型都可以滿足效能需求,但是,在極個別特殊場景中,一個Reactor執行緒負責監聽和處理所有的客戶端連線可能會存在效能問題。例如併發百萬客戶端連線,或者服務端需要對客戶端握手進行安全認證,但是認證本身非常損耗效能。因此,誕生了第三種執行緒模型。
主從多執行緒模型
該模型的特點:
- 服務端使用一個獨立的主Reactor執行緒池來處理客戶端連線,當服務端收到連線請求時,從主執行緒池中隨機選擇一個Reactor執行緒作為Acceptor執行緒處理連線;
- 鏈路建立成功後,將新建立的SocketChannel註冊到sub reactor執行緒池的某個Reactor執行緒上,由它處理後續的I/O操作。
Netty執行緒模型
Netty同時支援Reactor單執行緒模型 、Reactor多執行緒模型和Reactor主從多執行緒模型,使用者可根據啟動引數配置在這三種模型之間切換。Netty執行緒模型原理圖如下:
服務端啟動時,通常會建立兩個NioEventLoopGroup例項,對應了兩個獨立的Reactor執行緒池。常見服務端啟動程式碼實現如下:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
......
實際上比較重要的建立執行緒池建立程式碼:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
服務端啟動時建立了兩個NioEventLoopGroup,他們實際上時兩個獨立的Reactor執行緒池,一個負責接收客戶端的TCP連線,另一個用於處理I/O操作,或執行系統Task、定時任務Task等,接下來做一下原始碼分析。
NioEventLoopGroup
首先看下NioEventLoopGroup的繼承關係:
可以看出最終還是呼叫了java的執行緒池建立方式,接下來看一下它的構造方法。
檢視程式碼
public class NioEventLoopGroup extends MultithreadEventLoopGroup {
public NioEventLoopGroup() {
this(0);
}
public NioEventLoopGroup(int nThreads) {
this(nThreads, (Executor) null);
}
public NioEventLoopGroup(ThreadFactory threadFactory) {
this(0, threadFactory, SelectorProvider.provider());
}
public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory) {
this(nThreads, threadFactory, SelectorProvider.provider());
}
public NioEventLoopGroup(int nThreads, Executor executor) {
this(nThreads, executor, SelectorProvider.provider());
}
public NioEventLoopGroup(
int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider) {
this(nThreads, threadFactory, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory,
final SelectorProvider selectorProvider, final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, threadFactory, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
public NioEventLoopGroup(
int nThreads, Executor executor, final SelectorProvider selectorProvider) {
this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,
final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory,
RejectedExecutionHandlers.reject());
}
public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,
final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory,
final RejectedExecutionHandler rejectedExecutionHandler) {
super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory, rejectedExecutionHandler);
}
public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,
final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory,
final RejectedExecutionHandler rejectedExecutionHandler,
final EventLoopTaskQueueFactory taskQueueFactory) {
super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory,
rejectedExecutionHandler, taskQueueFactory);
}
public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,
SelectorProvider selectorProvider,
SelectStrategyFactory selectStrategyFactory,
RejectedExecutionHandler rejectedExecutionHandler,
EventLoopTaskQueueFactory taskQueueFactory,
EventLoopTaskQueueFactory tailTaskQueueFactory) {
super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory,
rejectedExecutionHandler, taskQueueFactory, tailTaskQueueFactory);
}
}
可以看到最終是呼叫了父類MultithreadEventExecutorGroup的構造方法,繼續跟蹤:
檢視程式碼
protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
this(nThreads, threadFactory == null ? null : new ThreadPerTaskExecutor(threadFactory), args);
}
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
checkPositive(nThreads, "nThreads");
if (executor == null) {
//建立執行緒執行器以及執行緒工廠
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
//根據執行緒數構建EventExecutor陣列
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
//初始化陣列中的執行緒,由NioEventLoopGroup建立NioEventLoop例項
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
//當初始化失敗時,需要優雅關閉,清理資源
if (!success) {
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
//當執行緒未終止時,等待終止
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
// Let the caller handle the interruption.
Thread.currentThread().interrupt();
break;
}
}
}
}
}
//根據執行緒數建立選擇器,選擇器主要適用於next()方法
chooser = chooserFactory.newChooser(children);
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
//為每個EventLoop執行緒增加執行緒終止監聽器
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
//建立執行器陣列只讀副本,便於在迭代查詢時使用
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
從程式碼看出,使用EventLoopGroup workerGroup = new NioEventLoopGroup()來建立執行緒池,如果不指定執行緒個數,那麼預設用0,在預設為0的情況下系統會使用預設的執行緒個數來建立執行緒池,如果制定了n>0個執行緒個數的話,就建立有限個數執行緒的執行緒池。那麼預設建立的執行緒個數規則是啥呢?可以詳見如下程式碼:
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
}
}
可以看出是在CPU核心數*2與“io.netty.eventLoopThreads”這個配置引數取或並且與1比較大小得到的結果,就說如果“io.netty.eventLoopThreads”這系統引數配置了就用系統引數與1比較的最大值返回,如果沒有配置使用cpu核心數*2與1比較的最大值返回。
關於NioEventLoopGroup我們從程式碼跟蹤中做如下的總結,該類主要是完成三件事:
- 建立一定數量的NioEventLoop執行緒組並初始化。
- 建立執行緒選擇器chooser,當獲取執行緒時,通過選擇器來獲取。
- 建立執行緒工廠並構建執行緒執行器。
執行緒組的生產分兩步:第一步,建立一定數量的EventExecutor陣列;第二步,通過呼叫子類的newChild()方法完成這些EventExecutor陣列的初始化。為了提高可擴充套件性,Netty的執行緒組除了NioEventLoopGroup,還有Netty通過JNI方式提供的一套由epoll模型實現的EpollEventLoop Group執行緒組,以及其他I/O多路複用模型執行緒組,因此newChild()方法由具體的執行緒組子類來實現。
children[i] = newChild(executor, args);
檢視程式碼
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
SelectorProvider selectorProvider = (SelectorProvider) args[0];
SelectStrategyFactory selectStrategyFactory = (SelectStrategyFactory) args[1];
RejectedExecutionHandler rejectedExecutionHandler = (RejectedExecutionHandler) args[2];
EventLoopTaskQueueFactory taskQueueFactory = null;
EventLoopTaskQueueFactory tailTaskQueueFactory = null;
int argsLength = args.length;
if (argsLength > 3) {
taskQueueFactory = (EventLoopTaskQueueFactory) args[3];
}
if (argsLength > 4) {
tailTaskQueueFactory = (EventLoopTaskQueueFactory) args[4];
}
return new NioEventLoop(this, executor, selectorProvider,
selectStrategyFactory.newSelectStrategy(),
rejectedExecutionHandler, taskQueueFactory, tailTaskQueueFactory);
}
在newChild()方法中,NioEventLoop的初始化引數有6個:第1個引數為NioEventLoopGroup執行緒組本身;第2個引數為執行緒執行器,用於啟動執行緒,在SingleThreadEventExecutor的doStartThread()方法中被呼叫;第3個引數為NIO的Selector選擇器的提供者;第4個引數主要在NioEventLoop的run()方法中用於控制選擇迴圈;第5個引數為非I/O任務提交被拒絕時的處理Handler;第6個引數為佇列工廠,在NioEventLoop中,佇列讀是單執行緒操作,而佇列寫則可能是多執行緒操作,使用支援多生產者、單消費者的佇列比較合適,預設為MpscChunkedArrayQueue佇列。
NioEventLoopGroup通過next()方法獲取NioEventLoop執行緒,最終會呼叫其父類MultithreadEventExecutorGroup的next()方法,委託父類的選擇器EventExecutorChooser。具體使用哪種選擇器物件取決於MultithreadEventExecutorGroup的構造方法中使用的策略模式。
根據執行緒條數是否為2的冪次來選擇策略,若是,則選擇器為PowerOfTwoEventExecutorChooser,其選擇策略使用與運算計算下一個選擇的執行緒組的下標index;若不是,則選擇器為GenericEventExecutorChooser,其選擇策略為使用求餘的方法計算下一個執行緒線上程組中的下標index。其中,PowerOfTwoEventExecutorChooser選擇器的與運算效能會更好。
根據執行緒條數是否為2的冪次來選擇策略,若是,則選擇器為PowerOfTwoEventExecutorChooser,其選擇策略使用與運算計算下一個選擇的執行緒組的下標index,此計算方法在第7章中也有相似的應用;若不是,則選擇器為GenericEventExecutorChooser,其選擇策略為使用求餘的方法計算下一個執行緒線上程組中的下標index。其中,PowerOfTwoEventExecutorChooser選擇器的與運算效能會更好。
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
由於Netty的NioEventLoop執行緒被包裝成了FastThreadLocalThread執行緒,同時,NioEventLoop執行緒的狀態由它自身管理,因此每個NioEventLoop執行緒都需要有一個執行緒執行器,並且線上程執行前需要通過執行緒工廠io.netty.util.concurrent.DefaultThreadFactory將其包裝成FastThreadLocalThread執行緒。執行緒執行器ThreadPerTaskExecutor與DefaultThreadFactory的newThread()方法的程式碼解讀如下:
檢視程式碼
public void execute(Runnable command) {
//呼叫執行緒工廠類的newThread包裝執行緒,並且啟動,等待執行緒排程。
threadFactory.newThread(command).start();
}
public Thread newThread(Runnable r) {
//包裝FastThreadLocalThread執行緒,執行緒的字首名字為NioEventLoopGroup-
//服務啟動後可以通過Arthas工具檢視
Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
try {
if (t.isDaemon() != daemon) {
t.setDaemon(daemon);
}
if (t.getPriority() != priority) {
t.setPriority(priority);
}
} catch (Exception ignored) {
// Doesn't matter even if failed to set.
}
return t;
}
//包裝為FastThreadLocalThread執行緒
protected Thread newThread(Runnable r, String name) {
return new FastThreadLocalThread(threadGroup, r, name);
}