Reactor反应器模式:单线程模式与多线程模式介绍与示例 高并发

10 篇文章 0 订阅

文章目录

博文所在专栏里有更多相关内容,如IO模型概述、JAVA NIO等,欢迎阅读与交流。
文字来源于读书笔记及个人心得,可能有引用其他博文,若引用了你的文字请联系我,我会加上来源,或者删除相关内容。

三 Reactor反应器模式

使用了Reactor反应器模式的有:Nginx、Netty、Redis等

Reactor模式是基于Java NIO的,由两个组件组成——Reactor和Handler:

(1)Reactor反应器线程:负责响应IO事件,并将事件分发到相应的Handler。

(2)Handler处理器:将自身(handler)与IO事件绑定,非阻塞地负责事件的处理,完成真正的连接建立、通道读取、业务逻辑处理、结果写出到通道等。

大致流程:

  • 创建反应器:
    • 使用ServerSocketChannel监听新连接
    • 创建选择器
    • 将ServerSocketChannel注册到选择器,并对返回的选择键添加附件,附件为处理器对象
  • 创建处理器:
    • 获取当前通道,注册到选择器,监听你感兴趣的IO事件
    • 将处理器自身作为附件添加到注册时返回的选择键
    • 选择器进行通道读取、业务逻辑处理、通道写入等操作

单线程模式:

reactor反应器分发通道、handler读写通道都在一个线程中处理

在这里插入图片描述
在这里插入图片描述

//单线程Reactor反应器模式示例
//反应器线程
class Reactor implements Runnable {
    final Selector selector;
    final ServerSocketChannel serverSocket;

    //Reactor初始化
    Reactor(int port) throws IOException {
        selector = Selector.open();
        serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(port));
        serverSocket.configureBlocking(false);

        //第一步,接收accept事件,监听新连接
        SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT);
        //SelectionKey的attach(Object o)方法可以将任何java对象作为附件添加到SelectionKey实例中,在单线程Reactor中用于添加处理器实例,此处添加了一个带有处理器实例的线程对象
        sk.attach(new Runnable() {
            public void run() {
                try {
                    SocketChannel channel = serverSocket.accept();
                    if (channel != null)
                        new Handler(selector, channel);
                } catch (IOException ex) { /* ... */ }
            }
        });
    }

    public void run() {
        try {
            while (!Thread.interrupted()) {
                //获取就绪选择器
                selector.select();
                Set selected = selector.selectedKeys();
                Iterator it = selected.iterator();
                while (it.hasNext()) {
                    //分发收到的事件(Reactor主要任务)
                    SelectionKey k=(SelectionKey) (it.next());
                    //SelectionKey的attachment()可以获取attach(Object o)所添加的附件(java对象)
                    Runnable r = (Runnable) (k.attachment());
                    //调用之前注册的callback对象
                    if (r != null) {
                        r.run();
                    }
                }
                selected.clear();
            }
        } catch (IOException ex) { /* ... */ }
    }
}
//处理器线程
class Handler implements Runnable {
    final SocketChannel channel;
    final SelectionKey sk;
    ByteBuffer input = ByteBuffer.allocate(1024);
    ByteBuffer output = ByteBuffer.allocate(1024);
    static final int READING = 0, SENDING = 1;
    int state = READING;

    Handler(Selector selector, SocketChannel c) throws IOException {
        channel = c;
        c.configureBlocking(false);
        //注册到选择器,监听读就绪事件
        sk = channel.register(selector, SelectionKey.OP_READ);
        //将处理器自身作为附件
        sk.attach(this);
        //            //第二步,注册Read就绪事件
        //            sk.interestOps(SelectionKey.OP_READ);
        //唤醒可能存在的select()带来的阻塞状态
        selector.wakeup();
    }

    boolean inputIsComplete() {
        /* ... */
        return false;
    }

    boolean outputIsComplete() {

        /* ... */
        return false;
    }

    void process() {
        /* ... */
        return;
    }

    //业务逻辑处理
    public void run() {
        try {
            if (state == READING) {
                read();
            } else if (state == SENDING) {
                send();
            }
        } catch (IOException ex) { /* ... */ }
    }

    void read() throws IOException {
        channel.read(input);
        if (inputIsComplete()) {

            process();

            state = SENDING;
            // Normally also do first write now

            //第三步,接收write就绪事件
            sk.interestOps(SelectionKey.OP_WRITE);
        }
    }

    void send() throws IOException {
        channel.write(output);

        //write完就结束了, 关闭select key
        //这里不能关闭,因为该选择器在反应器线程中还有用
        if (outputIsComplete()) {
            sk.cancel();
        }
    }
}

多线程模式

reactor反应器一个线程,handler处理器用线程池管理

在这里插入图片描述

在这里插入图片描述

示例与单线程类似,区别在于处理器中对通道数据的处理放在了线程池中进行:

class MthreadHandler implements Runnable {
    
   /* ... */

    synchronized void read() throws IOException {
        // ...
        channel.read(input);
        if (inputIsComplete()) {
            state = PROCESSING;
            //使用线程pool异步执行
            pool.execute(new Processer());
        }
    }

    class Processer implements Runnable {
        public void run() {
            processAndHandOff();
        }
    }

    synchronized void processAndHandOff() {
        process();
        state = SENDING;
        // or rebind attachment
        //process完,开始等待write就绪
        selectionKey.interestOps(SelectionKey.OP_WRITE);
    }

}

优点

1)响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;

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

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

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

缺点

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

2)Reactor模式需要底层的Synchronous Event Demultiplexer支持,比如Java中的Selector支持,操作系统的select系统调用支持,如果要自己实现Synchronous Event Demultiplexer可能不会有那么高效。

3) Reactor模式在IO读写数据时还是在同一个线程中实现的,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间,比如在大文件传输时,IO操作就会影响其他Client的相应时间,因而对这种操作,使用传统的Thread-Per-Connection或许是一个更好的选择,或则此时使用改进版的Reactor模式如Proactor模式。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值