Netty核心原理

女友在高考發表於2022-02-17

Netty核心原理

1. Netty介紹

1.1 原生NIO存在的問題

  1. NIO的類庫和API使用繁雜
  2. 需要具備其他額外的技能,如java多執行緒程式設計等才能編寫出高質量的NIO程式
  3. 開發工作量和難度都非常大:例如客戶端面臨斷連重連,半包讀寫,網路擁塞和異常流等情況的處理
  4. JDK NIO的BUG:Epoll BUG,它會導致Selector空輪詢,最終導致CPU 100%。直到JDK 1.7版本問題仍舊存在,沒有被根本解決。

1.2 概述

Netty是由JBOSS提供的一個java開源框架。Netty提供非同步的、基於事件驅動的網路應用程式框架,用以快速開發高效能、高可靠性的網路IO程式。Netty是一個基於NIO的網路程式設計框架,使用Netty可以幫助你快速、簡單的開發出一個網路應用,相當於簡化和流程化了NIO的開發過程。知名的Elasticsearch、Dubbo框架內部都採用了Netty。

Netty具備如下優點:

  1. 設計優雅,提供阻塞和非阻塞的Socket;提供靈活可擴充套件的事件模型;提供高度可定製的執行緒模型。
  2. 具備更高的效能和更大的吞吐量,使用零拷貝技術最小化不必要的記憶體複製
  3. 提供安全傳輸特性
  4. 支援多種主流協議,預置多種編解碼功能,支援使用者開發私有協議

2. 執行緒模型

2.1 傳統阻塞I/O服務模型

採用阻塞IO模式獲取輸入的資料,每個連線都需要獨立的執行緒完成資料的輸入,業務處理和資料返回工作。

存在問題:

  1. 當併發數很大,就會建立大量的執行緒,佔用很大的系統資源
  2. 連線建立後,如果當前執行緒暫時沒有資料可讀,該執行緒會阻塞在read操作

2.2 Reactor模型

Reactor模式,通過一個或多個輸入同時傳遞給服務處理器的模式,服務端程式處理傳入的多個請求,將他們同步分派到相應的處理執行緒,因此Reactor模式也叫Dispatcher模式。Reactor模式使用IO複用監聽模式,收到事件後,分發給某個執行緒,這點就是網路伺服器高併發處理關鍵。

1)單Reactor單執行緒

  • Selector是可以實現應用程式通過一個阻塞物件監聽多路連線請求
  • Reactor物件通過Selector監控客戶端請求事件,收到事件後通過Dispatch進行分發
  • 如果是建立連線請求事件,則由Acceptor通過Accept處理連線請求,然後建立一個Handler物件處理連線完成後的後續業務處理
  • Handler會完成Read->業務處理->Send的完整業務流程

優點:

  • 模型簡單,沒有多執行緒競爭通訊的問題

缺點:

  • 效能問題:只有一個執行緒,無法完全發揮出多核CPU的效能。Handler在處理某個連線上的業務時,整個程式無法處理其他連線事件,很容易導致效能瓶頸。
  • 可靠性問題:執行緒意外終止或進入死迴圈,會導致整個系統通訊模組不可用

2)單Reactor多執行緒

  • Reactor物件通過selector監控客戶端請求事件,收到事件後,通過dispatch進行分發
  • 如果是建立連線請求,則由Acceptor通過accept處理連線請求
  • 如果不是連線請求,則由reactor分發呼叫連線相應的handler來處理
  • handler只負責響應事件,不做具體的業務處理,通過read讀取資料後,會分發給後面的worker執行緒池來處理

優點:

  • 可以充分的利用多核CPU的處理能力

缺點:

  • 多執行緒資料共享和訪問比較複雜,reactor處理所有的事件的監聽和響應,在單執行緒執行,在高併發場景容易出現效能瓶頸

3)主從Reactor多執行緒

  • Reactor主執行緒MainReactor物件通過selector監聽客戶端連線事件,收到事件後,通過Acceptor處理客戶端連線事件
  • 當Acceptor處理完連線事件後,MainReactor將連線分為SubReactor
  • SubReactor將連線加入到自己的連線佇列進行監聽,並建立Handler對各種事件進行處理
  • 當連線上有新事件發生的時候,SubReactor就會呼叫對應的Handler處理
  • Handler通過read從連線上讀取請求資料,將請求資料分發給Worker執行緒池就緒業務處理。

優點:

  • MainReactor執行緒與SubReactor執行緒的資料互動簡單職責明確,MainReactor執行緒只需要接收新連線,SubReactor執行緒完成後續的業務處理
  • MainReactor執行緒與SubReactor執行緒的資料互動簡單,MainReactor執行緒只需要把新連線傳給SubReactor執行緒,SubReactor執行緒無需返回資料
  • 多個SubReactor執行緒能夠應對更高的併發請求

缺點:

  • 這種模式程式設計複雜度較高。

這種模式被廣泛使用,包括Nginx、Memcached、Netty等。這種模式也叫做1+M+N執行緒模式,即使用該模式開發的伺服器包含1個(或多個,1只是表示相對較少)連線建立執行緒+M個IO執行緒+N個業務處理執行緒。

2.3 Netty執行緒模型

Netty中我們使用最多的還是主從Reactor執行緒模型。

  • BossGroup中的執行緒專門負責和客戶端建立連線
  • WorkerGroup中的執行緒負責處理連線上的讀寫

3 入門案例

  1. 引入依賴
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.72.Final</version>
</dependency>
  1. 服務端程式碼
public class DemoNettyServer {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup=new NioEventLoopGroup(1);
        //不填寫執行緒數,預設是2*處理器執行緒數
        EventLoopGroup workerGroup=new NioEventLoopGroup();
        ServerBootstrap bootstrap=new ServerBootstrap();
        bootstrap.group(bossGroup,workerGroup)
                //設定服務端通道實現
                .channel(NioServerSocketChannel.class)
               //設定執行緒佇列中等待連線個數
                .option(ChannelOption.SO_BACKLOG,128)
                //設定活躍狀態,child是設定wokerGroup
                .childOption(ChannelOption.SO_KEEPALIVE,Boolean.TRUE)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    //建立一個通道初始化物件
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new DemoNettyServerHandle());
                    }
                });
        //啟動服務端並繫結埠,將非同步改為同步
        ChannelFuture channelFuture = bootstrap.bind(9999).sync();
        System.out.println("服務端啟動成功");
        //關閉通道(不是真正意義上的關閉,而是監聽通道關閉狀態)
        channelFuture.channel().closeFuture().sync();
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}
public class DemoNettyServerHandle implements ChannelInboundHandler {
    @Override
    public void channelRegistered(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    @Override
    public void channelUnregistered(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    @Override
    public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    @Override
    public void channelInactive(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    /**
     * 通道讀取事件
     * @param channelHandlerContext
     * @param o
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        ByteBuf byteBuf=(ByteBuf)o;
        System.out.println("客戶端發來訊息:"+byteBuf.toString(CharsetUtil.UTF_8));
    }

    /**
     * 讀取完畢事件
     * @param channelHandlerContext
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext channelHandlerContext) throws Exception {
        channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer("你好,我是Netty服務端",CharsetUtil.UTF_8));
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {

    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    @Override
    public void handlerAdded(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    @Override
    public void handlerRemoved(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    /**
     * 異常發生事件
     * @param channelHandlerContext
     * @param throwable
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) throws Exception {
        throwable.printStackTrace();
        channelHandlerContext.close();
    }
}
  1. 客戶端程式碼
public class DemoNettyClient {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup group=new NioEventLoopGroup();
        Bootstrap bootstrap=new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new DemoNettyLientHandle());
                    }
                });
        ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9999).sync();
        channelFuture.channel().closeFuture().sync();
        group.shutdownGracefully();
    }
}
public class DemoNettyLientHandle implements ChannelInboundHandler {
    @Override
    public void channelRegistered(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    @Override
    public void channelUnregistered(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    /**
     * 通道就緒事件
     * @param channelHandlerContext
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception {
        channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer("你好,我是客戶端",CharsetUtil.UTF_8));
    }

    @Override
    public void channelInactive(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    @Override
    public void channelRead(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        ByteBuf byteBuf=(ByteBuf)o;
        System.out.println("服務端發來訊息:"+byteBuf.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    @Override
    public void userEventTriggered(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {

    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    @Override
    public void handlerAdded(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    @Override
    public void handlerRemoved(ChannelHandlerContext channelHandlerContext) throws Exception {

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) throws Exception {

    }
}

相關文章