Netty的原始碼分析和業務場景

威哥爱编程發表於2024-08-02

Netty 是一個高效能、非同步事件驅動的網路應用框架,它基於 Java NIO 構建,廣泛應用於網際網路、大資料、遊戲開發、通訊行業等多個領域。以下是對 Netty 的原始碼分析、業務場景的詳細介紹:

原始碼概述

  1. Netty 的核心元件:Netty 的架構設計圍繞著事件驅動的核心思想,主要包括 Channel、EventLoopGroup、ChannelHandlerContext 和 ChannelPipeline 等關鍵概念。
  2. Channel:是網路連線的抽象表示,每個 Channel 都有一個或多個 ChannelHandler 來處理網路事件,如連線建立、資料接收等。
  3. EventLoopGroup:是一組 EventLoop 的集合,每個 EventLoop 負責處理一組 Channel 的 I/O 事件。當 Channel 的事件觸發時,相應的 EventLoop 會呼叫 ChannelHandler 中的方法進行處理。
  4. ChannelPipeline:是 ChannelHandler 的有序集合,用於處理進來的和出站的資料。透過在 Pipeline 中新增不同的 Handler,可以實現複雜的業務邏輯。
  5. 原始碼中的關鍵流程:Netty 的原始碼分析需要關注的關鍵流程包括初始化、Channel 的註冊、EventLoop 的工作流程、以及連線的建立和繫結過程。

Netty 提供了一個 Echo 示例,用於演示客戶端和伺服器端的基本通訊流程。在這個示例中,客戶端傳送的訊息被伺服器端接收並原樣返回,展示了 Netty 處理網路通訊的基本方法。

下面 V 哥來詳細介紹一下這幾外關鍵核心元件。

1. Channel元件

Netty 的 Channel 元件是整個框架的核心之一,它代表了網路中的一個連線,可以是客戶端的也可以是伺服器端的。Channel 是一個低階別的介面,用於執行網路 I/O 操作。以下是對 Channel 元件的原始碼分析和解釋:

Channel 介面定義

Channel 介面定義了一組操作網路連線的方法,例如繫結、連線、讀取、寫入和關閉。

public interface Channel extends AttributeMap {

    /**
     * Returns the {@link ChannelId} of this {@link Channel}.
     */
    ChannelId id();

    /**
     * Returns the parent {@link Channel} of this channel. {@code null} if this is the top-level channel.
     */
    Channel parent();

    /**
     * Returns the {@link ChannelConfig} of this channel.
     */
    ChannelConfig config();

    /**
     * Returns the local address of this channel.
     */
   SocketAddress localAddress();

    /**
     * Returns the remote address of this channel. {@code null} if the channel is not connected.
     */
    SocketAddress remoteAddress();

    /**
     * Returns {@code true} if this channel is open and may be used.
     */
    boolean isOpen();

    /**
     * Returns {@code true} if this channel is active and may be used for IO.
     */
    boolean isActive();

    /**
     * Returns the {@link ChannelPipeline}.
     */
    ChannelPipeline pipeline();

    /**
     * Returns the {@link ChannelFuture} which is fired once the channel is registered with its {@link EventLoop}.
     */
    ChannelFuture whenRegistered();

    /**
     * Returns the {@link ChannelFuture} which is fired once the channel is deregistered from its {@link EventLoop}.
     */
    ChannelFuture whenDeregistered();

    /**
     * Returns the {@link ChannelFuture} which is fired once the channel is closed.
     */
    ChannelFuture whenClosed();

    /**
     * Register this channel to the given {@link EventLoop}.
     */
    ChannelFuture register(EventLoop loop);

    /**
     * Bind and listen for incoming connections.
     */
    ChannelFuture bind(SocketAddress localAddress);

    /**
     * Connect to the given remote address.
     */
    ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress);

    /**
     * Disconnect if connected.
     */
    ChannelFuture disconnect();

    /**
     * Close this channel.
     */
    ChannelFuture close();

    /**
     * Deregister this channel from its {@link EventLoop}.
     */
    ChannelFuture deregister();

    /**
     * Write the specified message to this channel.
     */
    ChannelFuture write(Object msg);

    /**
     * Write the specified message to this channel and generate a {@link ChannelFuture} which is done
     * when the message is written.
     */
    ChannelFuture writeAndFlush(Object msg);

    /**
     * Flushes all pending messages.
     */
    ChannelFuture flush();

    // ... 更多方法定義
}

Channel 的關鍵方法

  • id(): 返回 Channel 的唯一識別符號。
  • parent(): 返回父 Channel,如果是頂級 Channel,則返回 null
  • config(): 獲取 Channel 的配置資訊。
  • localAddress()remoteAddress(): 分別返回本地和遠端地址。
  • isOpen()isActive(): 分別檢查 Channel 是否開啟和啟用。
  • pipeline(): 返回與 Channel 關聯的 ChannelPipeline,它是處理網路事件的處理器鏈。
  • register(), bind(), connect(), disconnect(), close(), deregister(): 這些方法用於執行網路 I/O 操作。

Channel 的實現類

Netty 為不同型別的網路通訊協議提供了多種 Channel 的實現,例如:

  • NioSocketChannel:用於 NIO 傳輸的 TCP 協議的 Channel 實現。
  • NioServerSocketChannel:用於 NIO 傳輸的 TCP 伺服器端 Channel 實現。
  • OioSocketChannelOioServerSocketChannel:類似 NIO,但是用於阻塞 I/O。

Channel 的生命週期

  1. 建立Channel 透過其工廠方法建立,通常與特定的 EventLoop 關聯。
  2. 註冊Channel 必須註冊到 EventLoop 上,以便可以處理 I/O 事件。
  3. 繫結/連線:伺服器端 Channel 繫結到特定地址並開始監聽;客戶端 Channel 連線到遠端地址。
  4. 讀取和寫入:透過 Channel 讀取和寫入資料。
  5. 關閉:關閉 Channel,釋放相關資源。

Channel 的事件處理

Channel 的事件處理是透過 ChannelPipelineChannelHandler 完成的。ChannelPipeline 是一個處理器鏈,負責處理所有的 I/O 事件和 I/O 操作。每個 Channel 都有一個與之關聯的 ChannelPipeline,可以透過 Channelpipeline() 方法訪問。

非同步處理

Channel 的操作(如繫結、連線、寫入、關閉)都是非同步的,返回一個 ChannelFuture 物件,允許開發者設定回撥,當操作完成或失敗時執行。

記憶體管理

Netty 的 Channel 實現還涉及記憶體管理,使用 ByteBuf 作為資料容器,它是一個可變的位元組容器,提供了一系列的操作方法來讀寫網路資料。

小結

Channel 是 Netty 中的一個核心介面,它定義了網路通訊的基本操作。Netty 提供了多種 Channel 的實現,以支援不同的 I/O 模型和協議。透過 Channel,Netty 實現了高效能、非同步和事件驅動的網路通訊。

2. EventLoopGroup元件

EventLoopGroup 是 Netty 中一個非常重要的元件,它負責管理一組 EventLoop,每個 EventLoop 可以處理多個 Channel 的 I/O 事件。以下是對 EventLoopGroup 元件的詳細分析和解釋:

EventLoopGroup 介面定義

EventLoopGroup 介面定義了一組管理 EventLoop 的方法,以下是一些關鍵方法:

public interface EventLoopGroup extends ExecutorService {

    /**
     * Returns the next {@link EventLoop} this group will use to handle an event.
     * This will either return an existing or a new instance depending on the implementation.
     */
    EventLoop next();

    /**
     * Shuts down all {@link EventLoop}s and releases all resources.
     */
    ChannelFuture shutdownGracefully();

    /**
     * Shuts down all {@link EventLoop}s and releases all resources.
     */
    ChannelFuture shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit);

    /**
     * Returns a copy of the list of all {@link EventLoop}s that are part of this group.
     */
    List<EventLoop> eventLoops();
}

EventLoopGroup 的關鍵方法

  • next(): 返回下一個 EventLoop,用於處理事件。這可以是現有的 EventLoop 或者新建立的例項,具體取決於實現。
  • shutdownGracefully(): 優雅地關閉所有 EventLoop 並釋放所有資源。這個方法允許指定一個靜默期和一個超時時間,以便在關閉之前等待所有任務完成。
  • eventLoops(): 返回當前 EventLoopGroup 中所有 EventLoop 的列表。

EventLoopGroup 的實現類

Netty 提供了幾種 EventLoopGroup 的實現,主要包括:

  • DefaultEventLoopGroup: 預設的 EventLoopGroup 實現,使用 NioEventLoop 作為其 EventLoop 實現。
  • EpollEventLoopGroup: 特定於 Linux 的 EventLoopGroup 實現,使用 EpollEventLoop 作為其 EventLoop 實現,利用 Linux 的 epoll 機制提高效能。
  • OioEventLoopGroup: 阻塞 I/O 模式下的 EventLoopGroup 實現,使用 OioEventLoop 作為其 EventLoop 實現。

EventLoopGroup 的工作原理

  1. 建立: EventLoopGroup 透過其建構函式建立,可以指定執行緒數。
  2. 註冊: Channel 需要註冊到 EventLoop 上,以便 EventLoop 可以處理其 I/O 事件。
  3. 事件迴圈: 每個 EventLoop 在其執行緒中執行一個事件迴圈,處理註冊到它的 Channel 的 I/O 事件。
  4. 關閉: EventLoopGroup 可以被關閉,釋放所有資源。

EventLoopGroup 的執行緒模型

  • 單執行緒模型: 一個 EventLoopGroup 只包含一個 EventLoop,適用於小容量應用。
  • 多執行緒模型: 一個 EventLoopGroup 包含多個 EventLoop,每個 EventLoop 在單獨的執行緒中執行,適用於高併發應用。

EventLoopGroup 的使用場景

  • 伺服器端: 在伺服器端,通常使用兩個 EventLoopGroup。一個用於接受連線(bossGroup),一個用於處理連線(workerGroup)。bossGroup 通常使用較少的執行緒,而 workerGroup 可以根據需要處理更多的併發連線。
  • 客戶端端: 在客戶端,通常只需要一個 EventLoopGroup,用於處理所有的連線。

示例程式碼

以下是如何在 Netty 中使用 EventLoopGroup 的示例程式碼:

public class NettyServer {

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 用於接受連線
        EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用於處理連線

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(new LoggingHandler());
                     p.addLast(new MyServerHandler());
                 }
             });

            ChannelFuture f = b.bind(8080).sync(); // 繫結埠並啟動伺服器
            System.out.println("Server started on port 8080");
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

在這個示例中,bossGroup 用於接受連線,workerGroup 用於處理連線。透過 ServerBootstrap 類配置伺服器,並使用 ChannelInitializer 來設定 Channel 的處理器鏈。

總結

EventLoopGroup 是 Netty 中管理事件迴圈的核心元件,它透過 EventLoop 處理 I/O 事件,支援高併發和非同步操作。透過合理配置 EventLoopGroup,可以顯著提高網路應用的效能和可擴充套件性。

3. ChannelPipeline元件

ChannelPipeline 是 Netty 中的一個核心元件,它負責管理一組 ChannelHandler,並且定義了 I/O 事件和操作如何在這些處理器之間流動。以下是對 ChannelPipeline 元件的詳細分析和解釋:

ChannelPipeline 介面定義

ChannelPipeline 是一個介面,定義了操作 ChannelHandler 的方法:

public interface ChannelPipeline extends Iterable<ChannelHandler> {

    /**
     * Add the specified handler to the context of the current channel.
     */
    void addLast(EventExecutorGroup executor, String name, ChannelHandler handler);

    /**
     * Add the specified handlers to the context of the current channel.
     */
    void addLast(EventExecutorGroup executor, ChannelHandler... handlers);

    // ... 省略其他 addFirst, addBefore, addAfter, remove, replace 方法

    /**
     * Get the {@link ChannelHandler} by its name.
     */
    ChannelHandler get(String name);

    /**
     * Find the first {@link ChannelHandler} in the {@link ChannelPipeline} that matches the specified class.
     */
    ChannelHandler first();

    /**
     * Find the last {@link ChannelHandler} in the {@link ChannelPipeline} that matches the specified class.
     */
    ChannelHandler last();

    /**
     * Returns the context object of the specified handler.
     */
    ChannelHandlerContext context(ChannelHandler handler);

    // ... 省略 contextFor, remove, replace, fireChannelRegistered, fireChannelUnregistered 等方法
}

ChannelPipeline 的關鍵方法

  • addLast(String name, ChannelHandler handler): 在管道的末尾新增一個新的處理器,併為其指定一個名稱。
  • addFirst(String name, ChannelHandler handler): 在管道的開頭新增一個新的處理器。
  • addBefore(String baseName, String name, ChannelHandler handler): 在指定處理器前新增一個新的處理器。
  • addAfter(String baseName, String name, ChannelHandler handler): 在指定處理器後新增一個新的處理器。
  • get(String name): 根據名稱獲取 ChannelHandler
  • first()last(): 分別獲取管道中的第一個和最後一個處理器。
  • context(ChannelHandler handler): 獲取指定處理器的上下文。

ChannelHandlerContext

ChannelHandlerContextChannelHandlerChannelPipeline 之間的橋樑,提供了訪問和管理 ChannelChannelPipelineChannelFuture 的能力:

public interface ChannelHandlerContext extends AttributeMap, ResourceLeakHint {

    /**
     * Return the current channel to which this context is bound.
     */
    Channel channel();

    /**
     * Return the current pipeline to which this context is bound.
     */
    ChannelPipeline pipeline();

    /**
     * Return the name of the {@link ChannelHandler} which is represented by this context.
     */
    String name();

    /**
     * Return the {@link ChannelHandler} which is represented by this context.
     */
    ChannelHandler handler();

    // ... 省略其他方法
}

ChannelPipeline 的工作原理

ChannelPipeline 維護了一個雙向連結串列的 ChannelHandler 集合。每個 Channel 例項都有一個與之關聯的 ChannelPipeline。當 I/O 事件發生時,如資料被讀取到 Channel,該事件會被傳遞到 ChannelPipeline,然後按照 ChannelHandler 在管道中的順序進行處理。

處理器的執行順序

  • 入站事件:當資料被讀取到 Channel 時,事件會從管道的尾部向頭部傳遞,直到某個 ChannelHandler 處理該事件。
  • 出站事件:當需要傳送資料時,事件會從管道的頭部向尾部傳遞,直到資料被寫出。

原始碼分析

ChannelPipeline 的實現類 DefaultChannelPipeline 內部使用了一個 ChannelHandler 的雙向連結串列來維護處理器的順序:

private final AbstractChannelHandlerContext head;
private final AbstractChannelHandlerContext tail;
private final List<ChannelHandler> handlers = new ArrayList<ChannelHandler>();
  • headtail 是連結串列的頭尾節點。
  • handlers 是儲存所有處理器的列表。

新增處理器時,DefaultChannelPipeline 會更新連結串列和列表:

public void addLast(EventExecutorGroup executor, String name, ChannelHandler handler) {
    if (handler == null) {
        throw new NullPointerException("handler");
    }
    if (name == null) {
        throw new NullPointerException("name");
    }
    AbstractChannelHandlerContext newCtx = new TailContext(this, executor, name, handler);
    synchronized (this) {
        if (tail == null) {
            head = tail = newCtx;
        } else {
            tail.next = newCtx;
            newCtx.prev = tail;
            tail = newCtx;
        }
        handlers.add(newCtx);
    }
}

小結

ChannelPipeline 是 Netty 中處理網路事件和請求的管道,它透過維護一個 ChannelHandler 的連結串列來管理事件的流動。透過 ChannelHandlerContextChannelHandler 能夠訪問和修改 ChannelChannelPipeline 的狀態。這種設計使得事件處理流程高度可定製和靈活,是 Netty 高效能和易於使用的關鍵因素之一。

4. 原始碼中的關鍵流程

在 Netty 的 ChannelPipeline 的原始碼中,關鍵流程涉及處理器的新增、事件的觸發、以及事件在處理器之間的流動。以下是一些關鍵流程的分析:

1. 處理器的新增

當建立 ChannelPipeline 並準備新增 ChannelHandler 時,需要確定處理器的順序和位置。Netty 允許開發者在管道的開始、結束或指定位置插入處理器。

ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("myHandler", new MyChannelHandler());

DefaultChannelPipeline 類中,處理器被新增到一個雙向連結串列中,每個處理器節點(AbstractChannelHandlerContext)儲存了指向前一個和後一個處理器的引用。

2. 事件迴圈和觸發

每個 Channel 都與一個 EventLoop 關聯,EventLoop 負責處理所有註冊到它上面的 Channel 的事件。當 EventLoop 執行時,它會不斷地迴圈,等待並處理 I/O 事件。

// EventLoop 的事件迴圈
public void run() {
    for (;;) {
        // ...
        processSelectedKeys();
        // ...
    }
}

3. 事件的捕獲和傳遞

EventLoop 檢測到一個 I/O 事件(如資料到達)時,它會觸發相應的操作。對於 ChannelPipeline 來說,這意味著需要呼叫適當的 ChannelHandler 方法。

// 虛擬碼,展示了事件如何被傳遞到 ChannelHandler
if (channelRead) {
    pipeline.fireChannelRead(msg);
}

4. 入站和出站事件的處理

  • 入站事件(如資料被讀取)通常從 ChannelPipeline 的尾部開始傳遞,沿著管道向前,直到某個處理器處理了該事件。
  • 出站事件(如寫資料)則從 ChannelPipeline 的頭部開始傳遞,沿著管道向後,直到資料被寫出。
// 入站事件處理
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    // 處理訊息或傳遞給下一個處理器
    ctx.fireChannelRead(msg);
}

// 出站事件處理
public void write(ChannelHandlerContext ctx, Object msg) {
    // 寫訊息或傳遞給下一個處理器
    ctx.write(msg);
}

5. 處理器鏈的遍歷

ChannelPipeline 需要能夠遍歷處理器鏈,以便按順序觸發事件。這通常透過從 ChannelHandlerContext 獲取下一個或前一個處理器來實現。

// 虛擬碼,展示瞭如何獲取下一個處理器並呼叫它
ChannelHandlerContext nextCtx = ctx.next();
if (nextCtx != null) {
    nextCtx.invokeChannelRead(msg);
}

6. 動態修改處理器鏈

在事件處理過程中,可能需要動態地修改處理器鏈,如新增新的處理器或移除當前處理器。

pipeline.addLast("newHandler", new AnotherChannelHandler());
pipeline.remove(ctx.handler());

7. 資源管理和清理

Channel 關閉時,ChannelPipeline 需要確保所有的 ChannelHandler 都能夠執行它們的清理邏輯,釋放資源。

public void channelInactive(ChannelHandlerContext ctx) {
    // 清理邏輯
}

8. 異常處理

在事件處理過程中,如果丟擲異常,ChannelPipeline 需要能夠捕獲並適當地處理這些異常,避免影響整個管道的執行。

try {
    // 可能丟擲異常的操作
} catch (Exception e) {
    ctx.fireExceptionCaught(e);
}

小結

ChannelPipeline 的原始碼中包含了多個關鍵流程,確保了事件能夠按順序在處理器之間傳遞,同時提供了動態修改處理器鏈和異常處理的能力。這些流程共同構成了 Netty 中事件驅動的網路程式設計模型的基礎。

業務場景

  1. 微服務架構:Netty 可以作為 RPC 框架的基礎,實現服務間的高效通訊。
  2. 遊戲伺服器:由於遊戲行業對延遲和併發要求極高,Netty 的非同步非阻塞特性非常適合構建高併發的遊戲伺服器。
  3. 實時通訊系統:Netty 可用於構建如即時訊息、視訊會議等需要低延遲資料傳輸的實時通訊系統。
  4. 物聯網平臺:Netty 可以作為裝置與雲平臺之間的通訊橋樑,處理大規模的裝置連線和資料流。
  5. 網際網路行業:在分散式系統中,Netty 常作為基礎通訊元件被 RPC 框架使用,例如阿里的分散式服務框架 Dubbo 使用 Netty 作為其通訊元件。
  6. 大資料領域:Netty 也被用於大資料技術的網路通訊部分,例如 Hadoop 的高效能通訊元件 Avro 的 RPC 框架就採用了 Netty。

最後

透過深入分析 Netty 的原始碼和理解其在不同業務場景下的應用,開發者可以更好地利用這一強大的網路程式設計框架,構建高效、穩定且可擴充套件的網路應用。

相關文章