Reactor模式

本文详细介绍了Reactor模式,包括单线程单Reactor模式下的回显服务器实现,分析了其在高并发场景下的问题,并进一步探讨了多线程Reactor模式的优化,通过引入线程池和多个Selector提升并发处理能力。
摘要由CSDN通过智能技术生成

模式简介

Reactor模式由Reactor线程、Handlers处理器两大角色组成,两
大角色的职责分别如下:

  • Reactor线程的职责:负责响应IO事件,并且分发到Handlers处理器
  • Handlers处理器的职责:非阻塞的执行业务处理逻辑

Connection Per Thread(一个线程处理一个连接)模式

对于每一个新的网络连接都分配给一个线程。每个线程都独自处理自己负责的socket连接的输入和输出。当然,服务器的监听线程也是独立的,任何socket连接的输入和输出处理都不会阻塞到后面新socket连接的监听和建立,这样服务器的吞吐量就得到了提升。早期版本的Tomcat服务器就是这样实现的。Connection Per Thread模式的缺点是对应于大量的连接,需要耗费大量的线程资源,对线程资源要求太高。而且,线程的反复创建、销毁、切换也需要代价。因此,在高并发的应用场景下,多线程OIO的缺陷是致命的。

单线程单Reactor模式

Reactor模式有点类似事件驱动模式。在事件驱动模式中,当有事件触发时,事件源会将事件分发到Handler(处理器),由
Handler负责事件处理,单线程Reactor就是Reactor和Handlers处在一个线程中执行。在Reactor模式中有Reactor和Handler两个重要的组件:

  • Reactor:负责查询IO事件,当检测到一个IO事件时将其发送给相应的Handler处理器去处理。这里的IO事件就是NIO中选择器查询出来的通道IO事件。
  • Handler:与IO事件(或者选择键)绑定,负责IO事件的处理,完成真正的连接建立、通道的读取、处理业务逻辑、负责将结果写到通道等。

回显服务器单线程单Reactor版本

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @author pangjian
 * @ClassName EchoServer
 * @Description 读取客户端的输入并回显到客户端,所以也叫回显服务器
 *  设计一个反应器类:EchoServerReactor类
 *  设计两个处理器类:AcceptorHandler新连接处理器、EchoHandler回显处理器。
 * @date 2022/6/11 14:23
 */

public class EchoServerReactor implements Runnable{

    Selector selector;

    ServerSocketChannel serverSocketChannel;

    public EchoServerReactor() throws IOException {
        // 1.创建选择器
        selector = Selector.open();
        // 2.创建通道
        serverSocketChannel = ServerSocketChannel.open();
        // 3.将通道设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 4.绑定链接
        serverSocketChannel.bind(new InetSocketAddress(18899));
        SelectionKey sk = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        sk.attach(new AcceptorHandler());
    }

    @Override
    public void run() {
        try {
            while(!Thread.interrupted()) {
                selector.select();
                Set<SelectionKey> selected = selector.selectedKeys();
                Iterator<SelectionKey> it = selected.iterator();
                while (it.hasNext()) {
                    SelectionKey sk = it.next();
                    dispatch(sk);
                }
                selected.clear();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * @Description: 事件分发执行到IOHandler(也就是之前绑定的EchoHandler实例中的run()方法)的run()方法,直接执行run并不是新建一个子线程,start才会创建一个子线程,所以为单线程Reactor,完成数据传输处理。
     * @Param sk:
     * @return void
     * @date 2022/6/11 14:43
    */
    void dispatch(SelectionKey sk) {
        Runnable handler = (Runnable) sk.attachment();
        // 调用之前,附加绑定到选择键的handle对象
        if (handler != null) {
            handler.run();
        }
    }

    /**
     * @Description:新链接处理器,监听连接事件,发生时将socketChannel和IOHandle实例自身作为附件附加到选择键中
     * @return
     * @date 2022/6/11 14:44
    */
    class AcceptorHandler implements Runnable {
        public void run() {
            try {
                SocketChannel channel = serverSocketChannel.accept();
                if (channel != null)
                    new EchoHandler(selector, channel);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Thread(new EchoServerReactor()).start();
    }
}

package nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

/**
 * @author pangjian
 * @ClassName EchoHandler
 * @Description 是一个传输处理器,主要是完成客户端的内容读取和回显
 * @date 2022/6/11 14:32
 */

public class EchoHandler implements Runnable{

    final SocketChannel channel;
    final SelectionKey sk;
    final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    // 处理器实例的状态:发送和接收,一个连接对应一个处理器实例
    static final int RECIEVING = 0, SENDING = 1;
    int state = RECIEVING;

    /**
     * @Description: Channel传输通道注册完成后,将IOHandler实例自身作为附
     * 件附加到选择键中。这样,在Reactor类分发事件(选择键)时,能执
     * 行到IOHandler的run()方法,完成数据传输处理。
     * @Param selector: 要和Server同一个选择器,所以作为参数传递进来
     * @Param c: socketChannel要进行注册和Handle事件绑定
     * @date 2022/6/12 12:51
    */
    EchoHandler(Selector selector, SocketChannel c) throws IOException {
        channel = c;
        c.configureBlocking(false);
        // 取得选择键,再设置感兴趣的IO事件
        sk = channel.register(selector, 0);
        // 将Handler自身作为选择键的附件,一个连接(通道实例)对应一个处理器实例
        sk.attach(this);
        // 注册Read就绪事件
        sk.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }

    public void run() {
        try {
            if (state == SENDING) {
                // 发送状态,把数据写入连接通道
                channel.write(byteBuffer);
                // byteBuffer切换成写模式,写完后,就准备开始从通道读
                byteBuffer.clear();
                // 注册read就绪事件,开始接收客户端数据
                sk.interestOps(SelectionKey.OP_READ);
                // 修改状态,进入接收状态
                state = RECIEVING;
            } else if (state == RECIEVING) {
                // 接收状态,从通道读取数据
                int length = 0;
                while ((length = channel.read(byteBuffer)) > 0) {
                    System.out.println(new String(byteBuffer.array(), 0,
                            length));
                }
                // 读完后,翻转byteBuffer的读写模式
                byteBuffer.flip();
                // 准备写数据到通道,注册write就绪事件
                sk.interestOps(SelectionKey.OP_WRITE);
                // 注册完成后,进入发送状态
                state = SENDING;
            }
            // 处理结束了,这里不能关闭select key,需要重复使用
            // sk.cancel();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

在单线程Reactor模式中,Reactor和Handler都在同一条线程中执行。这样当其中某个Handler阻塞时,会导致其他所有的Handler都得不到执行。在这种场景下,被阻塞的Handler不仅仅负责输入和输出处理的传输处理器,还包括负责新连接监听的AcceptorHandler处理器,可能导致服务器无响应。这是一个非常严重的缺陷,导致单线程反应器模型在生产场景中使用得比较少。


多线程Reactor模式

多线程Reactor的演进的两个方面:

  • 升级Handler。既要使用多线程,又要尽可能高效率,则可以考虑使用线程池。

将负责数据传输处理的IOHandler处理器的执行放入独立的线程池中。这样,业务处理线程与负责新连接监听的反应器线程就能相互隔离,避免服务器的连接监听受到阻塞。

  • 升级Reactor。可以考虑引入多个Selector(选择器),提升选择大量通道的能力。

如果服务器为多核的CPU,可以将反应器线程拆分为多个子反应器(SubReactor)线程;同时,引入多个选择器,并且为每一个SubReactor引入一个线程,一个线程负责一个选择器的事件轮询。这样充分释放了系统资源的能力,也大大提升了反应器管理大量连接或者监听大量传输通道的能力。

回显服务器优化

在这里插入图片描述

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * @author pangjian
 * @ClassName MultiThreadEchoServerReactor
 * @Description 多线程Reactor模式
 * @date 2022/6/12 14:12
 */

public class MultiThreadEchoServerReactor {

    ServerSocketChannel serverSocketChannel;
    AtomicInteger next = new AtomicInteger(0);
    // selectors集合,引入多个selector选择器
    Selector[] selectors = new Selector[2];
    /**
     * @Description: 引入多个子反应器,为了提升效率,可以让SubReactor的数量和选择器的数量一致,
     * 避免多个线程负责一个选择器,导致需要进行线程同步,引起效率低。
     * @date 2022/6/12 14:21
    */
    SubReactor[] subReactors = null;


    MultiThreadEchoServerReactor() throws IOException {
        // 初始化多个selector选择器
        selectors[0] = Selector.open();
        selectors[1] = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();

        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 18899);
        serverSocketChannel.socket().bind(address);
        // 非阻塞
        serverSocketChannel.configureBlocking(false);

        // 第一个selector, 负责监控新连接事件
        SelectionKey sk = serverSocketChannel.register(selectors[0], SelectionKey.OP_ACCEPT);
        // 将新连接处理器AcceptorHandler附加到SelectionKey(选择键)
        sk.attach(new AcceptorHandler());

        // 第一个子反应器,一子反应器负责一个选择器
        SubReactor subReactor1 = new SubReactor(selectors[0]);
        // 第二个子反应器,一子反应器负责一个选择器
        SubReactor subReactor2 = new SubReactor(selectors[1]);
        subReactors = new SubReactor[]{subReactor1, subReactor2};
    }

    /**
     * @Description:启动两个线程去执行两个Reactor
     * @date 2022/6/12 14:26
    */
    private void startService() {
        // 一子反应器对应一条线程
        new Thread(subReactors[0]).start();
        new Thread(subReactors[1]).start();
    }

    /**
     * @Description:一个SubReactor实例负责一个选择器的轮询
     * @date 2022/6/12 14:26
    */
    class SubReactor implements Runnable {
        // 每条线程负责一个选择器的查询
        final Selector selector;

        public SubReactor(Selector selector) {
            this.selector = selector;
        }

        public void run() {
            try {
                while (!Thread.interrupted()) {
                    selector.select();
                    Set<SelectionKey> keySet = selector.selectedKeys();
                    Iterator<SelectionKey> it = keySet.iterator();
                    while (it.hasNext()) {
                        //Reactor负责dispatch收到的事件
                        SelectionKey sk = it.next();
                        dispatch(sk);
                    }
                    keySet.clear();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }


        void dispatch(SelectionKey sk) {
            Runnable handler = (Runnable) sk.attachment();
            // 调用之前attach绑定到选择键的handler处理器对象
            if (handler != null) {
                // 执行了run方法,由于引进了线程池和多线程,业务处理线程之间会隔离,不会受到阻塞,而新连接处理线程和业务处理线程由不同的SubReactor负责轮询,不会互相干扰
                handler.run();
            }
        }
    }


    /**
     * @Description:新连接处理类
     * @date 2022/6/12 14:27
    */
    class AcceptorHandler implements Runnable {
        public void run() {
            try {
                SocketChannel channel = serverSocketChannel.accept();
                if (channel != null)
                    new MultiThreadEchoHandler(selectors[next.get()], channel);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (next.incrementAndGet() == selectors.length) {
                next.set(0);
            }
        }
    }


    public static void main(String[] args) throws IOException {
        MultiThreadEchoServerReactor server = new MultiThreadEchoServerReactor();
        server.startService();
    }

}
package nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * @author pangjian
 * @ClassName MultiThreadEchoHandler
 * @Description 拆分出来的IOHandle,是一个业务处理线程,引进线程池
 * @date 2022/6/12 14:13
 */

public class MultiThreadEchoHandler {

    final SocketChannel channel;
    final SelectionKey sk;
    final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    static final int RECIEVING = 0, SENDING = 1;
    int state = RECIEVING;
    // 引入线程池
    static ExecutorService pool = Executors.newFixedThreadPool(4);

    MultiThreadEchoHandler(Selector selector, SocketChannel c) throws IOException {
        channel = c;
        c.configureBlocking(false);
        // 仅仅取得选择键,后设置感兴趣的IO事件
        sk = channel.register(selector, 0);
        // 将本Handler作为sk选择键的附件,方便事件dispatch
        sk.attach(this);
        // 向sk选择键注册Read就绪事件
        sk.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }

    public void run() {
        // 异步任务,在独立的线程池中执行
        pool.execute(new AsyncTask());
    }

    // 异步任务,不在Reactor线程中执行
    public synchronized void asyncRun() {
        try {
            if (state == SENDING) {
                // 写入通道
                channel.write(byteBuffer);
                // 写完后,准备开始从通道读,byteBuffer切换成写模式
                byteBuffer.clear();
                // 写完后,注册read就绪事件
                sk.interestOps(SelectionKey.OP_READ);
                // 写完后,进入接收的状态
                state = RECIEVING;
            } else if (state == RECIEVING) {
                // 从通道读
                int length = 0;
                while ((length = channel.read(byteBuffer)) > 0) {
                    System.out.println(new String(byteBuffer.array(), 0, length));
                }
                // 读完后,准备开始写入通道,byteBuffer切换成读模式
                byteBuffer.flip();
                // 读完后,注册write就绪事件
                sk.interestOps(SelectionKey.OP_WRITE);
                // 读完后,进入发送的状态
                state = SENDING;
            }
            // 处理结束了, 这里不能关闭select key,需要重复使用
            // sk.cancel();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    // 异步任务的内部类
    class AsyncTask implements Runnable {
        public void run() {
            MultiThreadEchoHandler.this.asyncRun();
        }
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值