Netty中的Reactor模式

724 阅读7分钟

1 Reactor模式中IO事件的处理流程

一个IO事件从操作系统底层产生后,在Reactor模式中的处理流程如图:

未命名绘图.png

第1步:通道注册。IO事件源于通道(Channel),IO是和通道(对应于底层连接而言)强相关的。一个IO事件一定属于某个通道。如果要查询通道的事件,首先就要将通道注册到选择器。

第2步:查询事件。在Reactor模式中,一个线程会负责一个反应器(或者SubReactor子反应器),不断地轮询,查询选择器中的IO事件(选择键)。

第3步:事件分发。如果查询到IO事件,则分发给与IO事件有绑定关系的Handler业务处理器。

第4步:完成真正的IO操作和业务处理,这一步由Handler业务处理器负责。

2 Netty中的Channel

Netty中不直接使用Java NIO的Channel组件,对Channel组件进行了自己的封装。

Netty实现了一系列的Channel组件,对于每一种通信连接协议,Netty都实现了自己的通道。比如,除了Java的NIO,Netty还提供了Java面向流的OIO处理通道。

对应到不同的协议,Netty实现了对应的通道,每一种协议基本上都有NIO和OIO两个版本。

对应于不同的协议,Netty中常见的通道类型如下:

  • NioSocketChannel:异步非阻塞TCP Socket传输通道。
  • NioServerSocketChannel:异步非阻塞TCP Socket服务端监听通道。
  • NioDatagramChannel:异步非阻塞的UDP传输通道。
  • NioSctpChannel:异步非阻塞Sctp传输通道。
  • NioSctpServerChannel:异步非阻塞Sctp服务端监听通道。
  • OioSocketChannel:同步阻塞式TCP Socket传输通道。
  • OioServerSocketChannel:同步阻塞式TCP Socket服务端监听通道。
  • OioDatagramChannel:同步阻塞式UDP传输通道。
  • OioSctpChannel:同步阻塞式Sctp传输通道。
  • OioSctpServerChannel:同步阻塞式Sctp服务端监听通道。

一般来说,服务端编程用到最多的通信协议还是TCP,对应的Netty传输通道类型为NioSocketChannel类、Netty服务器监听通道类型为NioServerSocketChannel。不论是哪种通道类型,在主要的API和使用方式上和NioSocketChannel类基本都是相同的,更多是底层的传输协议不同,而Netty帮大家极大地屏蔽了传输差异。

在Netty的NioSocketChannel内部封装了一个Java NIO的SelectableChannel成员,通过对该内部的Java NIO通道的封装,对Netty的NioSocketChannel通道上的所有IO操作最终都会落地到Java NIO的SelectableChannel底层通道。

3 Netty中的Reactor

在Reactor模式中,一个反应器(或者SubReactor子反应器)会由一个事件处理线程负责事件查询和分发。该线程不断进行轮询,通过Selector选择器不断查询注册过的IO事件(选择键)。如果查询到IO事件,就分发给Handler业务处理器。

Netty中的反应器组件有多个实现类,这些实现类与其通道类型相互匹配。对应于NioSocketChannel通道,Netty的反应器类为NioEventLoop(NIO事件轮询)。

NioEventLoop类有两个重要的成员属性:一个是Thread线程类的成员,一个是Java NIO选择器的成员属性。

未命名绘图.png

一个NioEventLoop拥有一个线程,负责一个Java NIO选择器的IO事件轮询。

在Netty中,EventLoop反应器和Channel的关系是什么呢?理论上来说,一个EventLoop反应器和NettyChannel通道是一对多的关系:一个反应器可以注册成千上万的通道。

4 Netty中的Handler

Java NIO的IO事件类型中,可供选择器监控的通道IO事件类型包括以下4种:

  1. 可读:SelectionKey.OP_READ。
  2. 可写:SelectionKey.OP_WRITE。
  3. 连接:SelectionKey.OP_CONNECT。
  4. 接收:SelectionKey.OP_ACCEPT。

在Netty中,EventLoop反应器内部有一个线程负责Java NIO选择器的事件的轮询,然后进行对应的事件分发。事件分发(Dispatch)的目标就是Netty的Handler(含用户定义的业务处理器)。

Netty的Handler分为两大类,二者都继承了ChannelHandler处理器接口:

  1. 第一类是ChannelInboundHandler入站处理器;
  2. 第二类是ChannelOutboundHandler出站处理器

4.1 Netty入站处理的流程

Netty中的入站处理触发的方向为从通道触发,ChannelInboundHandler入站处理器负责接收(或者执行)。

以底层的Java NIO中的OP_READ输入事件为例:

  1. 在通道中发生了OP_READ事件后,会被EventLoop查询到
  2. 然后分发给ChannelInboundHandler入站处理器,调用对应的入站处理的read()方法。
  3. 在ChannelInboundHandler入站处理器内部的read()方法具体实现中,可以从通道中读取数据。

Netty中的入站处理不仅仅是OP_READ输入事件的还包括从通道底层触发,由Netty通过层层传递,调用ChannelInboundHandler入站处理器进行的其他某个处理。

4.2 Netty出站处理的流程

Netty中的出站处理指的是从ChannelOutboundHandler出站处理器到通道的某次IO操作。

例如,在应用程序完成业务处理后,可以通过ChannelOutboundHandler出站处理器将处理的结果写入底层通道。最常用的一个方法就是write()方法,即把数据写入通道。

Netty中的出站处理不仅仅包括Java NIO的OP_WRITE可写事件,还包括Netty自身从处理器到通道方向的其他操作。OP_WRITE可写事件是Java NIO的概念,和Netty的出站处理在概念上不是一个维度,Netty的出站处理是应用层维度的。

无论是入站还是出站,Netty都提供了各自的默认适配器实现:

  • ChannelInboundHandler的默认实现为ChannelInboundHandlerAdapter(入站处理适配器)。
  • ChannelOutboundHandler的默认实现为ChannelOutBoundHandlerAdapter(出站处理适配器)。
  • 这两个默认的通道处理适配器分别实现了基本的入站操作和出站操作功能。如果要实现自己的业务处理器,不需要从零开始去实现处理器的接口,只需要继承通道处理适配器即可。

5 Netty中的Pipeline

先梳理一下Netty的Reactor模式实现中各个组件之间的关系:

  1. 反应器(或者SubReactor子反应器)和通道之间是一对多的关系:一个反应器可以查询很多个通道的IO事件。

  2. 通道和Handler处理器实例之间是多对多的关系:一个通道的IO事件可以被多个Handler实例处理;一个Handler处理器实例也能绑定到很多通道,处理多个通道的IO事件。

通道和Handler处理器实例之间的绑定关系,Netty是如何组织的呢?

Netty设计了一个特殊的组件,叫作ChannelPipeline(通道流水线)。它像一条管道,将绑定到一个通道的多个Handler处理器实例串联在一起,形成一条流水线。ChannelPipeline的默认实现实际上被设计成一个双向链表。所有的Handler处理器实例被包装成双向链表的节点,被加入到ChannelPipeline中。

一个Netty通道拥有一个ChannelPipeline类型的成员属性,该属性的名称叫作pipeline。

以入站处理为例,每一个来自通道的IO事件都会进入一次ChannelPipeline。在进入第一个Handler处理器后,这个IO事件将按照既定的从前往后次序,在流水线上不断地向后流动,流向下一个Handler处理器。

在向后流动的过程中,会出现3种情况:

  1. 如果后面还有其他Handler入站处理器,那么IO事件可以交给下一个Handler处理器向后流动。

  2. 如果后面没有其他的入站处理器,就意味着这个IO事件在此次流水线中的处理结束了。

  3. 如果在中间需要终止流动,可以选择不将IO事件交给下一个Handler处理器,流水线的执行也被终止了。

Netty的通道流水线与普通的流水线不同,Netty的流水线不是单向的,而是双向的,而普通的流水线基本都是单向的。Netty是这样规定的:入站处理器的执行次序是从前到后,出站处理器的执行次序是从后到前。总之,IO事件在流水线上的执行次序与IO事件的类型是有关系的

20210708084236521.png 除了流动的方向与IO操作类型有关之外,流动过程中所经过的处理器类型也是与IO操作的类型有关的。入站的IO操作只能从Inbound入站处理器类型的Handler流过;出站的IO操作只能从Outbound出站处理器类型的Handler流过。