今天我们分享原生 JDK 网络编程- NIO 之 Reactor 模式:
1、“反应”器名字中”反应“的由来:
“反应”即“倒置”,“控制逆转”
,
具体事件处理程序不调用反应器,而向反应器注 册一个事件处理器,表示自己对某些事件感兴趣,有时间来了,具体事件处理程序通过事件
处理器对某个指定的事件发生做出反应;这种控制逆转又称为“好莱坞法则”(不要调用我,让我来调用你)
例如,路人甲去做男士
SPA
,前台的接待小姐接待了路人甲,路人甲现在只对
10000
技 师感兴趣,但是路人甲去的比较早,就告诉接待小姐,等 10000
技师上班了或者是空闲了, 通知我。等路人甲接到接待小姐通知,做出了反应,把 10000
技师占住了。
然后,路人甲想起上一次的那个
10000
号房间不错,设备舒适,灯光暧昧,又告诉前台
的接待小姐,我对
10000
号房间很感兴趣,房间空出来了就告诉我,我现在先和
10000
这 个小姐聊下人生,10000
号房间空出来了,路人甲再次接到接待小姐通知,路人甲再次做出 了反应。路人甲就是具体事件处理程序,前台的接待小姐就是所谓的反应器,“10000
技师上班 了”和“10000
号房间空闲了”就是事件,路人甲只对这两个事件感兴趣,其他,比如
10001 号技师或者 10002
号房间空闲了也是事件,但是路人甲不感兴趣。 前台的接待小姐不仅仅服务路人甲 1
人,他还可以同时服务路人乙、丙
……..
,每个人所 感兴趣的事件是不一样的,前台的接待小姐会根据每个人感兴趣的事件通知对应的每个人。
2、单线程
Reactor
模式流程:
①服务器端的 Reactor 是一个线程对象,该线程会启动事件循环,并使用
Selector(
选 择器)
来实现
IO
的多路复用。注册一个
Acceptor
事件处理器到
Reactor
中,
Acceptor
事件处 理器所关注的事件是 ACCEPT
事件,这样
Reactor
会监听客户端向服务器端发起的连接请求 事件(ACCEPT
事件
)
。
② 客户端向服务器端发起一个连接请求,
Reactor
监听到了该
ACCEPT
事件的发生并将 该 ACCEPT
事件派发给相应的
Acceptor
处理器来进行处理。
Acceptor
处理器通过
accept()
方 法得到与这个客户端对应的连接(SocketChannel)
,然后将该连接所关注的
READ
事件以及对 应的 READ
事件处理器注册到
Reactor
中,这样一来
Reactor
就会监听该连接的
READ
事件了。
③ 当
Reactor
监听到有读或者写事件发生时,将相关的事件派发给对应的处理器进行 处理。比如,读处理器会通过 SocketChannel
的
read()
方法读取数据,此时
read()
操作可以直 接读取到数据,而不会堵塞与等待可读的数据到来。
④ 每当处理完所有就绪的感兴趣的
I/O
事件后,
Reactor
线程会再次执行
select()
阻塞等 待新的事件就绪并将其分派给对应处理器进行处理。
注意,
Reactor
的单线程模式的单线程主要是针对于
I/O
操作而言,也就是所有的
I/O
的 accept()、
read()
、
write()
以及
connect()
操作都在一个线程上完成的。
但在目前的单线程
Reactor
模式中,不仅
I/O
操作在该
Reactor
线程上,连非
I/O
的业务 操作也在该线程上进行处理了,这可能会大大延迟 I/O
请求的响应。所以我们应该将非
I/O 的业务逻辑操作从 Reactor
线程上卸载,以此来加速
Reactor
线程对
I/O
请求的响应。
3、单线程
Reactor
,工作者线程池
与单线程
Reactor
模式不同的是,添加了一个工作者线程池,并将非
I/O
操作从
Reactor 线程中移出转交给工作者线程池来执行。这样能够提高 Reactor
线程的
I/O
响应,不至于因 为一些耗时的业务逻辑而延迟对后面 I/O
请求的处理。 使用线程池的优势:
① 通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊在线程创建和 销毁过程产生的巨大开销。
② 另一个额外的好处是,当请求到达时,工作线程通常已经存在,因此不会由于等待 创建线程而延迟任务的执行,从而提高了响应性。
③ 通过适当调整线程池的大小,可以创建足够多的线程以便使处理器保持忙碌状态。 同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存或失败。 改进的版本中,所以的 I/O
操作依旧由一个
Reactor
来完成,包括
I/O
的
accept()
、read()、 write()以及
connect()
操作。 对于一些小容量应用场景,可以使用单线程模型。但是对于高负载、大并发或大数据量 的应用场景却不合适,主要原因如下:
① 一个
NIO
线程同时处理成百上千的链路,性能上无法支撑,即便
NIO
线程的
CPU
负 荷达到 100%
,也无法满足海量消息的读取和发送;
② 当
NIO
线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时 之后往往会进行重发,这更加重了 NIO
线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈;
![](https://i-blog.csdnimg.cn/blog_migrate/7e4a7fe8f69bbfb034386982e8644d7b.png)
4、多
Reactor
线程模式
Reactor
线程池中的每一
Reactor
线程都会有自己的
Selector
、线程和分发的事件循环逻 辑。mainReactor
可以只有一个,但
subReactor
一般会有多个。
mainReactor
线程主要负责接 收客户端的连接请求,然后将接收到的 SocketChannel
传递给
subReactor
,由
subReactor
来 完成和客户端的通信。
流程:
① 注册一个
Acceptor
事件处理器到
mainReactor
中,
Acceptor
事件处理器所关注的事 件是 ACCEPT
事件,这样
mainReactor
会监听客户端向服务器端发起的连接请求事件
(ACCEPT 事件)
。启动
mainReactor
的事件循环。
② 客户端向服务器端发起一个连接请求,
mainReactor
监听到了该
ACCEPT
事件并将该 ACCEPT 事件派发给
Acceptor
处理器来进行处理。
Acceptor
处理器通过
accept()
方法得到与这 个客户端对应的连接(SocketChannel)
,然后将这个
SocketChannel
传递给
subReactor
线程池。
③
subReactor
线程池分配一个
subReactor
线程给这个
SocketChannel
,即,将 SocketChannel 关注的
READ
事件以及对应的
READ
事件处理器注册到
subReactor
线程中。当 然你也注册 WRITE
事件以及
WRITE
事件处理器到
subReactor
线程中以完成
I/O
写操作。 Reactor 线程池中的每一
Reactor
线程都会有自己的
Selector
、线程和分发的循环逻辑。
④ 当有
I/O
事件就绪时,相关的
subReactor
就将事件派发给响应的处理器处理。注意, 这里 subReactor
线程只负责完成
I/O
的
read()
操作,在读取到数据后将业务逻辑的处理放入 到线程池中完成,若完成业务逻辑后需要返回数据给客户端,则相关的 I/O
的
write
操作还 是会被提交回 subReactor
线程来完成。
注意,所以的
I/O
操作
(
包括,
I/O
的
accept()
、
read()
、
write()
以及
connect()
操作
)
依旧还 是在 Reactor
线程
(mainReactor
线程 或
subReactor
线程
)
中完成的。
Thread Pool(
线程池
)
仅用 来处理非 I/O
操作的逻辑。多 Reactor 线程模式将“接受客户端的连接请求”和“与该客户端的通信”分在了两个
Reactor 线程来完成。
mainReactor
完成接收客户端连接请求的操作,它不负责与客户端的通 信,而是将建立好的连接转交给 subReactor
线程来完成与客户端的通信,这样一来就不会 因为 read()
数据量太大而导致后面的客户端连接请求得不到即时处理的情况。并且多
Reactor 线程模式在海量的客户端并发请求的情况下,还可以通过实现 subReactor
线程池来将海量
的连接分发给多个
subReactor
线程,在多核的操作系统中这能大大提升应用的负载和吞吐 量。
Netty
服务端使用了
“
多
Reactor
线程模式
”
![](https://i-blog.csdnimg.cn/blog_migrate/b2b33a6d8dc8f3bfc10733ad50b3ba11.png)
5、和观察者模式的区别
观察者模式:
也可以称为为 发布
-
订阅 模式,主要适用于多个对象依赖某一个对象的状态并,当某 对象状态发生改变时,要通知其他依赖对象做出更新。是一种一对多的关系。当然,如果依 赖的对象只有一个时,也是一种特殊的一对一关系。通常,观察者模式适用于消息事件处理, 监听者监听到事件时通知事件处理者对事件进行处理(这一点上面有点像是回调,容易与反 应器模式和前摄器模式的回调搞混淆)。
Reactor
模式:
reactor
模式,即反应器模式,是一种高效的异步
IO
模式,特征是回调,当
IO
完成时, 回调对应的函数进行处理。这种模式并非是真正的异步,而是运用了异步的思想,当 IO
事 件触发时,通知应用程序作出 IO
处理。模式本身并不调用系统的异步
IO
函数。 reactor 模式与观察者模式有点像。不过,观察者模式与单个事件源关联,而反应器模
式则与多个事件源关联 。当一个主体发生改变时,所有依属体都得到通知。
原生 JDK 网络编程- NIO 之 Reactor 模式分析完成,下篇我们分析Netty的使用,敬请期待!