【Netty簡單入門】-爲什麼Netty人見人愛?

前言

最近在看ES源碼,Netty貫穿在整個ES之中,想要看懂和更好的理解ES源碼,必須首先對Netty有一定的認識和了解,所以簡單總結了Netty的一些基本架構和認知, 後續再總結更加深入的Netty知識。

什麼是Netty,爲什麼使用Netty

Netty是一個高性能、異步事件驅動的NIO框架,它提供了對TCP、UDP和文件傳輸的支持。
作爲一個異步NIO框架,Netty的所有IO操作都是異步非阻塞的,通過Future-Listener機制,用戶可以方便的主動獲取或者通過通知機制獲得IO操作結果。

作爲當前最流行的NIO框架,Netty在互聯網領域、大數據分佈式計算領域、遊戲行業、通信行業等獲得了廣泛的應用,一些業界著名的開源組件也基於Netty的NIO框架構建。

像大型公司 Facebook 和 Instagram 以及流行 開源項目如 Infinispan, HornetQ,Vert.x, Apache Cassandra 和 Elasticsearch 等,都利用其強大的對於網絡抽象的核心代碼。

Netty受歡迎的原因主要有:

  1. 併發高
  2. 傳輸快
  3. 封裝好

Netty爲什麼併發高

Netty是一款基於NIO(Nonblocking I/O,非阻塞IO)開發的網絡通信框架,對比於BIO(Blocking I/O,阻塞IO),他的併發性能得到了很大提高,兩張圖讓你瞭解BIO和NIO的區別:
在這裏插入圖片描述
在這裏插入圖片描述
從這兩圖可以看出,NIO的單線程能處理連接的數量比BIO要高出很多,而爲什麼單線程能處理更多的連接呢?原因就是圖二中出現的Selector。

當一個連接建立之後,他有兩個步驟要做,第一步是接收完客戶端發過來的全部數據,第二步是服務端處理完請求業務之後返回response給客戶端。

NIO和BIO的區別主要是在第一步。

在BIO中,等待客戶端發數據這個過程是阻塞的,這樣就造成了一個線程只能處理一個請求的情況,而機器能支持的最大線程數是有限的,這就是爲什麼BIO不能支持高併發的原因。

而NIO中,當一個Socket建立好之後,Thread並不會阻塞去接受這個Socket,而是將這個請求交給Selector,Selector會不斷的去遍歷所有的Socket,一旦有一個Socket建立完成,他會通知Thread,然後Thread處理完數據再返回給客戶端——這個過程是阻塞的,這樣就能讓一個Thread處理更多的請求了。

下面兩張圖是基於BIO的處理流程和netty的處理流程,輔助你理解兩種方式的差別:

在這裏插入圖片描述

在這裏插入圖片描述

Netty爲什麼傳輸快

Netty的傳輸快其實也是依賴了NIO的一個特性——零拷貝。

我們知道,Java的內存有堆內存、棧內存和字符串常量池等等,其中堆內存是佔用內存空間最大的一塊,也是Java對象存放的地方,一般我們的數據如果需要從IO讀取到堆內存,中間需要經過Socket緩衝區,也就是說一個數據會被拷貝兩次才能到達他的的終點,如果數據量大,就會造成不必要的資源浪費。

Netty針對這種情況,使用了NIO中的另一大特性——零拷貝,當他需要接收數據的時候,他會在堆內存之外開闢一塊內存,數據就直接從IO讀到了那塊內存中去,在netty裏面通過ByteBuf可以直接對這些數據進行直接操作,從而加快了傳輸速度。

下兩圖就介紹了兩種拷貝方式的區別:

在這裏插入圖片描述
在這裏插入圖片描述
上文介紹的ByteBuf是Netty的一個重要概念,他是netty數據處理的容器,也是Netty封裝好的一個重要體現。

Netty爲什麼封裝好

要說Netty爲什麼封裝好,這種用文字是說不清的,直接上代碼:

阻塞I/O:

public class PlainOioServer {
    public void serve(int port) throws IOException {
        final ServerSocket socket = new ServerSocket(port);     //1
        try {
            for (;;) {
                final Socket clientSocket = socket.accept();   
                System.out.println("Accepted connection from " + clientSocket);
                new Thread(new Runnable() {                       
                    @Override
                    public void run() {
                        OutputStream out;
                        try {
                            out = clientSocket.getOutputStream();
                            out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8")));    
                            out.flush();
                            clientSocket.close();               
                        } catch (IOException e) {
                            e.printStackTrace();
                            try {
                                clientSocket.close();
                            } catch (IOException ex) {
                                // ignore on close
                            }
                        }
                    }
                }).start();                                      
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Netty:

public class NettyOioServer {
    public void server(int port) throws Exception {
        final ByteBuf buf = Unpooled.unreleasableBuffer(
                Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8")));
        EventLoopGroup group = new OioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();        
            b.group(group)                                    
             .channel(OioServerSocketChannel.class)
             .localAddress(new InetSocketAddress(port))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) 
                     throws Exception {
                     ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {            
                         @Override
                         public void channelActive(ChannelHandlerContext ctx) throws Exception {
                             ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);
                         }
                     });
                 }
             });
            ChannelFuture f = b.bind().sync();  
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();       
        }
    }
}

從代碼量上來看,Netty就已經秒殺傳統Socket編程了,但是這一部分博大精深,僅僅貼幾個代碼豈能說明問題,在這裏給大家介紹一下Netty的一些重要概念,讓大家更理解Netty。

Channel:

數據傳輸流,與channel相關的概念有以下四個,上一張圖讓你瞭解netty裏面的Channel:
在這裏插入圖片描述
Channel,表示一個連接,可以理解爲每一個請求,就是一個Channel。
ChannelHandler,核心處理業務就在這裏,用於處理業務請求。
ChannelHandlerContext,用於傳輸業務數據。
ChannelPipeline,用於保存處理過程需要用到的ChannelHandler和ChannelHandlerContext。

ByteBuf:

ByteBuf是一個存儲字節的容器,最大特點就是使用方便,它既有自己的讀索引和寫索引,方便你對整段字節緩存進行讀寫,也支持get/set,方便你對其中每一個字節進行讀寫,他的數據結構如下圖所示:

在這裏插入圖片描述
他有三種使用模式:
Heap Buffer 堆緩衝區
堆緩衝區是ByteBuf最常用的模式,他將數據存儲在堆空間。

Direct Buffer 直接緩衝區
直接緩衝區是ByteBuf的另外一種常用模式,他的內存分配都不發生在堆,jdk1.4引入的nio的ByteBuffer類允許jvm通過本地方法調用分配內存,這樣做有兩個好處

  1. 通過免去中間交換的內存拷貝, 提升IO處理速度; 直接緩衝區的內容可以駐留在垃圾回收掃描的堆區以外。
  2. DirectBuffer 在 -XX:MaxDirectMemorySize=xxM大小限制下, 使用 Heap 之外的內存,,GC對此”無能爲力”,也就意味着規避了在高負載下頻繁的GC過程對應用線程的中斷影響。

Composite Buffer 複合緩衝區
複合緩衝區相當於多個不同ByteBuf的視圖,這是netty提供的,jdk不提供這樣的功能。

參考文檔:

https://www.jianshu.com/p/b9f3f6a16911
https://blog.csdn.net/a953713428/article/details/65629552
https://www.kancloud.cn/kancloud/essential-netty-in-action/52617

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