设计模式------反应器模式(Reactor Pattern)

目录

1. 简介

2. 为什么要使用该模式 

3. Reactor 的结构

4. Reactor 模式的实现

4.1. 单 Reactor 线程模式

4.2. 单 Reactor 线程模式 + 线程池

4.3 多 Reactor 线程模式

5. 参考


1. 简介

Reactor模式(反应器模式)是一种处理一个或多个客户端并发交付服务请求的事件设计模式。

Reactor 模式被广泛应用在设计高性能IO方面上,大多数IO相关组件如 Netty、Redis 就都在使用的该设计模式。

2. 为什么要使用该模式 

在以前传统的Java IO网络编程方式就是在服务器调用一个 while 循环,然后在其中调用 serverSocket 的 accept 阻塞方法来不断监听端口是否有新的 Scoket 连接,这是一种 BIO 的模式

Server端代码如下:

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Created by vMars on 2019/9/15.
 */
public class Server {
    public static void main(String[] args) throws IOException {

        //监听 8000 端口
        ServerSocket serverSocket = new ServerSocket(8000);

        // 新建一个线程,防止 accept() 阻塞主线程
        new Thread(() -> {
            while (true) {
                try {
                    // 阻塞方法获取新的连接
                    Socket socket = serverSocket.accept();

                    // 每一个新的连接都创建一个线程,负责读取数据
                    new Thread(() -> {
                        try {
                            //获得输入流
                            InputStream inputStream = socket.getInputStream();
                            int len;
                            byte[] data = new byte[1024];
                            
                            // 按字节流方式读取数据
                            while ((len = inputStream.read(data)) != -1) {
                                String message = new String(data, 0, len);
                                System.out.println("获得客户端传来消息:" + message);
                            }

                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }).start();

                } catch (IOException e) {
                    System.out.println("服务端启动失败");
                    e.printStackTrace();
                }
            }
        }).start();

    }
}

在这种IO方式中通过 while循环来监听端口是否有新的连接,建立了连接之后我们会新建一个线程来处理IO,所以这个时候我们是一个 Scoket连接对应一个线程

我们可以可以把它类比成餐厅(服务器)中顾客(客户端的Scoket连接)与服务员(线程)的关系:

(图来自 https://lotabout.me/2018/reactor-pattern/,很生动的解释了 为什么使用Reactor)

但是线程是很宝贵的资源,如果同时有大量的 Scoket 连接就会创建大量的线程,且线程的销毁和创建也是需要代价的,这个时候可能会想到可以用线程池解决。

但线程池虽然能让线程重复利用,避免了重复创建销毁线程,但是这并没有解决线程粒度太大的问题,每个线程都需要各自完成交互的全过程(如读取内容,解码,逻辑处理,编码,内容回复等),所以线程池并不是最好的解决方案。

所以我们可以考虑降低线程的粒度,我们将过程的操作细分为多个子过程 handler,每一个 handler 处理一种 event,然后通过一个 selector 来管理,连接 channel 进来后为其注册感兴趣的事件 event,当 channel 触发某个事件后 selector 可以监听到然后再进一步处理。

这样我们就可以通过事件驱动降低线程的粒度了,这就是 Reactor模式。

3. Reactor 的结构

(图来自 https://www.cnblogs.com/winner-0715/p/8733787.html

具体详细的分析可以看上面的链接,这里记录我理解的大致流程:

1. 首先我们会在 Initiation Dispatcher(即是 Reactor 模式的核心处理器) 注册我们的 Concrete Event Handler(即是我们自己编写的具体处理器),在注册的时候我们会指定感兴趣的事件 Event,如读事件READ、新连接事件ACCEPT等事件。

2. 然后会使用 Synchronous Event Demultiplexer 同步阻塞的等待一个或多个事件的发生,这里的 Synchronous Event Demultiplexer 就是使用了IO多路复用的机制,如 Java NIO中提供的 selector 。

3. 当我们所监听的 Handler 变为 ready 时,Synchronous Event Demultiplexer 会通知 Initiation Dispatcher,然后 Initiation Dispatcher 会根据这个 Handler 去分发恰当的事件处理器 Concrete Event Handler

4. Reactor 模式的实现

4.1. 单 Reactor 线程模式

在该种模式下我们使用的是一个单线程的 Reactor,acceptor() 处理器注册了 ACCEPT 事件,即连接事件,当有连接请求时 Reactor 会将其分发给 acceptor() 处理。

但在这种模式下,Reactor 线程不但要处理 accept()、read()、send(),连非IO业务也要处理,如果业务逻辑复杂,这可能会使 Reactor 线程无法处理其它事件的响应。

为了避免这种事情发生,我们需要把非IO业务逻辑处理交给子线程

4.2. 单 Reactor 线程模式 + 线程池

通过加入工作线程池,把具体的逻辑操作交由子线程,提高了 Reactor 线程的IO响应时间,但是这样的模式还是存在着缺陷。

就是Reactor 线程要处理包括I/O的accept()、read()、write()以及connect()操作,当同时有大量的连接建立时单线程的 Reactor性能会下降,然后可能会使大量客户端连接超时,最终使大量消息积压和连接超时。

为了解决这个问题我们采用多 Reactor 模式。

4.3 多 Reactor 模式

在多Reactor 模式中,我们分为 mainReactor 和 subReactor,每一个 Reactor 都会有自己的 Selector 与不同的事件循环逻辑。

其中 mainReactor 主要负责接受客户的连接请求,然后将建立的 ScoketChannel 传递给 subReactor,由 subReactor 完成和客户端的通信

subReactor 一般会有多个,这可以很好的解决单 Reactor 线程模式下的瓶颈。

5. 参考

Scalable IO in Java

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值