netty系列之:netty架構概述

hjavn發表於2021-08-05

簡介

Netty為什麼這麼優秀,它在JDK本身的NIO基礎上又做了什麼改進呢?它的架構和工作流程如何呢?請走進今天的netty系列文章之:netty架構概述。

netty架構圖

netty的主要作用就是提供一個簡單的NIO框架可以和上層的各種協議相結合,最終實現高效能的伺服器。下面是netty官網提供的架構圖:

從上圖可以看到netty的核心主要分成三部分,分別是可擴充套件的event model、統一的API、和強大的Byte Buffer。這三個特性是netty的制勝法寶。

下面會從這幾個方面對netty的特性進行詳細說明,務必讓讀者瞭解到netty的優秀之處。

豐富的Buffer資料機構

首先從最底層的Buffer資料結構開始,netty提供了一個io.netty.buffer的包,該包裡面定義了各種型別的ByteBuf和其衍生的型別。

netty Buffer的基礎是ByteBuf類,這是一個抽象類,其他的Buffer類基本上都是由該類衍生而得的,這個類也定義了netty整體Buffer的基調。

netty重寫ByteBuf其目的是讓最底層的ByteBuf比JDK自帶的更快更適合擴充套件。具體而言,netty的ByteBuf要比JDK中的ByteBuffer要快,同時,擴充套件也更加容易,大家可以根據需要Buf進行自定義。另外netty有一些內建的複合緩衝區型別,所以可以實現透明的零拷貝。對於動態緩衝區型別來說,可以和StringBuffer一樣按需擴充套件,非常好用。

零拷貝

什麼是零拷貝呢?零拷貝的意思是在需要拷貝的時候不做拷貝。我們知道資料在使用底層協議進行傳輸的過程中是會被封裝成為一個個的包進行傳輸。當傳輸的資料過大,一個包放不下的時候,還需要對資料進行拆分,目的方在接收到資料之後,需要對收到的資料進行組裝,一般情況下這個組裝的操作是對資料的拷貝,將拆分過後的物件拷貝到一個長的資料空間中。

比如下面的例子所示,將底層的TCP包組合稱為頂層的HTTP包,但是並沒有進行拷貝:

具體怎麼拷貝呢?在上一篇文章中,我們知道netty提供了一個工具類方法Unpooled,這個工具類中有很多wrapped開頭的方法,我們舉幾個例子:

 public static ByteBuf wrappedBuffer(byte[]... arrays) {
        return wrappedBuffer(arrays.length, arrays);
    }

public static ByteBuf wrappedBuffer(ByteBuf... buffers) {
        return wrappedBuffer(buffers.length, buffers);
    }

public static ByteBuf wrappedBuffer(ByteBuffer... buffers) {
        return wrappedBuffer(buffers.length, buffers);
    }

上面三個方法分別是封裝byte陣列、封裝ByteBuf和封裝ByteBuffer,這些方法都是零拷貝。大家可以在實際的專案中根據實際情況,自行選用。

統一的API

一般來說,在傳統的JDK的IO API中,根據傳輸型別或者協議的不同,使用的API也是不同的。我們需要對不同的傳輸方式開發不同的應用程式,不能做到統一。這樣的結果就是無法平滑的遷移,並且在程式擴充套件的時候需要進行額外的處理。

什麼是傳輸方式呢?這裡是指以什麼樣的方式來實現IO,比如傳統的阻塞型IO,我們可以稱之為OIO,java的new IO可以稱之為NIO,非同步IO可以稱之為AIO等等。

並且JDK中的傳統IO和NIO是割裂的,如果在最開始你使用的是傳統IO,那麼當你的客戶數目增長到一定的程度準備切換到NIO的時候,就會發現切換起來異常複雜,因為他們是割裂的。

為了解決這個問題,netty提供了一個統一的類Channel來提供統一的API。

先看下Channel中定義的方法:

從上圖我們可以看到使用Channel可以判斷channel當前的狀態,可以對其進行引數配置,可以對其進行I/O操作,還有和channel相關的ChannelPipeline用來處理channel關聯的IO請求和事件。

使用Channel就可以對NIO的TCP/IP,OIO的TCP/IP,OIO的UDP/IP和本地傳輸都能提供很好的支援。

傳輸方式的切換,只需要進行很小的成本替換。

當然,如果你對現有的實現都不滿意的話,還可以對核心API進行自定義擴充套件。

事件驅動

netty是一個事件驅動的框架,事件驅動框架的基礎就是事件模型,Netty專門為IO定義了一個非常有效的事件模型。可以在不破壞現有程式碼的情況下實現自己的事件型別。netty中自定義的事件型別通過嚴格的型別層次結構跟其他事件型別區分開來,所以可擴充套件性很強。

netty中的事件驅動是由ChannelEvent、ChannelHandler和ChannelPipeline共同作用的結果。其中ChannelEvent表示發生的事件,ChannelHandler定義瞭如何對事件進行處理,而ChannelPipeline類似一個攔截器,可以讓使用者自行對定義好的ChannelHandler進行控制,從而達到控制事件處理的結果。

我們看一個簡單的自定義Handler:

public class MyHandler extends SimpleChannelInboundHandler<Object> {

    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 對訊息進行處理
        ByteBuf in = (ByteBuf) msg;
        try {
            log.info("收到訊息:{}",in.toString(io.netty.util.CharsetUtil.US_ASCII));
        }finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        //異常處理
        cause.printStackTrace();
        ctx.close();
    }
}

上面的例子中,我們定義瞭如何對接收到的訊息和異常進行處理。在後續的文章中我們會詳細對ChannelEvent、ChannelHandler和ChannelPipeline之間的互動使用進行介紹。

其他優秀的特性

除了上面提到的三大核心特性之外,netty還有其他幾個優點,方便程式設計師的開發工作。

比如對SSL / TLS的支援,對HTTP協議的實現,對WebSockets 實現和Google Protocol Buffers的實現等等,表明netty在各個方面多個場景都有很強的應用能力。

總結

netty是由三個核心元件構成:緩衝區、通道和事件模型,通過理解這三個核心元件是如何相互工作的,那麼再去理解建立在netty之上的高階功能就不難了。

本文的例子可以參考:learn-netty4

本文已收錄於 http://www.flydean.com/03-netty-architecture/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

相關文章