Netty--高性能架構設計

Netty架構設計

Netty簡介

  1. 一個異步的、基於事件驅動的網絡應用框架,用以快速開發高性能、高可靠性的網路IO程序;
  2. 主要針對TCP協議下,面向clients端的高併發應用,或者大量數據傳輸的應用;
  3. 本質是一個NIO框架,適用於服務器通信相關的多種應用場景;

線程模型

名詞解釋 :

黃色的框表示對象, 藍色的框表示線程,白色的框表示方法(API);

傳統阻塞IO模型

在這裏插入圖片描述
特點

  1. 採用阻塞IO模式獲取輸入的數據;
  2. 每個連接都需要獨立的線程完成數據的輸入、業務處理、數據返回等工作;

優缺點分析

  1. 當併發數很大,就會創建大量的線程,佔用很大系統資源;
  2. 連接創建後,如果當前線程暫時沒有數據可讀,該線程會阻塞在read,造成線程資源浪費;
Reactor模型

在這裏插入圖片描述
特點

  1. 基於 I/O 複用模型:多個連接(Client)共用一個阻塞對象(ServiceHandler),應用程序只需要在一個阻塞對象等待,無需阻塞等待的所有連接。
  2. 當某個連接有新的數據可以處理時,操作系統通知應用程序,線程從阻塞狀態返回,開始進行業務處理;
  3. 服務器端程序處理傳入的多個請求,並將它們同步分派到相應的處理線程, 基於線程池複用線程原則,不必再爲每個連接創建線程,將連接完成後的業務處理任務分配給線程進行處理,一個線程就可以複用處理多個連接的業務,因此Reactor模式也叫 Dispatcher模式;
  4. Reactor 模式使用IO複用監聽事件, 收到事件後,分發給某個線程(進程), 這點就是網絡服務器高併發處理關鍵;
  5. 響應快,不必爲單個同步時間所阻塞,雖然 Reactor 本身依然是同步的,多個SubReactor有效的避免了阻塞;
  6. 可以最大程度的避免複雜的多線程及同步問題,並且避免了多線程/進程的切換開銷;
  7. 擴展性好,可以方便的通過增加 Reactor 實例個數來充分利用 CPU 資源;
  8. 複用性好,Reactor 模型本身與具體事件處理邏輯無關,具有很高的複用性;

單Reactor單線程模型
在這裏插入圖片描述

  1. 上圖Select可以實現應用程序通過一個阻塞對象監聽多路連接請求;
  2. Reactor對象通過Select監控客戶端請求事件,收到事件後通過Dispatch 進行分發;
  3. 如果是建立連接請求事件,則由 Acceptor 通過 Accept 處理連接請求,然後創建一個 Handler 對象處理連接完成後的後續業務處理;
  4. 如果不是建立連接事件,則 Reactor 會分發調用連接對應的 Handler 來響應;
  5. Handler 會完成Read→業務處理→Send的完整業務流程

優缺點:

  1. 模型簡單,沒有多線程、進程通信、競爭的問題,全部都在一個線程中完成;
  2. 性能問題,只有一個線程,無法完全發揮多核 CPU 的性能。Handler 在處理某個連接上的業務時,整個進程無法處理其他連接事件,很容易導致性能瓶頸;
  3. 可靠性問題,線程意外終止,或者進入死循環,會導致整個系統通信模塊不可用,不能接收和處理外部消息,造成節點故障;
  4. 使用場景:客戶端的數量有限,業務處理非常快速,比如 Redis在業務處理的時間複雜度 O(1) 的情況;

單Reactor多線程
在這裏插入圖片描述

  1. Reactor 對象通過select 監控客戶端請求事件, 收到事件後,通過dispatch進行分發;
  2. 如果建立連接請求, 則右Acceptor 通過accept 處理連接請求, 然後創建一個Handler對象處理完成連接後的各種事件;
  3. 如果不是連接請求,則由reactor分發調用連接對應的handler 來處理;
  4. handler 只負責響應事件,不做具體的業務處理,通過read 讀取數據後,會分發給後面的worker線程池的某個線程處理業務;
  5. worker 線程池會分配獨立線程完成真正的業務,並將結果返回handler;
  6. handler收到響應後,通過send將結果返回給client;

優缺點:

  1. 可以充分的利用多核cpu 的處理能力;
  2. 多線程數據共享和訪問比較複雜, reactor 處理所有的事件的監聽和響應,在單線程運行, 在高併發場景容易出現性能瓶頸;

主從Reactor多線程
在這裏插入圖片描述

  1. Reactor主線程 MainReactor 對象通過select 監聽連接事件, 收到事件後,通過Acceptor 處理連接事件,Reactor 主線程可以對應多個Reactor 子線程, 即MainRecator 可以關聯多個SubReactor;
  2. 當 Acceptor 處理連接事件後,MainReactor 將連接分配給SubReactor ;
  3. Subreactor 將連接加入到連接隊列進行監聽,並創建handler進行各種事件處理;
  4. 當有新事件發生時, subreactor 就會調用對應的handler處理;
  5. handler 通過read 讀取數據,分發給後面的worker 線程處理;
  6. worker 線程池分配獨立的worker 線程進行業務處理,並返回結果;
  7. handler 收到響應的結果後,再通過send 將結果返回給client;

優缺點:

  1. 父線程與子線程的數據交互簡單職責明確,父線程只需要接收新連接,子線程完成後續的業務處理;
  2. 父線程與子線程的數據交互簡單,Reactor 主線程只需要把新連接傳給子線程,子線程無需返回數據;
  3. 編程複雜度較高;
Netty模型

在這裏插入圖片描述

  1. Netty抽象出兩組線程池 BossGroup 專門負責接收客戶端的連接, WorkerGroup 專門負責網絡的讀寫;
  2. BossGroup 和 WorkerGroup 類型都是 NioEventLoopGroup;
  3. NioEventLoopGroup 相當於一個事件循環組, 這個組中含有多個事件循環 ,每一個事件循環是 NioEventLoop;
  4. NioEventLoop 表示一個不斷循環的執行處理任務的線程, 每個NioEventLoop 都有一個selector , 用於監聽綁定在其上的socket的網絡通訊;
  5. NioEventLoopGroup 可以有多個線程, 即可以含有多個NioEventLoop;
  6. 每個Boss下的NioEventLoop 循環執行的步驟有3步:輪詢accept 事件、處理accept 事件,與client建立連接,生成NioScocketChannel,並將其註冊到某個worker中NIOEventLoop上的selector中、處理任務隊列的任務 , 即 runAllTasks;
  7. 每個 Worker下的NIOEventLoop 循環執行的步驟:輪詢read,write 事件、處理i/o事件,即read , write 事件,在對應NioScocketChannel 處理、處理任務隊列的任務 , 即 runAllTasks;
  8. 每個Worker下的NIOEventLoop 處理業務時,會使用pipeline(管道), pipeline 中包含了 channel , 即通過pipeline 可以獲取到對應通道, 管道中維護了很多的 處理器;
  9. Worker下的NIOEventLoop 處理業務採用輪詢機制;
Netty-Demo

服務器端:

package com.neei.netty.simple;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @param
 * @Author: AaNeei
 * @Date: 2019/12/4  22:24
 * @Description: 遊學網
 * @throws:
 */
public class NettyServer {

    public static void main(String[] args) throws InterruptedException {

        //創建BossGroup線程組
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //創建WorkerGroup線程組
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        //創建服務器端啓動對象
        ServerBootstrap bootstrap = new ServerBootstrap();
        try {
            //設置參數
            bootstrap.group(bossGroup, workerGroup)//綁定線程組
                    .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 {
                            //註冊處理器handler
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });//設置WorkerGroup的 EventLoop 對應的管道設置處理器
            //服務準備完畢
            System.out.println("-------------  NettyServer is ready  --------------");
            //綁定端口並同步,啓動服務
            ChannelFuture channelFuture = bootstrap.bind(6666).sync();
            //監聽關閉的通道
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

服務器Handler:

package com.neei.netty.simple;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * @param
 * @Author: AaNeei
 * @Date: 2019/12/5  22:13
 * @Description: 遊學網
 * @throws:
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 讀取數據方法
     * 當通道有讀取事件時觸發該方法
     * @param ctx 上下文對象 包含:pipeline、channel等
     * @param msg 數據內容
     * @throws Exception
     */
     @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //如果存在耗時操作,需要將其放到task隊列中去異步執行
        //1.自定義線程
        ctx.channel().eventLoop().execute(() -> {
            try {
                Thread.sleep(10000L);
                ctx.writeAndFlush(Unpooled.copiedBuffer("這是耗時操作", CharsetUtil.UTF_8));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        //2.定時執行
        ctx.channel().eventLoop().schedule(() -> {
            ctx.writeAndFlush(Unpooled.copiedBuffer("這是定時執行操作", CharsetUtil.UTF_8));
        }, 5, TimeUnit.SECONDS);
        
        System.out.println("server ctx=" + ctx);
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("客戶端發送的消息是:" + buf.toString(CharsetUtil.UTF_8));
        System.out.println("客戶端地址:" + ctx.channel().localAddress());
    }

    /**
     * 讀取數據完成之後 觸發該方法
     * 可以執行的操作
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,NettyClient", CharsetUtil.UTF_8));
    }

    /**
     * 異常觸發該方法
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
    }
}

客戶端

package com.neei.netty.simple;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * @param
 * @Author: AaNeei
 * @Date: 2019/12/5  22:29
 * @Description: 遊學網
 * @throws:
 */
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyClientHandler());
                        }
                    });
            System.out.println("----------client is ok---------");
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6666).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}

客戶端handler:

package com.neei.netty.simple;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * @param
 * @Author: AaNeei
 * @Date: 2019/12/5  22:37
 * @Description: 遊學網
 * @throws:
 */
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    /**
     * 通道就緒觸發該方法
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client ctx=" + ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,NettyServer", CharsetUtil.UTF_8));
    }

    /**
     * 通道有讀事件時觸發該方法
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @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().localAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        cause.printStackTrace();
    }
}

注:學習資源連接穀粒學院

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