文章目錄
1. Netty 組件架構
Netty 的事件驅動框架基於 主從 Reactor 多線程 實現,其工作處理的流程如下所示。根據功能的不同,Netty 的組件主要可分爲兩個部分,分別是 事件分發組件 和 業務處理組件
2. 事件分發組件
事件分發組件主要包含了 EventLoopGroup
和 Acceptor
兩類,其中 EventLoopGroup
爲事件循環組,負責事件的分發處理,Acceptor
則是服務端處理 accept 事件的處理器,負責將主事件循環組新建的連接註冊到從事件循環組,起到連接的作用
2.1 事件循環組 EventLoopGroup
EventLoopGroup
主要管理 EventLoop 的生命週期,內部維護了一組 EventLoop,每個 EventLoop 負責處理多個 Channel 上的事件,而一個 Channel 只對應於一個 EventLoop
2.1.1 事件循環組實例 NioEventLoopGroup
NioEventLoopGroup
繼承結構很深,各個關鍵屬性分佈在不同的父類中,比較重要的如下:
MultithreadEventLoopGroup:
DEFAULT_EVENT_LOOP_THREADS
: 表示默認的事件循環線程數,默認值爲機器可用線程 * 2MultithreadEventExecutorGroup:
children
: EventExecutor 數組,維護屬於當前 group 的事件循環線程,也就是 NioEventLoop 實例對象
MultithreadEventExecutorGroup
是 EventLoopGroup
最重要的實現,其構造方法主要完成了以下幾件事:
new ThreadPerTaskExecutor(new DefaultThreadFactory())
生成 Executor 實例,並指定其線程工廠- 調用
newChild()
方法爲當前 group 新建 NioEventLoop 實例,並指定其 Executor 入參爲ThreadPerTaskExecutor
對象,該對象後續將用於創建和啓動 EventLoop 線程- 如果有一個 NioEventLoop 實例新建失敗,調用已創建的每個 NioEventLoop 實例的
shutdownGracefully()
方法啓動事件循環線程
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
}
if (executor == null) {
// #1
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
// #2
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 ++) {
// #3
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;
}
}
}
}
}
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);
}
}
};
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
2.1.2 事件循環實例 NioEventLoop
NioEventLoop
維護了一個線程和任務隊列,類似於單線程線程池,支持異步提交執行任務,主要執行 I/O 任務
和非 I/O 任務
。兩種任務的執行時間比由變量 ioRatio
控制,默認爲 50,表示非 IO 任務執行的時間與 IO 任務的執行時間相等
I/O 任務
即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由NioEventLoop#processSelectedKeys()
方法觸發非 IO 任務
添加到任務隊列中的任務,如 register0、bind0 等任務,由NioEventLoop#runAllTasks()
方法觸發
NioEventLoop
的繼承結構同樣很深,其比較關鍵的屬性如下:
selector
選擇器,用於監聽註冊其上的 channel 連接上的各個事件SingleThreadEventExecutor:
taskQueue
: 任務存放的隊列
executor
: 用於新建線程的線程工廠和執行器
NioEventLoop#run()
方法是事件循環處理的核心邏輯,SingleThreadEventExecutor#execute()
方法則爲事件循環開始的入口,此處暫不做深入分析
2.2 連接接收器 Acceptor
Netty 中的 Acceptor
在實現上屬於業務處理組件
,因爲其實現類ServerBootstrapAcceptor
繼承於 ChannelHandler
,主要負責在 Server 端完成對 Channel 的處理
之所以將這個組件歸入事件分發組件,是因爲從功能劃分的角度看其屬於 MainReactor 與 SubReactor 之間的連接結構,負責將 MainReactor 接收客戶端請求建立的新連接註冊到 SubReactor 上
ServerBootstrapAcceptor#channelRead()
是這個 Acceptor
最重要的方法,其實現了將新建連接註冊到 SubReactor 的邏輯
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
try {
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
3. 業務處理組件
業務處理組件主要包含了Channel
、ChannelHandler
以及 ChannelHandlerContext
。一個 Channel
包含了一個 ChannelPipeline
,而 ChannelPipeline
中又維護了一個由 ChannelHandlerContext
組成的雙向鏈表,每個 ChannelHandlerContext
中又關聯着一個 ChannelHandler
根據ChannelHandler
實現類繼承接口的不同,可以將其分爲 ChannelInboundHandler入站處理器
和 ChannelOutboundHandler出站處理器
,二者分別對應讀寫操作。 入站事件(read)
和出站事件(write)
在ChannelPipeline
中鏈式處理,入站事件會從鏈表 head 往後傳遞到最後一個入站處理器 tail,出站事件會從鏈表 tail 往前傳遞到最前一個出站處理器 head,兩種類型的 ChannelHandler 互不干擾
3.1 管道 Channel
Channel
是一個管道,是用於連接字節緩衝區和另一端的實體,可以分成服務端 NioServerSocketChannel
和 客戶端 NioSocketChannel
兩個大類。Netty 中 Channel
經過 ChannelPipeline
中的多個 ChannelHandler
處理器處理,最終完成 IO 數據的處理
- 服務端 NioServerSocketChannel
該類繼承於AbstractNioMessageChannel
,持有一個NioMessageUnsafe
對象來完成 Chanel 上 IO 操作- 客戶端 NioSocketChannel
該類繼承於AbstractNioByteChannel
,持有一個NioByteUnsafe
對象完成 Channel 上事件的處理所有繼承於
AbstractChannel
的 Channel 實例中都會持有一個DefaultChannelPipeline
對象,該對象用於完成 IO 數據的流處理,另外還會持有一個EventLoop
對象標識 Channel 所屬的事件循環實例, 可用於處理任務提交
3.2 管道處理器 ChannelHandler
ChannelHandler
是一個接口,處理 I/O 事件或攔截 I/O 操作,並將其轉發到 ChannelPipeline
業務處理鏈中的下一個 ChannelHandler
ChannelHandler
主要分爲了進站處理器ChannelInboundHandler
和出站處理器ChannelOutboundHandler
,分別對應 read 和 write 操作處理。處理器可由用戶編寫處理邏輯,Netty 內部會將其包裝爲AbstractChannelHandlerContext
對象,再將其註冊到業務處理鏈中,即可完成對 IO 數據的邏輯處理
3.3 管道處理器上下文 ChannelHandlerContext
ChannelHandlerContext
保存 Channel
相關的上下文信息,其主要有 3 種實現DefaultChannelHandlerContext
、HeadContext
以及 TailContext
,以下爲其關鍵屬性:
AbstractChannelHandlerContext:
next
: 當前管道處理器上下文節點的下一個節點
prev
: 當前管道處理器上下文節點的上一個節點
inbound
: 當前管道處理器上下文節點的出入站標識屬性,爲 true 表示該節點可處理入站事件
outbound
: 當前管道處理器上下文節點的出入站標識屬性,爲 true 表示該節點可處理出站事件
pipeline
:ChannelPipeline
的實例,其實現爲DefaultChannelPipeline
類DefaultChannelHandlerContext:
handler
: 封裝的 ChannelHandler 處理器
ChannelPipeline
的子類 DefaultChannelPipeline
對象實例化時會生成一個HeadContext
及 TailContext
對象,並將其前後指針互相指向對方,形成了一個雙向鏈表。 DefaultChannelPipeline#addLast()
方法會向該雙向鏈表添加節點,主要處理步驟如下:
newContext()
將ChannelHandler
封裝到DefaultChannelHandlerContext
對象addLast0()
將新建的ChannelHandlerContext
對象加入到雙向鏈表中
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler);
newCtx = newContext(group, filterName(name, handler), handler);
addLast0(newCtx);
// If the registered is false it means that the channel was not registered on an eventloop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}