JavaNIO--5.多Reactor模式

一. 多Reactor模型

1.1最优的解决方案

当我们经历了最初的Reactor模型,实现了单线程基于Selector选择器的NIO通信模式,到升级为多线程Reactor模型,由一个Selector实现事件的分发到不同的线程进行服务器的操作,最后我们找到了一种最优化的解决方案——多Reactor模型。
这里写图片描述

从图中可以直接看出,我们实现了一个主反应器(MainReactor)和多个次反应器(subReactor)主反应器中包含了一个ServerSocketChannel用来接收所有的请求,主反应器中的选择器通过事件循环(EventLoop),交由acceptor进行分发到不同的次反应器次反应器维护主反应器交付的SocketChannel,其中的选择器通过事件循环,来进行readwrite事件的派发。

1.2模型代码

1.2.1主反应器

public class NioMainReactor {

    // 主反应器中的选择器
    private Selector reactorSelector;
    //主反应器中的服务通道
    private ServerSocketChannel serverSocketChannel;
    // 次反应器数组
    private NioSubReactor[] subReactor;
    // cpu核心数
    int coreNum;

    /**
     * 主反应器构造函数
     * 负责确定监听端口,打开选择器,打开通道,初始化次反应器数组
     * 
     * @param port
     * @throws IOException
     */
    public NioMainReactor(int port) throws IOException {
        reactorSelector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(reactorSelector, SelectionKey.OP_ACCEPT);
        serverSocketChannel.bind(new InetSocketAddress(port));  
        // 获得当前机器环境的cpu核心数量
        coreNum = Runtime.getRuntime().availableProcessors();
        subReactor = new NioSubReactor[coreNum];
        for(int i =0; i<subReactor.length;i++) {
            subReactor[i] = new NioSubReactor();
        }
    }

    /**
     * 主反应器的事件循环,用于接收外界的socket请求,并分发给次选择器
     * 
     * @throws IOException
     */
    private void selectLoop() throws IOException {
        int index = 0;
        while(true) {
            reactorSelector.select();
            Set<SelectionKey> selectionKeys = reactorSelector.selectedKeys();
            for (SelectionKey selectionKey : selectionKeys) {
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel();
                    // 负载均衡的策略是轮流分发任务
                    index = (index++)%coreNum;
                    accept(serverSocketChannel,index);
                }
                selectionKeys.remove(selectionKey);
            }
        }
    }

    /**
     * 负责将接收到的SocketChannel分发给次反应器
     * 
     * @param serverSocketChannel
     * @param index
     * @throws IOException
     */
    private void accept(ServerSocketChannel serverSocketChannel,int index) throws IOException {
        SocketChannel socketChannel = serverSocketChannel.accept();
        if (socketChannel != null) {
            socketChannel.configureBlocking(false);
            // 分发给subReactor处理
            NioSubReactor currentSubReactor = subReactor[index];
            currentSubReactor.addSocketChannel(socketChannel);
            currentSubReactor.wakeup();
        }
    }
}

1.2.2次反应器

public class NioSubReactor {

    //次反应器中的选择器
    private Selector subReactorSelector;
    // 当前机器环境中CPU核心数量
    private static int coreNum = Runtime.getRuntime().availableProcessors();
    // 静态线程池,每一条线程对应一个次反应器
    private static Executor pool = Executors.newFixedThreadPool(coreNum);

    public NioSubReactor() throws IOException {
        subReactorSelector = Selector.open();
    }

    /**
     * 添加通道到该次反应器
     * 注册到次反应器的选择器中
     * 同时创建该通道的处理器类
     * 
     * @param socketChannel
     * @throws ClosedChannelException
     */
    public void addScocketChannel(SocketChannel socketChannel) throws ClosedChannelException {
        // 主选择器传入的通道注册到次次反应器
        SelectionKey selectionKey = socketChannel.register(subReactorSelector, SelectionKey.OP_READ);
        selectionKey.attach(new Handler(socketChannel));
    }

    public void wakeup() {
        // 唤醒因为选择器选择阻塞的方法
        subReactorSelector.wakeup();
    }

    /**
     * 事件循环
     * 
     * @throws IOException
     */
    public void selectLoop() throws IOException {
        while(true) {
            subReactorSelector.select();
            Set<SelectionKey>selectionKeys = subReactorSelector.selectedKeys();
            for (SelectionKey selectionKey : selectionKeys) {
                // 获得绑定的处理器
                Handler handler = (Handler)selectionKey.attachment();
                if (selectionKey.isReadable()) {
                    handler.read();
                }else if (selectionKey.isWritable()) {
                    handler.write();
                }
            }
            selectionKeys.clear();
        }
    }


    /**
     * 通道数据处理内部类
     * 
     * @author CringKong
     *
     */
    class Handler{

        private SocketChannel socketChannel;

        public Handler(SocketChannel socketChannel) {
            this.socketChannel = socketChannel;
        }

        public void read() {
            // 数据处理后执行socketChannel.read();
        }

        public void write() {
            // 数据处理后执行socketChannel.write();
        }
    }

}

1.3模型代码分析

多Reactor模式其实就是多个单Reactor模式的组合模式,而且每个Reactor都是同步阻塞的模型,为了说明这一点,我们先来看主反应器的核心代码——事件循环。

    private void selectLoop() throws IOException {
        int index = 0;
        while(true) {
            reactorSelector.select();
            Set<SelectionKey> selectionKeys = reactorSelector.selectedKeys();
            for (SelectionKey selectionKey : selectionKeys) {
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel();
                    // 负载均衡的策略是轮流分发任务
                    index = (index++)%coreNum;
                    accept(serverSocketChannel,index);
                }
                selectionKeys.remove(selectionKey);
            }
        }
    }
  1. 同步:因为是单线程顺序执行,每一次accept(serverSocketChannel,index)都是在循环中顺序执行的,确保已经完成方法以后进行下一步操作。
  2. 阻塞:reactorSelector.select()方法调用以后会阻塞线程,直到有下一个连接接入,但在高并发的服务器情况下,几乎是没有阻塞的,因为待处理任务队列始终会有任务存在。

对于次选择器,我们可以使用异步处理的模式,也就是说和我们之前的多线程Reactor模型一样,每次都将对于通道数据的处理(I/O过程),启动一个单独的线程,但这样做有一些问题,需要很好的处理才能防止通道被重复选择(因为另外一个线程处理数据的同时,选择器会认为该通道依旧处于可读/写状态,所以会重复选择)。

所以对于次选择器,我们采用了同步非阻塞的模型来进行数据的处理,我们来看一下次选择器的核心代码——事件循环和处理器。

    public void selectLoop() throws IOException {
        while(true) {
            subReactorSelector.select();
            Set<SelectionKey>selectionKeys = subReactorSelector.selectedKeys();
            for (SelectionKey selectionKey : selectionKeys) {
                Handler handler = (Handler)selectionKey.attachment();
                if (selectionKey.isReadable()) {
                    handler.read();
                }else if (selectionKey.isWritable()) {
                    handler.write();
                }
            }
            selectionKeys.clear();
        }
    }

对于次选择器的事件循环,有以下几点:
1. Handler handler = (Handler)selectionKey.attachment(),用来获取相应通道的处理器对象,因为每个通道都唯一绑定了一个自己的处理器对象。
2. 根据事件类型的不同,进行不同的任务派派发selectionKey.isReadable()以及selectionKey.isWritable()验证当前通道的准备好的操作类型,然后通过调用处理器的方法来进行操作。
3. 处理器的read()和write()方法是非阻塞的,也就是说直接就可以进行数据的读写操作,而不需要等待操作系统内核的数据准备工作,这也是NIO比起BIO高效的原因。

    class Handler{

        private SocketChannel socketChannel;

        public Handler(SocketChannel socketChannel) {
            this.socketChannel = socketChannel;
        }

        public void read() {
            // 数据处理后执行socketChannel.read();
        }

        public void write() {
            // 数据处理后执行socketChannel.write();
        }

这只是一个模型版的处理器,真实的处理器需要进行判断传输是否完成,编码解码,数据处理,协议封装等等功能,但其核心的任务依旧是readwrite数据。

二. 总结

多反应器模型是一种实用的模型,Jboss-netty框架就是采用了这种模型。

  1. 这种模型相较于单Reactor模型极大的提高了对于高并发处理的性能,而且对于次选择器的数量,取了当前机器可用的CPU核心数,最大化的利用了多核优势。
  2. 相较于多线程Reactor模型,多Reactor模型避免了一些设计上的缺陷,同步的设计配合上非阻塞式的I/O模型,效率也是十分高的。

猜你喜欢

转载自blog.csdn.net/cringkong/article/details/80385628