技术干货 | Netty之Reactor模式

本文介绍了Reactor模式,一种广泛应用于服务器端开发的设计模式,基于事件驱动,能有效处理CPU与IO速度差异问题。文章分析了Reactor模式在Netty及其他网络应用服务器中的应用,探讨了其在并发日志系统中的实现,并讨论了多线程环境下Reactor模式的优势,包括资源占用减少、成本降低。最后,文章提到了Reactor模式的使用场景及其优缺点。
摘要由CSDN通过智能技术生成

01
什么是Reactor模式

Reactor是一种广泛应用在服务器端开发的设计模式,是一种基于事件驱动的设计模式,所谓的事件驱动通俗点说就是回调的方式。我们知道,对于应用服务器,一个主要规律就是,CPU的处理速度是要远远快于IO速度的,如果CPU为了IO操作(例如从Socket读取一段数据)而阻塞显然是不划算的。好一点的方法是分为多进程或者线程去进行处理,但是这样会带来一些进程切换的开销,试想一个进程一个数据读了500ms,期间进程切换到它3次,但是CPU却什么都不能干,就这么切换走了,是不是也不划算,这时先驱们找到了事件驱动,或者叫回调的方式,来完成这件事情。这种方式就是,应用业务向一个中间人注册一个回调(event handler),当IO就绪后,就这个中间人产生一个事件,并通知此handler进行处理。

02

了解Reactor模式

了解Reactor模式有助于我们理解Netty设计模式,以及它们的源码分析,而且不单单Netty采用此设计模式,其他的网络应用服务器开发也应用了此模式。下面我们根据dre.vanderbilt.edu/~sch 文章分析一下Reactor模式。

对于一个事件驱动的分布式日志登录服务系统,如下图1所示:


客户端应用通过日志服务来录入它们当前状态和记录,这些状态可记录可能包含了错误通知信息、断点调试信息等。日志记录被发送到一个中央日服务器上,该服务器可以处理日志和连接用户请求。客户端想要记录日志信息,首先必须发送一个连接请求给服务器。服务器通过一个“处理工厂”来监听客户端对应的地址信息,以等待这些连接请求的到来。当一个连接请求到来时,“处理工厂”就创建一个handle,其代表了连接的端点,用来建立客户端和服务器之间的连接。当handle收到来自客户端的请求连接时,就会返回给服务器。一旦客户端连接成功,它们就可以同时发送日志记录到服务器。

或许最有效的方法来开发一个并发日志系统是使用多线程,这样可以同时处多个理客户端请求,如下图2所示。


然而,多线程实现这样的分布式日志系统可能会面临下面的问题:

  • 可用性:服务器必须能够处理传入请求即使是等待其他请求到达的。特别是,一个服务器不能无限期地处理任何单一来源的事件而排斥其他事件源。因为这可能大大延迟响应其他客户的时间。
  • 效率:一个服务器应该做到延迟最小化、吞吐量最大化,避免不必要地使用CPU。多线程可能会导致糟糕的性能由于上下文切换、同步和数据移动。
  • 编程简洁:服务器的设计上应该简化使用合适的并发策略。多线程可能需要复杂的并发控制方案。
  • 可移植性:多线程不是可用在所有操作系统平台。
  • 适应性:集成新的或改进服务,如改变消息格式或添加服务器端缓存,应该承担最小的现有代码的修改和维护成本。例如,实现新应用程序服务应该不需要修改通用事件多路分解和调度机制。

针对上面的问题,可以集成同步多路分解事件并分发相应的事件处理程序来处理相应的事件。对于每一个应用程序所提供的服务,引入一个单独的事件处理器处理某些类型的事件。所有事件处理程序实现了相同的接口。事件处理程序注册一个初始调度程序,它使用一个同步事件信号分离器等待事件发生。当事件发生时,同步事件信号分离器通知初始调度器,它同步告知事件处理程序去关联对应的事件。事件处理程序然后分派事件到实现了所请求服务的方法中。

上述日志系统的Reactor模式类图如下所示:


由图可已看出Reactor模式的角色构成一种有5种:

1. Handle(句柄或者描述符):本质上表示一种资源,是有操作系统提供的;该资源用于一个个的事件,比如说文件描述符,或者针对网络编程中的Socket描述符。事件即可已来自于外部,也可以来自于内部;外部事件比如说客户端的连接请求,客户端发送过来数据等;内部事件比如操作系统产生的定时器事件等。它本质就是一个文件描述符。Handle是事件产生的发源地,例如客户端链接服务器端,以及发送数据事件等。

2. Synchronous Event Demultipexer(同步事件分离器):它本身就是一个系统调用,用于等待事件发生(事件可能是一个,也可能是多个)。调用方在调用它的时候会被阻塞,一直阻塞到同步事件分离器上有事件产生为止。对于Liunx来说,同步事件分离器指的是常用的IO多路复用器,不如select、poll、epoll等。在JAVA NIO中,同步事件分离器对应的组件就是Selector;对应的阻塞方法就是select方法。

3. Event Handler(事件处理器):本身由多个回调方法构成,这些回调方法构成了与应用相关的对于某个事件的反馈机制。而JAVA NIO 并未给我们提供相关的事件处理器,我们都是自己处理的,而Netty 提供了很多的回调方法 例如 通道注册,通道激活,通道读数据等回调方法。Netty相比NiO来说,在事件处理器方面提供了大量的回调方法,提供了在特定事件产生时实现相应的回调方法进行业务逻辑的处理。

4. Concrete Event Handler(具体事件处理器):它是Event Handler 的实现,它本身实现了事件处理器锁提供的各个回调方法,从而实现了特定于业务的逻辑,它本质就是我们编写的一个个的处理器实现。

5. Initiation Dispathcher(初始分发器):实际上就是Reactor角色。它本身定义了一些规范,这些规范用于控制事件的调度方式,同时又提供了应用进行事件处理器的,注册、删除等设施。它本身是整个事件处理器的核心所在,Initiation Dispathcher会通过同步事件分离器来等待事件的发生。一旦事件发生,Initiation Dispathcher首先会分离出一每一个事件,然后调用事件处理器,最后调用相关的回调方法来处理这些事件。

客户端连接到日志服务器所经过的一系列步骤如下图所示:


日志服务器记录日志所经过的一系列步骤如下图所示:


03

如何使用Reactor模式

在网络服务和分布式对象中,对于网络中的某一个请求处理,我们比较关注的内容大致为:

  • Read request: 读请求
  • Decode request:解码
  • Process service:系统里的业务处理
  • Encode reply:编码
  • Send reply: 响应

传统的数据传输方式:

一个客户端连接服务器端,服务器端 就会生成一个线程去做相对应IO操作以及业务逻辑处理。传统的数据传输方式有什么问题呢?

每一个客户端会连接服务端就会产生一个线程,如果10万个客户端那么就会产生10万个线程,而每个服务端的CPU和内存资源是有限的,并且线程的切换会耗费CPU资源,而且这种模型由于IO在阻塞时会一直等待,因此在用户负载增加时,性能下降的非常快。


基础Reacotor 版本:

对比原有的方式进行了改进,客户端不变服务器端会有一个Reactor线程对象,它去检测监听客户端向服务器发起的连接,当连接建立好后或触发相应的事件之后,Reactor会通过派发给特定的处理器,也就是说由一个不断等待和循环的单独进程(线程)来做这件事,它接受所有handler的注册,并负责先操作系统查询IO是否就绪,在就绪后就调用指定handler进行处理。

这里补充一下NIO相关的支持:

  • Channel:通道,它就像自来水管道一样,网络数据通过channle读取和写入,支持非阻塞的读。
  • Buffer: 缓冲区,可以被Channel 直接读写
  • Selectors:选择器,用来检查Channel 上IO事件的产生
  • SelectionKeys: 维护IO事件的状态和绑定信息



使用多个Reacotrs以及多线程版本:

相对基础Reactor版本,Reactor进行分发后的业务操作处理显然没有 Reactor处理的快,因为reactor主要作用是接收,主要的是后续的业务操作,如果大量的业务操作处理的很慢会拖累Reactor分发的速度,这样我们就将业务操作交给非IO操作的线程池去处理。



04

Reator 模式的优缺点


1、占用更少的资源 这种方式并不需要对每个客户端产生一个线程。

2、它的成本更低,因为线程少了,上下文切换就少了。

3、相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛,并且不易于调试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值