暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

理解Reactor模型

小青菜的技术博客 2021-04-12
2351


1. 基本概念

1.1 背景

  • 为了处理Web请求,有以下两种架构处理方式

1.1.1 基于线程的架构

  • 也就是一个连接一个线程,即BIO的方式

  • 优点:

    • 可以隔离每个请求,请求之间互不影响

  • 缺点:

    • 严重依赖于线程,虽然线程相比于进程轻量级,但还是很占用资源

      • 线程的创建和销毁成本很高(可以通过线程池和有界阻塞队列优化)

      • 线程本身占用较大的内存

      • 线程的切换成本是很高的

      • 容易造成锯齿状的系统负载

      • 无法解决C10K问题

  • 适用场景

    • 适用于连接数目比较小且固定的架构

1.1.2 事件驱动架构

  • 通过事件驱动的方法可以将线程与连接分开,连接只将线程用于特定回调或程序的事件处理。

  • Reactor是事件驱动架构的一种实现技术。

    • 简单来说,它使用单线程事件循环对资源发出的事件进行阻塞,并将其分配给相应的处理程序和回调。

    • 只要注册了事件的处理程序和回调来处理它们,就不需要阻塞I O。事件是指实例,例如新的传入连接,可以读取,可以写入等。这些处理程序/回调可以在多核环境中利用线程池。

  • 优点

    • 事件处理器之间高度解耦,扩展性好

    • 性能较好,因为事件的异步本质,不易产生阻塞

  • 缺点

    • 开发相对复杂

1.2 Reactor的概念

1.2.1 Reactor模式的组成部分:

  • Reactor

    • 负责响应IO事件,当检测到一个新的事件,将其发送给相应的Handler去处理;

    • 对应的实现有Java的Selector

  • Handler

    • 处理程序执行与I O事件有关的实际工作。

1.2.2 优点

  • 响应快,不必为单个事件处理所阻塞,虽然Reactor本身依然是同步的;

  • 编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;

  • 可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;

  • 可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性;

1.2.3 缺点

  • 相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛,并且不易于调试。

  • Reactor模式需要底层的Synchronous Event Demultiplexer支持

    • 比如Java中的Selector支持,操作系统的select系统调用支持,如果要自己实现Synchronous Event Demultiplexer可能不会有那么高效。

  • 不适用于类似于大文件传输等耗时长的IO操作

    • Reactor模式在IO读写数据时还是在同一个线程中实现的,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间

    • 使用传统的Thread-Per-Connection或许是一个更好的选择,或此时使用改进版的Reactor模式如Proactor模式。

2. 单线程Reactor模型

  • Reactor模型的朴素原型:所有的操作都在同一个NIO线程上面完成

2.1 单线程Reactor的介绍

  •  如下图所示:

    • 这是最简单的单Reactor单线程模型。Reactor线程是个多面手,负责多路分离套接字,Accept新连接,并分派请求到Handler处理器中。

    • Reactor和Hander 处于一条线程执行。

2.2 Reactor单线程模式的分析

  • 由于是单线程,只要有一个handler出现阻塞情况,会导致其他client都被阻塞。并且不能充分利用多核资源

  • 因此,单线程模型仅仅适用于handler 中业务处理组件能快速完成的场景。

2.3 单线程Reactor的参考代码

  • 基于Java的NIO基础实现的单线程Reactor,可以参考:https://www.icode9.com/content-4-622761.html

  • 基于Netty实现的单线程Reactor模型代码如下:

    • 最核心的方法:ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)

    • parentGroup主要是用来接收socket连接请求,然后会产生一个channel,并把这个channel交给childGroup线程来处理

    • childGroup主要用来处理channel里的所有事件和IO

    public void bind(int port) {
    EventLoopGroup reactorGroup = new NioEventLoopGroup(1);
    try {
    ServerBootstrap b = new ServerBootstrap();
    b.group(reactorGroup, reactorGroup)
    .channel(NioServerSocketChannel.class)
    .childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
    ch.pipeline().addLast("http-codec", new HttpServerCodec());
    ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
    ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
    后面代码省略
    }
    });


    Channel ch = b.bind(port).sync().channel();
    ch.closeFuture().sync();
    } catch (InterruptedException e) {
    e.printStackTrace();
    } finally {
    reactorGroup.shutdownGracefully();
    }
    }

    3. 多线程的Reactor

    3.1 基于线程池的改进

    • 在单线程Reactor模式基础上,做如下改进:

      • 将Handler处理器的执行放入线程池,多线程进行业务处理。

      • 而对于Reactor而言,可以仍为单个线程

    • 一个简单的图如下:

    3.2 特点

    • 有一个专门的NIO线程用于监听服务端,接收客户端的TCP连接请求;

    • 网络I/O操作--读、写等以及业务处理由一个NIO线程池负责,由这些NIO线程负责消息的读取、解码、编码和发送;

    3.3 适用场景

    • 在绝大多数场景下,Reactor多线程模型都可以满足性能需求;

    • 但是,在极特殊应用场景中,一个NIO线程负责监听和处理所有的客户端连接可能会存在性能问题。例如百万客户端并发连接,或者服务端需要对客户端的握手信息进行安全认证,认证本身非常损耗性能。这类场景下,单独一个Acceptor线程可能会存在性能不足问题,为了解决性能问题,产生了第三种Reactor线程模型--主从Reactor多线程模型。

    3.4 多线程Reactor的参考代码

    • 基于Java的NIO的多线程Reactor的代码实现可以参考:https://www.cnblogs.com/crazymakercircle/p/9833847.html

    • 基于Netty的多线程Reactor的代码实现

      • 与单线程Reactor的Netty实现不同的是没有共用同一个EventLoopGroup

      public void bind2(int port) {
      EventLoopGroup acceptorGroup = new NioEventLoopGroup(1);
      NioEventLoopGroup ioGroup = new NioEventLoopGroup();
      try {
      ServerBootstrap b = new ServerBootstrap();
      b.group(acceptorGroup, ioGroup)
      .channel(NioServerSocketChannel.class)
      .childHandler(new ChannelInitializer<SocketChannel>() {
      @Override
      protected void initChannel(SocketChannel ch) throws Exception {
      ch.pipeline().addLast("http-codec", new HttpServerCodec());
      ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
      ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
      后面代码省略
      }
      });


      Channel ch = b.bind(port).sync().channel();
      ch.closeFuture().sync();
      } catch (InterruptedException e) {
      e.printStackTrace();
      } finally {
      acceptorGroup.shutdownGracefully();
      ioGroup.shutdownGracefully();
      }
      }

      4. Reactor持续改进

      4.1 主从Reactor多线程模型的特点

      • 在多线程Reactor的基础上,将接收客户端连接的部分(acceptor部分)使用线程池来处理

      • acceptor接收到客户端TCP连接请求处理完成后(可能包含接入认证等),将新创建的SocketChannel注册到I/O线程池(sub reactor线程池)的某个I/O线程上,由它负责SocketChannel的读写和编解码以及业务处理工作。


      4.2 主从Reactor的参考代码

      • 基于Java的NIO的主从Reactor代码实现可以参考:https://blog.csdn.net/weixin_42412601/article/details/106610642

      • 基于Netty的主从Reactor代码实现

        public void bind3(int port) {
        EventLoopGroup acceptorGroup = new NioEventLoopGroup();
        NioEventLoopGroup ioGroup = new NioEventLoopGroup();
        try {
        ServerBootstrap b = new ServerBootstrap();
        b.group(acceptorGroup, ioGroup)
        .channel(NioServerSocketChannel.class)
        .childHandler(new ChannelInitializer<SocketChannel>() {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast("http-codec", new HttpServerCodec());
        ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
        ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
        //后面代码省略
        }
        });


        Channel ch = b.bind(port).sync().channel();
        ch.closeFuture().sync();
        } catch (InterruptedException e) {
        e.printStackTrace();
        } finally {
        acceptorGroup.shutdownGracefully();
        ioGroup.shutdownGracefully();
        }
        }


        5. 参考文章



        https://www.cnblogs.com/crazymakercircle/p/9833847.html

        https://dzone.com/articles/understanding-reactor-pattern-thread-based-and-eve

        https://tech.meituan.com/2016/11/04/nio.html

        https://blog.csdn.net/woaixiaopangniu521/article/details/70256018

        https://www.cnblogs.com/duanxz/p/3696849.html


        文章转载自小青菜的技术博客,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论