Netty详解(一):Reactor模型

1. Reactor模型简介

在Java进行网络通信时,通常有两种体系结构,分别为:thread-based architecture(基于线程)、event-driven architecture(事件驱动)

1.1 thread-based architecture

基于线程的体系结构通常会使用多线程来处理客户端的请求,每当接收到一个请求,便开启一个独立的线程来处理。这种方式虽然是直观的,但是仅适用于并发访问量不大的场景,因为线程需要占用一定的内存资源,且操作系统在线程之间的切换也需要一定的开销,当线程数过多时显然会降低web服务器的性能。并且,当线程在处理I/O操作,在等待输入的这段时间线程处于空闲的状态,同样也会造成cpu资源的浪费。一个典型的设计如下:

在这里插入图片描述

1.2 event-driven architecture

事件驱动体系结构是目前比较广泛使用的一种。这种方式会定义一系列的事件处理器来响应事件的发生,并且将服务端接受连接与对事件的处理分离。其中,事件是一种状态的改变。比如,tcp中socket的new incoming connection(接受新的连接)、ready for read(Socket可读状态)、ready for write(Socket可写状态)。

2. Reactor模型基本概念

Reactor模型主要由以下几个角色构成:handle、Synchronous Event Demultiplexer、Initiation Dispatcher、Event Handler、Concrete Event Handler

2.1 Handle

handle在linux中一般称为文件描述符,而在window称为句柄,两者的含义一样。handle是事件的发源地。比如一个网络socket、磁盘文件等。而发生在handle上的事件可以有connection、ready for read、ready for write等。

2.2 Synchronous Event Demultiplexer

同步事件分离器,本质上是系统调用。比如linux中的select、poll、epoll等。比如,select方法会一直阻塞直到handle上有事件发生时才会返回。select会轮询所有注册到系统内核的Socket连接,一旦发现某个Socket连接数据准备完毕,select就会返回。用户线程获得了目标连接后,发起read系统调用,用户线程阻塞。内核开始复制数据。它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),然后kernel返回结果。poll类似与select,只是poll上注册的socket列表可以无限制,因为他是通过链表实现的。epoll系统调用则是对每个注册的Socket连接加入一个回调函数,一旦Socket连接数据准备完毕,Socket连接执行回调函数,然后将该fd放入到就绪链表中。用户线程可以通过访问就绪链表获得准备数据的Socket,然后发起read系统调用获得数据。

2.3 Event Handler

事件处理器,其会定义一些回调方法或者称为钩子函数,当handle上有事件发生时,回调方法便会执行,一种事件处理机制。

2.4 Concrete Event Handler

具体的事件处理器,实现了Event Handler。在回调方法中会实现具体的业务逻辑。

2.5 Initiation Dispatcher

初始分发器,也是reactor角色,提供了注册、删除与转发event handler的方法。当Synchronous Event Demultiplexer检测到handle上有事件发生时,便会通知initiation dispatcher调用特定的event handler的回调方法。

3. Reactor模型(Basic 版本)

单线程版本

Reactor设计模式是event-driven architecture的一种实现方式,处理多个客户端并发的向服务端请求服务的场景。每种服务在服务端可能由多个方法组成。reactor会解耦并发请求的服务并分发给对应的事件处理器来处理。目前,许多流行的开源框架都用到了reactor模式,如:netty、node.js等,包括java的NIO。

总体图示如下:

在这里插入图片描述

3.1 处理流程

  1. 当应用向Initiation Dispatcher注册Concrete Event Handler时,应用会标识出该事件处理器希望Initiation Dispatcher在某种类型的事件发生发生时向其通知,事件与handle关联。

  2. Initiation Dispatcher要求注册在其上面的Concrete Event Handler传递内部关联的handle,该handle会向操作系统标识。

  3. 当所有的Concrete Event Handler都注册到 Initiation Dispatcher上后,应用会调用handle_events方法来启动Initiation Dispatcher的事件循环,这时Initiation Dispatcher会将每个Concrete Event Handler关联的handle合并,并使用Synchronous Event Demultiplexer来等待这些handle上事件的发生

  4. 当与某个事件源对应的handle变为ready时,Synchronous Event Demultiplexer便会通知 Initiation Dispatcher。比如tcp的socket变为ready for reading

  5. Initiation Dispatcher会触发事件处理器的回调方法。当事件发生时, Initiation Dispatcher会将被一个“key”(表示一个激活的handle)定位和分发给特定的Event Handler的回调方法

  6. Initiation Dispatcher调用特定的Concrete Event Handler的回调方法来响应其关联的handle上发生的事件

4. Reactor模型(MultiThreaded 版本)

  • 战略性的添加线程以增加通信模型的可扩展性,主要是利用多核处理器架构体系。
  • 工作线程:多线程版本利用工作线程加速Handler的处理过程,并且它将非I/O的处理工作卸载到其他线程处理。
  • Reactor模型(MultiThread 版本):Reactor线程可以更加专注与执行I/O事件监听操作。

在这里插入图片描述

4.1 WorkThreads

  • 卸载非I/O处理任务以加速Reactor线程的监听事件处理工作
  • 将计算绑定处理的模型重新转换为事件驱动的模型形式,这种方式更加简单快速
  • 但是这种方式很难与I/O模式下重叠处理
  • 使用线程池可以很方便的调整和控制任务的执行

5. Reactor模型(MultiReactors 版本)

第三种模型,是将Reactor分成两部分

  • mainReactor负责监听server socket,accept新连接,并将建立的socket分派给subReactor。
  • subReactor负责多路分离已连接的socket,读写网络数据,对业务处理功能,其扔给worker线程池完成。通常,subReactor个数上可与CPU个数等同。利用多个subReactor可以实现事件监听处理逻辑的负载均衡。

在这里插入图片描述

5. Netty Reactor模型

说完Reacotr模型的三种形式,那么Netty是哪种呢?其实,还有一种Reactor模型的变种没说,那就是去掉线程池的第三种形式的变种,这也 是Netty NIO的默认模式。在实现上,Netty中的Boss类充当mainReactor,NioWorker类充当subReactor(默认 NioWorker的个数是Runtime.getRuntime().availableProcessors())。在处理新来的请求时,NioWorker读完已收到的数据到ChannelBuffer中,之后触发ChannelPipeline中的ChannelHandler流。

Netty是事件驱动的,可以通过ChannelHandler链来控制执行流向。因为ChannelHandler链的执行过程是在 subReactor中同步的,所以如果业务处理handler耗时长,将严重影响可支持的并发数。这种模型适合于像Memcache缓存这样的应用场景,但对需要操作数据库或者和其他模块阻塞交互的系统就不是很合适。

Netty的可扩展性非常好,而像ChannelHandler线程池化的需要,可以通过在 ChannelPipeline中添加Netty内置的ChannelHandler实现类–ExecutionHandler实现,对使用者来说只是 添加一行代码而已。对于ExecutionHandler需要的线程池模型,Netty提供了两种可选:

  1. MemoryAwareThreadPoolExecutor 可控制Executor中待处理任务的上限(超过上限时,后续进来的任务将被阻 塞),并可控制单个Channel待处理任务的上限;
  2. OrderedMemoryAwareThreadPoolExecutor是MemoryAwareThreadPoolExecutor 的子类,它还可以保证同一Channel中处理的事件流的顺序性,这主要是控制事件在异步处理模式下可能出现的错误的事件顺序,但它并不保证同一Channel中的事件都在一个线程中执行(通常也没必要)。一般来说,OrderedMemoryAwareThreadPoolExecutor 是个很不错的选择。
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值