• Java——Netty 线程模型与Reactor 模式(转)


     

    前言

            Netty 的线程模型是基于NIO的Selector 构建的,使用了异步驱动的Reactor 模式来构建的线程模型,可以很好的支持成百上千的 SocketChannel 连接。由于 READ/WRITE 都是非阻塞的,可以充分提升I/O线程的运行效率 ,避免了IO阻塞导致线程挂起, 同时可以让一个线程支持对多个客户端的连接SocketChannel的 READ/WRITE 操作, 从根本上解决了传统阻塞IO的一线程处理一连接的弊端。

    高效率的Reactor模式

    Reactor 模式 

    是一种为处理服务请求并发,提交到一个或者多个服务处理程序的事件设计模式。当请求抵达后,服务处理程序使用解多路分配策略,然后同步地派发这些请求至相关的请求处理程序
    (来自维基百科:https://zh.wikipedia.org/wiki/反应器模式

    常见的reactor模式有以下三种

    1. 单线程reactor
    2. 多线程reactor
    3. 主从reactor

    1、单线程reactor

    ractor 单线程模式是指所有的I/O操作都在一个NIO线程完成,该线程的职责:

    1.作为NIO服务端,接收客户端TCP连接

    2.作为NIO客户端,向客户端发送TCP连接

    3.READ/WRITE 客户端的请求

     

    不过单线程的reactor 模式无法发挥多核的优势,因此对于高并发量的系统仍然存在瓶颈,主要原因如下:

    1、reactor 线程既要处理来自客户端的连接,又要处理READ/WRITE/编码/解码。即便cpu 100% 也难以满足实际场景的需求

    多线程Reactor 解决了这些问题

    2、多线程reactor模型

    reactor 多线程的实现最大的区别是拥有一个专门用来处理实际I/O 操作是线程池

    优点:

    1、拥有一个Acceptor 专门用来监听请求的I/O 类型

    2、使用专门线程池可以提高acceptor的并发量,并且可以将同一个SocketChannel 放于同一个I/O 线程处理,同一个I/O线程可以处理多个SocketChannel的READ/WRITE事件

    在大部分场景,该线程模型都能处理,但存在这样一种场景:单个Acceptor 线程 可能会因为需要监听大量的 SocketChannel 连接 或 I/O事件处理或在建立建立时需要进行安全的握手认证、黑白名单过滤,而导致出现性能瓶颈。所以这种场景下,单独一个Accceptor 会导致性能不足,便出现了第三种线程模型,主从Reactor 模型

    3、主从reactor 多线程模型

    相比多线程reactor模型,主从reactor多线程模型拥有了一个独立处理 SocketChannel 连接的线程池,当客户端从Acceptor建立连接之后,便将该连接绑定到subreactor 线程池中的某个线程中,然后由该线程绑定客户端感兴趣的I/O事件(READ/WRITE),监听客户端连接请求,最后处理。

    mainReactor : 监听 ServerSocketChannel 、建立与 SocketChannel  的连接、将完成建立连接之后的Socket 交给subReactor

    subReactor : 监听SocketChannel的 I/O事件,完成编解码、相应的业务处理(默认为CPU个数)

    Netty的线程模型可以通过配置不同参数实现不同reactor的线程模型(netty4版本未发现多线程reactor版本实现方式)

    1、单线程reactor

    复制代码
    NioEventLoopGroup loopGroup = new NioEventLoopGroup(1);
    ServerBootstrap b = new ServerBootstrap();
    b.group(loopGroup, loopGroup)
            .channel(NioServerSocketChannel.class)
            .handler(new LoggingHandler(LogLevel.DEBUG))
            .childHandler(new ChannelInitializer<Channel>() {
                @Override
                protected void initChannel(Channel ch) throws Exception {
                    // xxx
                }
            });
    try {
        ChannelFuture future = b.bind(8086).sync();
        future.channel().closeFuture().sync();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        loopGroup.shutdownGracefully().sync();
    }
    复制代码

    2、主从reactor

    复制代码
    NioEventLoopGroup mainGroup = new NioEventLoopGroup(1);
    NioEventLoopGroup subGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors());
    ServerBootstrap b = new ServerBootstrap();
    b.group(mainGroup, subGroup)
            .channel(NioServerSocketChannel.class)
            .handler(new LoggingHandler(LogLevel.DEBUG))
            .childHandler(new ChannelInitializer<Channel>() {
                @Override
                protected void initChannel(Channel ch) throws Exception {
                    // xxx
                }
            });
     
    try {
        ChannelFuture future = b.bind(8086).sync();
        future.channel().closeFuture().sync();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        mainGroup.shutdownGracefully().sync();
        subGroup.shutdownGracefully().sync();
    }
    复制代码

    NioEventLoop 与 NioEventLoopGroup 介绍

    1、NioEventLoopGroup 是通过实现JUC 包相关线程池的接口来达到自定义线程池的目的,而NioEventLoop 则持有一个线程,用来处理SocketChannel 建立连接之后的I/O事件。

    2、在初始化 NioEventLoopGroup 时,将初始化一定数量 NioEventLoop,并将这些初始化之后的 NioEventLoops 的引用交给NioEventLoopGroup 管理

    3、由接口的实现可以看出,他们都是实现了juc 线程池的顶层接口  Executor,因此都具有公共的 execute(Runable command)及 submit(Runable task) 方法,那么这些通用的方法将为 NioEventLoopGroup  把任务交给 子的NioEventLoop处理提供标准及便利

    复制代码
    //轮询一个子的 NioEventLoop 进行提交处理
    @Override
    public Future<?> submit(Runnable task) {
        return next().submit(task);
    }
    ......
    @Override
    public void execute(Runnable command) {
        next().execute(command);
    }
    复制代码

    Netty 具体线程模

    从整个线程模型来看 相关组件之间的是如何相互协作的

     

    1、初始化NioEventLoopGroup ,  将为ServerSocketChannel 提供一个 bossGroup 线程池,为 SockerChannel 的I/O 事件处理 提供一个workGroup

    2、使用ServerBootstrap 绑定端口等相关信息,此时会初始化一个ServerSocketChannel 和 bossGroup,并且将 ServerSocketChannel 绑定到 bossGroup 中的一个NioEventLoop 中进行监听客户端的连接请求

    3、当 Client 发起连接请求时,首先经过三次握手通过后,然后服务端被触发,接着收到连接成功的通知(因为是异步所以是触发)

    4、ServerSocketChannel 收到连接成功的通知后,将建立好的连接交给 workGroup中的某个NioEventLoop,然后将感兴趣的事件注册到 该 NioEventLoop 持有的Selector上,等待Client 下一次请求

    5、当 Client 发起 READ/WRITE 相关的请求时,则提交给NioEventLoop  进行处理 

     

    参考:

    http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf

  • 相关阅读:
    Android获取Java类名/文件名/方法名/行号
    Service的onStartCommand方法的返回值和参数详解
    Centos7 k8s Replication副本控制器
    Centos7 k8s 基础单元pod
    Linux问题故障定位
    Linux 搭建YUM仓库源详解
    Centos7 k8s安装部署
    Centos7 KVM热迁移
    Centos7 KVM热添加网卡、内存、CPU
    Centos7 KVM虚拟机硬盘热添加和扩容
  • 原文地址:https://www.cnblogs.com/xingchong/p/12763039.html
Copyright © 2020-2023  润新知