简介:Reactor 设计模式是一种事件驱动的设计模式,将一个或者多个客户端请求分发到不同的处理器上,来提升事件处理的效率。主要的应用场景就是java NIO当中用户处理网络请求。使用的是异步非阻塞IO

在接受Reactor 模式之前,需要先了解常见的几种IO网络模型。

1、BIO(阻塞IO模型)

以套接字模型为例:在进程空间中调用recvfrom, 其系统调用直到数据包到达且被复制到应用进程的缓冲区中或者发生错误时才可以返回,在此期间会一直等待,进程在从调用recvfrom 开始到它返回的整段事件内都是阻塞的,(举个栗子:我去餐馆吃饭,我先点菜,点完菜之后,我就坐在一旁等待,直到菜做好之后,我再开始吃,在师傅做菜的过程中,我没有做任何事情一直在等待)

2、非阻塞IO模型

recvfrom 从应用层到内核的时候,如果缓冲区没有数据,会直接返回一个信息,然后过段时间之后,再去进行轮询检测这个状态,看是不是有数据到来,

3、I/O复用模型

进程通过将一个或者多个操作阻塞在select上,select可以帮我们检测fd(文件操作符)是否处于就绪状态,如果有数据准备好,就返回可操作的信息,再进行进一步的读写操作

4、异步I/O

提前告知内核启动某个操作,并让内核在整个操作完成后(将数据从内核复制到用户自己的缓冲区)通知我们,

I/O多路复用技术

I/O多路复用技术通过把多个I/O的阻塞复用到同一个select的阻塞上,从而是系统在单线程的情况下可以同时处理多个客户端请求。最大的优势就是系统开销小,不需要创建额外的线程,服务器需要同时处理多个处于监听状态或者多个连接状态的套接字,

要了解IO多路复用,这个是NIO(None-blocking IO)的基础

我们最开始传统(BIO)的处理方式:

每个客户端发送一个请求,服务器就创建一个线程,用来处理上面的操作步骤,但是当请求的数据量达到一定情况之后,线程的开销也是很大的,虽然这种情况可以实现非阻塞的IO请求,但是弊端也是非常明显的

有以下缺点:

随着线程数增加,极大的降低了数据的吞吐量
线程之间进行上下文切换的开销也很大
每个客户端和服务器的连接建立好之后,数据并不是一直都存在,服务器端的线程还处于存活状态,但是一直空闲,这就造成了资源的极大浪费。
因此,单线程的reactor模式就产生了

上面这个流程操作就是:

所有的IO操作都在一个NIO线程上工作,

客户端发起连接之后,acceptor类接受客户端的请求,链路建立成功之后,通过dispatcher把信息发送到指定的handler上面进行处理, 用户线程编码后,通过NIO线程将信息发送给客户端

Acceptor 会在服务器启动之间将相关的事件注册相应的组件上面,用于处理连接

Reactor角色是一个线程,用来检测用户发送来的连接,当连接建立之后,将数据派发给特定的处理器,也就是途中黄色部分。

这里的Reactor 通过调用适当的处理器来响应具体的IO事件

handler用来处理非阻塞的动作,通过绑定处理程序来管理事件。

这样做,不会为每个客户端都创建一个线程,我们只有一个线程来处理客户端的连接,连接建立好之后,就会把相关的操作交给handler 处理,

这样的问题就是:

1、如果handler的处理时间过长,是会拖慢reactor整个线程的执行速度,并不能很好的响应客户端的请求

2、当请求的数量很大的时候,性能上根本无法支撑,cpu处理达到100%,并不能加快响应速度,反而会降低

3、连接数量过多,会导致连接超时,进一步重新发起连接,最终会导致大量的信息积压。

4、一旦线程处于死循环,会导致整个系统无法使用,不能处理外部请求和返回响应信息,导致系统故障

多线程模型:

多线程模型,主要是有一组NIO线程来处理IO操作,

多线程的操作流程是:

有一个专门的NIO线程来监听服务端,接收客户的TCP连接

网络IO操作有一个专门的线程池去处理,

对于信息的读取及相关的操作都是在一个线程中执行,这样避免了并发操作问题

缺点:

一个线程负责监听处理客户端连接可能会造成性能问题,有些业务场景下对于连接可能会比较耗时,比如安全认证之类的

主从Reactor模型:

这种主从reactor的处理方式是:

服务端用来接受客户端连接请求的不再是一个NIO线程,而且是一个NIO线程池;

mainReactor是由NIO线程池组成的,主要是用来监听客户端连接,连接建立之后,会将创建的SocketChannel注册到subReactor(也是一个NIO线程池)上,在这个线程上负责编码,解码,数据处理等一系列操作,

同时,mainReactor 也使用线程池或者多个线程(少于客户端连接数量的线程数),用来处理IO请求,将读取到的数据,转交给subReactor, 在subReactor中通过线程池来处理这些非IO的操作。

其实这个模式在日志服务器模型中也是有使用到的

上图是客户端发送请求到服务器,我们来

看下处理过程

服务器端,会先将处理器注册到初始分发器(Initiation Dispatcher)上面
服务器端调用事件处理方法(handle_events),然后进行启动
服务器端一直阻塞在select方法上面
客户端发送请求到服务器端,select方法就会返回
handle_event方法通知初始分发器
acceptor 接受到客户端的请求
acceptor 创建一个handler 来处理客户端的请求
将handler注册到初始分发器上面
以上只是在客户端向服务器建立连接时的操作步骤,接下来是连接建立好之后,数据处理流程:

客户端发送数据到服务器,
服务器的初始分发器检测到事件发生,根据返回的handle(句柄),
遍历所有的处理器,找到与这个事件关联的处理器,
然后处理器对数据进行处理,
最后将处理好的数据返回给客户端。
在reactor 模式中一共有以下五个角色

Handle: 表示的是一个句柄或者描述符,本质上是一种资源,由操作系统提供,该资源用于表示一个个的事件,比如文件描述符,在网络服务情况下,就是网络编程中的socket 描述符。这个事件既可以来自内部也可以来自外部,外部主要是指客户端的连接请求,客户端发送过来的数据等;内部的话,指的是操作系统产生的定时任务等。本质上是一个文件描述符

Synchronous Event Demultiplexer(同步事件分离器):用于等待事件的发生,调用方在调用的时候会发生阻塞,一直阻塞到同步事件分离器上游事件发生为止。对于linux系统来说,同步事件分离器指的就是I/O多路复用机制,比如select, poll, epoll 等,映射到java NIO 上就是selector 组件。

Event Handler(事件处理器):这个角色就是用多个回调方法组成的,这些回调方法构成了与应用相关的某个事件的反馈,netty 当中提供了大量的事件处理器,比如InboundHanler, 事件对应的回调就是,注册通道,取消通道,主要是用于在特定事件产生时进行逻辑处理。

Concrete Event Handler (具体的事件处理器):这个处理器主要是开发者自己去编写的,是事件处理器的具体实现,和业务逻辑相关,

Initiation Dispatcher(初始分发器):主要作用是添加EventHandler, 删除 EventHandler, 以及派发事件到 EventHandler, 通过同步分离器等待事件的发生,一旦发生之后,出事分发器就会分离出一个事件,然后调用事件处理器,最后调用该处理器的相关方法。

整个模型的执行流程是:

应用回向Initiation Dispatcher 注册具体的事件处理器,应用会标识出,当某个事件发生时,通知该处理器,这个事件是与handle关联的
Initiation Dispatcher 会要求每个事件处理器向其传递内部的handle,
当所有的处理器都注册结束之后,应用会调用handle_event 来启动 Initiation Dispatcher的事件循环。这时Initiation Dispatcher 会将每个注册的事件管理器的handle合并起来,并使用同步分离器等待事件的发生。比如说,TCP协议层会使用select 同步事件分离器操作来等待客户端发送的数据到达连接的socket handle 上
当与某个事件源对应的handle变为ready状态时,比如说,TCP socket变为等待读状态时,同步事件分离器就会通知Initiation Dispatcher。
Initiation Dispatcher 就会触发事件处理器的回调方法,从而来响应这个已经处于ready状态的事件、
Initiation Dispatcher回调处理器 handle_event 的具体方法,来执行特定的业务功能。
netty 当中的线程模型

对于以上的线程模型,netty都可以通过配置相应的参数来实现,

netty中的reactor模型的使用比较类似主从reactor模型,在服务端的启动代码,会创建一个bossGroup,workerGroup

bossGroupl类似于mainReactor

workerGroup 类似于 subReactor

ServerBootstrapAcceptor 对应acceptor

concrete Event Hanler 就是用户自己编写的处理器

浅析Reactor设计模式的更多相关文章

  1. 浅析JAVA设计模式之工厂模式(一)

    1 工厂模式简单介绍 工厂模式的定义:简单地说,用来实例化对象,取代new操作. 工厂模式专门负责将大量有共同接口的类实例化.工作模式能够动态决定将哪一个类实例化.不用先知道每次要实例化哪一个类. 工 ...

  2. 浅析JAVA设计模式之工厂模式(二)

    1 工厂方法模式简单介绍 工厂方法 (Factroy Method)模式:又称多态性工厂模式(Polymorphic Factory),在这样的模式中,核心工厂不再是一个详细的类.而是一个抽象工厂,提 ...

  3. Java NIO 与 基于reactor设计模式的事件处理模型

    Java NIO非堵塞应用通常适用用在I/O读写等方面,我们知道,系统运行的性能瓶颈通常在I/O读写,包括对端口和文件的操作上,过去,在打开一个I/O通道后,read()将一直等待在端口一边读取字节内 ...

  4. SimpleRpc-网络事件响应Reactor设计模式

    前言 这篇文章主要介绍整个框架用到的最核的一个设计模式:反应器模式.这个设计模式可以在<面向对象的软件架构>中详细了解,没有这本书的小伙伴不要急,我通过咱们的SimpleRpc来告诉大家这 ...

  5. reactor设计模式

    reactor介绍 reactor的工作模式就像它的名字一样,是一种反射模式,当事件发生时,根据发生的事件调用注册的处理器. Reactor的优点和应用 Reactor最常用于非阻塞的socket 传 ...

  6. 浅析JAVA设计模式(一)

    第一写技术博客,只是想把自己一天天积累的东西与大家分享.今天在看<大型网站架构和java中间件>这本书时,其中提到代理模式的动态代理.作为java中间件的一个重要基础,我觉的有必要整理和分 ...

  7. 浅析JAVA设计模式(三)

    4.接口隔离原则: ISP(Interface Segregation Principle)  客户端不应该依赖它不需要的接口,或者说类的依赖的关系应该建立在最小的接口上.举个例子,直接上代码:  1 ...

  8. [转] 浅析JavaScript设计模式——发布-订阅/观察者模式

    前一段时间一直在写CSS3的文章 一直都没写设计模式 今天来写写大名鼎鼎观察者模式 先画张图 观察者模式的理解 我觉得还是发布-订阅模式的叫法更容易我们理解 (不过也有的书上认为它们是两种模式……)  ...

  9. 浅析JAVA设计模式(二)

    2. 里氏替换原则:LSP(Liskov Substitution Principle)里氏替换原则,定义为只要父类出现的地方子类就可以出现,而且用子类替换后,程序也不会出现问题,使用者根本不用关心是 ...

随机推荐

  1. WPF自定义控件(三)

    今天我们开始制作我们的按钮,主要的效果就是一个按钮正常状态.鼠标滑过.按下三态显示不同的图片. 首先我们需要给扩展按钮添加三个属性,分别是正常状态图片,鼠标滑过图片,按钮按下图片. 先贴出Button ...

  2. mvc 当中 [ValidateAntiForgeryToken] 的作用 转载https://www.cnblogs.com/hechunming/p/4647646.html

    一.CSRF是什么? CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSR ...

  3. 抓包工具charles下载安装(MAC版)

    什么是charles? charles是一个HTTP代理服务器,HTTP监视器,反转代理服务器,当浏览器连接Charles的代理访问互联网时,Charles可以监控浏览器发送和接收的所有数据.它允许一 ...

  4. The life-saving straw

    English learning   In contemporary world, English learning has gained great popularity and it is of ...

  5. HDU 1205 吃糖果 (鸽巢原理)

    题目链接:HDU 1205 Problem Description HOHO,终于从Speakless手上赢走了所有的糖果,是Gardon吃糖果时有个特殊的癖好,就是不喜欢将一样的糖果放在一起吃,喜欢 ...

  6. anaconda 安装2个python环境 亲测

    本机环境: anaconda3,pyhon3.7.4 配置第2个python环境,安装python3.6 > conda create --name tensorflow python=3.6 ...

  7. BPT(Business Process Testing)

  8. js日历算法

    页面 <div class="un1"> <h2>服务档期</h2> <div class="date-panel" ...

  9. go 学习之函数

    个人把go函数理解分三种: 1.普通函数 普通函数声明: func name(parameter-list) (result-list) { body} package main import &qu ...

  10. vue证明题四,使用组件

    vue的开发方式,基本上是以组件为主的,至于为啥,我也不好去论述,网上看别人的 所谓渐进式开发,也是源自于单页面应用这一说,而注册一个域名以后,指定了首页,爬虫爬取链接都是从首页开始的 如果一个网址, ...