Netty源碼分析(1)-核心組件與架構

1. Netty 組件架構

Netty 的事件驅動框架基於 主從 Reactor 多線程 實現,其工作處理的流程如下所示。根據功能的不同,Netty 的組件主要可分爲兩個部分,分別是 事件分發組件 和 業務處理組件

在這裏插入圖片描述

2. 事件分發組件

事件分發組件主要包含了 EventLoopGroupAcceptor 兩類,其中 EventLoopGroup 爲事件循環組,負責事件的分發處理,Acceptor 則是服務端處理 accept 事件的處理器,負責將主事件循環組新建的連接註冊到從事件循環組,起到連接的作用

2.1 事件循環組 EventLoopGroup

EventLoopGroup 主要管理 EventLoop 的生命週期,內部維護了一組 EventLoop,每個 EventLoop 負責處理多個 Channel 上的事件,而一個 Channel 只對應於一個 EventLoop

2.1.1 事件循環組實例 NioEventLoopGroup

NioEventLoopGroup 繼承結構很深,各個關鍵屬性分佈在不同的父類中,比較重要的如下:

  1. MultithreadEventLoopGroup:
    DEFAULT_EVENT_LOOP_THREADS : 表示默認的事件循環線程數,默認值爲機器可用線程 * 2
  2. MultithreadEventExecutorGroup:
    children: EventExecutor 數組,維護屬於當前 group 的事件循環線程,也就是 NioEventLoop 實例對象

MultithreadEventExecutorGroupEventLoopGroup 最重要的實現,其構造方法主要完成了以下幾件事:

  1. new ThreadPerTaskExecutor(new DefaultThreadFactory()) 生成 Executor 實例,並指定其線程工廠
  2. 調用 newChild() 方法爲當前 group 新建 NioEventLoop 實例,並指定其 Executor 入參爲 ThreadPerTaskExecutor 對象,該對象後續將用於創建和啓動 EventLoop 線程
  3. 如果有一個 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 任務的執行時間相等

  1. I/O 任務
    即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 NioEventLoop#processSelectedKeys() 方法觸發
  2. 非 IO 任務
    添加到任務隊列中的任務,如 register0、bind0 等任務,由 NioEventLoop#runAllTasks() 方法觸發

NioEventLoop 的繼承結構同樣很深,其比較關鍵的屬性如下:

  1. selector 選擇器,用於監聽註冊其上的 channel 連接上的各個事件
  2. 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. 業務處理組件

業務處理組件主要包含了ChannelChannelHandler 以及 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 種實現DefaultChannelHandlerContextHeadContext以及 TailContext,以下爲其關鍵屬性:

  1. AbstractChannelHandlerContext:
    next: 當前管道處理器上下文節點的下一個節點
    prev: 當前管道處理器上下文節點的上一個節點
    inbound: 當前管道處理器上下文節點的出入站標識屬性,爲 true 表示該節點可處理入站事件
    outbound: 當前管道處理器上下文節點的出入站標識屬性,爲 true 表示該節點可處理出站事件
    pipeline: ChannelPipeline 的實例,其實現爲 DefaultChannelPipeline
  2. DefaultChannelHandlerContext:
    handler: 封裝的 ChannelHandler 處理器

ChannelPipeline 的子類 DefaultChannelPipeline 對象實例化時會生成一個HeadContextTailContext 對象,並將其前後指針互相指向對方,形成了一個雙向鏈表。 DefaultChannelPipeline#addLast() 方法會向該雙向鏈表添加節點,主要處理步驟如下:

  1. newContext()ChannelHandler 封裝到 DefaultChannelHandlerContext 對象
  2. 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;
    }

在這裏插入圖片描述

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