Netty模型

相比於NIO


  • NIO學習成本高,代碼複雜
  • NIO存在Bug

Netty架構設計、線程模型


傳統的阻塞IO

一個請求一個線程。

缺點

  • 併發數高時,就會創建大量的線程,佔用系統資源
  • 當沒有數據可讀時,線程會阻塞read操作,浪費資源

Reactor模式

單Reactor單線程

單Reactor單線程

						Handler中處理了業務邏輯。
  • Reactor通過select監聽客戶端請求事件,通過Dispatch分發
  • 如果不是建立連接,則會分發調用Handler處理
  • 一個阻塞對象監聽多路連接請求

優點:模型簡單,全部在一個線程中完成
缺點:只有一個線程,無法發揮多核cpu的性能。如果線程意外終止,會導致整個通信系統不可用。

單Reactor多線程

單Reactor多線程

							將業務處理從Handler剝離出來,分發給某個線程池

說明

  • Reactor接收請求,如果是連接請求,則創建連接,並創建Handler對象用來響應事件
  • 如果不是連接請求,則由Reactor分發調用對應的Handler處理。
  • Handler只負責響應事件,不負責業務邏輯。將業務邏輯分發給某個線程池處理

優點:充分利用多核CPU的性能
缺點:多線程數據共享和訪問比較複雜。Reactor處理所有的監聽和響應,仍是單線程,高併發的場景下仍然有瓶頸。

主從Reactor多線程

主從Reactor多線程
說明

  • Reactor分爲主Reactor和從Reactor,MainReactor可以關聯多個SubReacor
  • 主線程Reactor只負責連接請求,創建請求連接完成後,將連接分配給SubReactor
  • 從Reactor負責監聽事件和調用對應的handler響應事件
  • handler完成read後,分發給線程池進行業務處理,線程池分配的線程完成處理後,將結果趕回給handler
  • handler將結果send給client

優點:主從Reactor分工明確
缺點:編程複雜度高

Netty模型


Netty模型

  • BossGroup只負責監聽連接事件,有連接請求時,建立連接,並將返回的NIOSocketChannel註冊到WorkGroup下的某個Selector上
  • WorkGroup只負責監聽IO事件,當有讀寫請求時,處理IO事件。
  • 每個Work NIOEventLoop處理業務時,會用到PipeLine(管道),PipeLine包含Channel,管道中維護了很多處理器。
代碼示例

NettyServer

		/**
         * BossGroup和WorkGroup都是無限循環的
         * 含有的子線程數量(NioEventLoop)
         * 默認是cpu核心數量的兩倍
         */
        // 創建BossGroup , 只監聽連接事件
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // 創建WorkGroup , 只監聽IO事件
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            // 鏈式編程設置參數
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workGroup) //設置兩個線程組
                    .channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作爲服務器通道實現
                    .option(ChannelOption.SO_BACKLOG, 128) // 設置線程隊列得到連接的個數
                    .childOption(ChannelOption.SO_KEEPALIVE, true) // 設置保持活動連接狀態
                    .childHandler(new ChannelInitializer<SocketChannel>() { // 創建一個通道連接測試對象
                        // 給pipeline設置處理器
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // 添加處理器
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    }); //給WorkGroup的EventLoop設置處理器
            System.out.println("服務器準備好了...");
            // 綁定一個端口並同步,生成一個ChannelFuture對象
            // 啓動服務器(並綁定端口)
            ChannelFuture cf = bootstrap.bind(6666).sync();
            // 對 “關閉通道” 事件監聽
            cf.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

NettyServerHandler

/**
 * 自定義的handler,需要繼承netty規定好的某個HandlerAdapter
 **/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 讀取數據
     * 1. ctx 上下文對象,含有管道、通道、地址
     * 2. msg 消息
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("----- ctx : " + ctx);
        System.out.println("客戶端地址 : " + ctx.channel().remoteAddress());
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("msg : " + buf.toString(CharsetUtil.UTF_8));
    }

    /**
     * 讀取數據完成
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 寫數據並flush
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客戶端", CharsetUtil.UTF_8));
    }

    /**
     * 處理異常
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.channel().close();
    }
}

NettyClient

		NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            // Bootstrap
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // 加入自己的處理器
                            socketChannel.pipeline().addLast(new NettyClientHandler());
                        }
                    });
            System.out.println("客戶端準備好了....");
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6666).sync();

            // 監聽關閉事件
            channelFuture.channel().closeFuture().sync();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }

NettyClientHandler

public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("喵喵喵", CharsetUtil.UTF_8));
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("服務器回覆的信息 ; " + buf.toString(CharsetUtil.UTF_8));
        System.out.println("服務器地址 : " + ctx.channel().remoteAddress());
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}
說明
  • BossGroup只負責監聽連接事件,WorkGroup只監聽IO事件。兩者都包含多個NIOEventLoop,無限循環執行
  • BossGroup和WorkGroup默認含有的子線程(NIOEventLoop)數量爲cpu核心的兩倍
  • NIOEventLoop有一個Selector,用於監聽綁定在其上的socket通道。NIOEventLoop包含一個taskQueue
  • 一個NIOEventLoop可以監聽多個NIOChannel
  • 處理器需要重寫,根據需要重寫方法
    在這裏插入圖片描述
  • 上下文對象ChannelHandlerContext包含了pipeline,從pipeline中可以獲取NIOChannel
  • pipeline本質是一個雙向鏈表
    pipeline
  • NIOChannel中也包含pipeline,兩者相互對應

異步執行

在Handler中,儘量不要執行耗時長的業務,避免阻塞,考慮使用異步執行耗時長的任務。

使用普通任務,提交到TaskQueue

	@Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        // 如果要執行一個耗時長的業務,這裏可以考慮異步執行
        // 1. 使用channel中eventLoop異步執行,任務提交到TaskQueue中
        ctx.channel().eventLoop().execute(()->{
            try {
                Thread.sleep(10 * 1000);
                ctx.writeAndFlush(Unpooled.copiedBuffer("hahaha",CharsetUtil.UTF_8));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

使用定時任務,提交到scheduleTaskQueue

		// 使用定時任務,任務提交到scheduleTaskQueue
        ctx.channel().eventLoop().schedule(()->{
            try {
                Thread.sleep(10 * 1000);
                ctx.writeAndFlush(Unpooled.copiedBuffer("hahaha2",CharsetUtil.UTF_8));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },5,TimeUnit.SECONDS);

非當前Reactor線程調用Channel中的方法

如推送系統中,根據用戶標識,找到對應的Channel應用,然後調用Write方法推送信息。最終Write會提交到任務隊列中被異步消費。

可以在ChannelInitializer中的initChannel方法中,將Channel的hashcode與用戶綁定,加入到集合中,需要推送消息時,從集合中獲取Channel,加入到相應的NIOEventLoop的TaskQueue或schdueleTaskQueue推送消息

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