Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【Netty4.x教程】Reactor 模式 #3

Open
TFdream opened this issue Apr 10, 2020 · 0 comments
Open

【Netty4.x教程】Reactor 模式 #3

TFdream opened this issue Apr 10, 2020 · 0 comments

Comments

@TFdream
Copy link
Owner

TFdream commented Apr 10, 2020

基本上所有的网络处理程序都有以下基本的处理过程:
Read request
Decode request
Process service
Encode reply
Send reply

典型的Thread-Per-Connection 模式

image

对于每一个请求都分发给一个线程,每个线程中都独自处理上面的流程。
这种模型由于IO在阻塞时会一直等待,因此在用户负载增加时,性能下降的非常快。

server导致阻塞的原因:

  1. ServerSocket的accept方法,阻塞等待client连接,直到client连接成功。
  2. 线程从socket inputstream读入数据,会进入阻塞状态,直到全部数据读完。
  3. 线程向socket outputstream写入数据,会阻塞直到全部数据写完。

什么是 Reactor 模式?

维基定位如下:

The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.

从这个描述中,我们知道Reactor模式首先是事件驱动的,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handler。

从结构上,这有点类似生产者消费者模式,即有一个或多个生产者将事件放入一个Queue中,而一个或多个消费者主动的从这个Queue中Poll事件来处理;而Reactor模式则并没有Queue来做缓冲,每当一个Event输入到Service Handler之后,该Service Handler会主动的根据不同的Event类型将其分发给对应的Request Handler来处理。

为什么会有 Reactor 呢?

对于应用程序而言,CPU 的处理速度是远远快于 IO 的速度的。如果CPU为了IO操作(例如从Socket读取一段数据)而阻塞显然是不划算的。好一点的方法是分为多进程或者线程去进行处理,但是这样会带来一些进程切换的开销,试想一个进程一个数据读了500ms,期间进程切换到它3次,但是CPU却什么都不能干,就这么切换走了,是不是也不划算?
这时先驱们找到了事件驱动,或者叫回调的方式,来完成这件事情。这种方式就是,应用业务向一个中间人注册一个回调(event handler),当IO就绪后,就这个中间人产生一个事件,并通知此handler进行处理。这种回调的方式,也体现了“好莱坞原则”(Hollywood principle)-“Don’t call us, we’ll call you”,在我们熟悉的IoC中也有用到。看来软件开发真是互通的!

Reactor 应用场景

Reactor 核心是解决多请求问题。一般来说,Thread-Per-Connection 的应用场景并发量不是特别大,如果并发量过大,会导致线程资源瞬间耗尽,导致服务陷入阻塞,这个时候就需要 Reactor 模式来解决这个问题。Reactor 通过多路复用的思想大大减少线程资源的使用。

Reactor 三种模式

单线程版本

image

简单来说,接收请求和处理请求是同一线程中处理。

  • Reactor:负责响应IO事件,当检测到一个新的事件,将其发送给相应的Handler去处理。
  • Handler:负责处理非阻塞的行为,标识系统管理的资源;同时将handler与事件绑定。

Reactor为单个线程,需要处理accept连接,同时发送请求到处理器中。由于只有单个线程,所以处理器中的业务需要能够快速处理完。

对于一些小容量应用场景,可以使用单线程模型。但是对于高负载、大并发或大数据量的应用场景却不合适,主要原因如下:

  • 一个NIO线程同时处理成百上千的链路,性能上无法支撑,即便NIO线程的CPU负荷达到100%,也无法满足海量消息的读取和发送;
  • 当NIO线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了NIO线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈;

多线程版本

image

将处理器的执行放入线程池,多线程进行业务处理。但Reactor仍为单个线程。

继续改进:对于多个CPU的机器,为充分利用系统资源,将Reactor拆分为两部分。

主从多线程

image

简单来说,接收请求和处理请求是不同线程中处理。

mainReactor负责监听连接,accept连接给subReactor处理(为什么要单独分一个Reactor来处理监听呢?因为像TCP这样需要经过3次握手才能建立连接,这个建立连接的过程也是要耗时间和资源的,单独分一个Reactor来处理,可以提高性能)。mainReactor 一般只有一个,主要负责接收客户端的连接并将其传递给 subReactor。
subReactor 一般会有多个,主要负责处理与客户端的通信。

注意:上图使用了Thread Pool来处理耗时的业务逻辑,提高Reactor线程的I/O响应,不至于因为一些耗时的业务逻辑而延迟对后面I/O请求的处理。

Reactor 的优缺点

优点:

  • 大多数设计模式的共性:解耦、提升复用性、模块化、可移植性、事件驱动、细力度的并发控制等。
  • 更为显著的是对性能的提升,即不需要每个 Client 对应一个线程,减少线程的使用。

缺点:

  • 相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛,并且不易于调试。
  • Reactor模式需要底层的Synchronous Event Demultiplexer支持,比如Java中的Selector支持,操作系统的select系统调用支持,如果要自己实现Synchronous Event Demultiplexer可能不会有那么高效。
  • Reactor模式在IO读写数据时还是在同一个线程中实现的,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间,比如在大文件传输时,IO操作就会影响其他Client的相应时间,因而对这种操作,使用传统的Thread-Per-Connection或许是一个更好的选择,或则此时使用Proactor模式。

相关资料

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant