Reactor设计模式

一. 为什么需要

解决多请求问题,但是这些请求不需要一直占有整个线程资源(比如IO操作时不必一直等待),所以不适合使用一个请求分配一个线程的多线程方案;类似于消息队列模型,但是是事件驱动,没有Queue来做缓冲;优点:解耦、高效、提高复用,缺点:需要操作系统底层支持、内部回调复杂。

二. 预备知识

IO操作主要分成两部分:

  1. 数据准备,将数据从磁盘加载到内核缓存
  2. 将数据从内核缓存加载到用户缓存

2.1 IO的4种模型

  • 阻塞、非阻塞(等待数据全部读取成功再返回,还是读取为空马上返回然后下次再读)
  • 同步、异步(用户缓存主动去读取内核缓存,还是内核缓存读取磁盘成功后通知用户缓存)
  • NIO是同步非阻塞模型,也是IO多路复用基础
  • Reactor模式基于同步I/O,Proactor模式基于异步I/O

2.2 IO多路复用

区别于传统的多进程并发模型 (每有新的IO流就分配一个新的进程管理),IO多路复用仅使用单个线程,通过记录跟踪每个I/O流的状态来同时管理多个I/O流(哪个IO流ready线程就处理哪个)

select, poll, epoll 都是I/O多路复用的具体的实现:
select:仅返回有无事件不返回具体事件Id,只能监控1024个连接,线程不安全
poll:连接数无限制
epoll:返回具体事件Id,线程安全

三. 反应器模式

处理一个或多个客户端并发请求服务的事件设计模式。当请求抵达后,服务处理程序使用I/O多路复用策略,然后同步地派发这些请求至相关的请求处理程序。

Reactor_Structures.png

3.1 模块组成

包括5个模块:

  • Handle:事件(网络编程中就是一个Socket,数据库操作中就是一个DBConnection,Java NIO中的Channel)
  • EventHandler:事件处理器,用于处理不同状态的事件
  • Concrete Event Handler:事件处理器的具体实现,实现了事件处理器所提供的各种回调方法,从而实现特定于业务的逻辑
  • Synchronous Event Demultiplexer:用于等待事件的发生,调用方在调用它的时候会被阻塞,一直阻塞到同步事件分离器上有事件产生为止(NIO中对应Selector,当Selector.select()返回时说明有事件发生,然后调用Selector的selectedKeys()方法获取Set<SelectionKey>,一个SelectionKey表示一个有事件发生的Channel以及该Channel上的事件类型)
  • Initiation Dispatcher:用于管理EventHandler、分发event。通过Synchronous Event Demultiplexer来等待事件的发生,一旦事件发生,Initiation Dispatcher首先会分离出每一个事件,然后调用事件处理器,最后调用相关的回调方法来处理这些事件

3.2 运行流程

  1. 初始化dispatcher,注册具体事件处理器到分发器(即指定什么事件触发什么事件处理器)
  2. 注册完毕后,分发器调用handle_events方法启动事件循环,并启动Synchronous Event Demultiplexer等待事件发生(阻塞等待)
  3. 当有事件发生,即某个Handle变为ready状态(如TCP socket变为等待读状态),Synchronous Event Demultiplexer就会通知Initiation Dispatcher
  4. Initiation Dispatcher根据发生的事件,将被事件源激活的Handle作为『key』来寻找并分发恰当的事件处理器回调方法

3.3 具体模型分类

  • 单线程模型(I/O、非I/O业务操作都在一个线程上处理,可能会大大延迟I/O请求的响应)
  • 工作站线程池模型(非I/O操作从Reactor线程中移出转交给工作者线程池执行)
  • 多线程模型(mainReactor线程主要负责接收客户端的连接请求,然后将接收到的SocketChannel传递给subReactor,由subReactor来完成和客户端的通信),但是注意subReactor线程只负责完成I/O的read()或者write()操作,在读取到数据后业务逻辑的处理仍然放入到工作者线程池中完成,可避免因为read()数据量太大而导致后面的客户端连接请求得不到即时处理的情况
singleReactor.png
workerThreadPool.png
multipleReactors.png

四. 源码分析

高性能NIO框架netty、腾讯开源RPC框架Tars的NIO模型都是很典型的Reactor设计模式,下面以Tars源码来分析Reactor模式的java NIO实现(仅展示关键实现)。

package com.qq.tars.net.core.nio;
tarsNIO.PNG

4.1 Reactor

import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SelectableChannel;

public final class Reactor extends Thread {

    protected volatile Selector selector = null;
    private Acceptor acceptor = null;

    //启动
    public Reactor(SelectorManager selectorManager) throws IOException {
        this.acceptor = new TCPAcceptor(selectorManager);
        this.selector = Selector.open();
    }

    //注册
    public void registerChannel(SelectableChannel channel, int ops, Object attachment) throws IOException {

        SelectionKey key = channel.register(this.selector, ops, attachment);
    }

    //循环事件
    public void run() {

            for (;;) {
                selector.select();
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while (iter.hasNext()) {
                    SelectionKey key = iter.next();
                    dispatchEvent(key);
                }
            }
        
    }

    //处理事件
    private void dispatchEvent(final SelectionKey key) throws IOException {
        if (key.isConnectable()) {
            acceptor.handleConnectEvent(key);
        } else if (key.isAcceptable()) {
            acceptor.handleAcceptEvent(key);
        } else if (key.isReadable()) {
            acceptor.handleReadEvent(key);
        } else if (key.isValid() && key.isWritable()) {
            acceptor.handleWriteEvent(key);
        }
    }

}

4.2 TCPAcceptor
处理不同事件,以处理connect、read事件为例:

    public void handleConnectEvent(SelectionKey key) throws IOException {
        //1. Get the client channel
        SocketChannel client = (SocketChannel) key.channel();

        //2. Set the session status
        TCPSession session = (TCPSession) key.attachment();
        if (session == null) throw new RuntimeException("The session is null when connecting to ...");

        //3. Connect to server
        try {
            client.finishConnect();
            key.interestOps(SelectionKey.OP_READ);
            session.setStatus(SessionStatus.CLIENT_CONNECTED);
        } finally {
            session.finishConnect();
        }
    }

    public void handleReadEvent(SelectionKey key) throws IOException {
        TCPSession session = (TCPSession) key.attachment();
        if (session == null) throw new RuntimeException("The session is null when reading data...");
        session.read();
    }

4.3 TCPSession
以read事件的readResponse方法为例:

//放入工作线程池
response = selectorManager.getProtocolFactory().getDecoder().decodeResponse(tempBuffer, this);
selectorManager.getThreadPool().execute(new WorkThread(response, selectorManager));

4.4 工作线程池
SelectorManager提供线程池,WorkThread具体进行业务处理

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容