【Netty权威指南】Netty架构剖析

1、Netty逻辑架构

Netty采用了典型的三层网络架构进行设计和开发,逻辑架构如图20-1所示。
【Netty权威指南】Netty架构剖析

1.1、Reactor通信调度层

它由一系列辅助类完成,包括 Reactor线程 NioEventLoop及其父类, NioSocketChannel、NioServerSocketChannel及其父类, ByteBuffer以及由其衍生出来的各种 Buffer, Unsafe以及其衍生出的各种内部类等。该层的主要职责就是监听网络的读写和连接操作,负责将网络层的数据读取到内存缓冲区中,然后触发各种网络事件,例如连接创建、连接**读事件、写事件等,将这些事件触发到 Pipeline中,由 Pipeline管理的职责链来进行后续的处理。

1.2、职责链 ChannelPipeline

它负责事件在职责链中的有序传播,同时负责动态地编排职责链。职责链可以选择监听和处理自己关心的事件,它可以拦截处理和向后/向前传播事件。不同应用的 Handler节点的功能也不同,通常情况下,往往会开发编解码 Hanlder用于消息的编解码,它可以将外部的协议消息转换成内部的POJO对象,这样上层业务则只需要关心处理业务逻辑即可,不需要感知底层的协议差异和线程模型差异,实现了架构层面的分层隔离。

1.3、业务逻辑编排层(Service ChannelHandler)

业务逻辑编排层通常有两类:一类是纯粹的业务逻辑编排,还有一类是其他的应用层协议插件,用于特定协议相关的会话和链路管理。例如CMPP协议,用于管理和中国移动短信系统的对接。
架构的不同层面,需要关心和处理的对象都不同,通常情况下,对于业务开发者,只需要关心职责链的拦截和业务 Handler的编排。因为应用层协议栈往往是开发一次,到处运行,所以实际上对于业务开发者来说,只需要关心服务层的业务逻辑开发即可。各种应用协议以插件的形式提供,只有协议开发人员需要关注协议插件,对于其他业务开发人员来说,只需关心业务逻辑定制。这种分层的架构设计理念实现了NIO框架各层之间的解耦,便于上层业务协议栈的开发和业务逻辑的定制正是由于 Netty的分层架构设计非常合理,基于Nety的各种应用服务器和协议栈开发才能够如雨后春笋般得到快速发展。

2、关键架构质量属性

2.1、高性能

影响最终产品的性能因素非常多,其中软件因素如下。
◎架构不合理导致的性能问题。
◎编码实现不合理导致的性能问题,例如锁的不恰当使用导致性能瓶颈。
硬件因素如下。
◎服务器硬件配置太低导致的性能问题。
◎带宽、磁盘的IOPS等限制导致的IO操作性能差
◎测试环境被共用导致被测试的软件产品受到影响
尽管影响产品性能的因素非常多,但是架构的性能模型合理与否对性能的影响非常大。如果一个产品的架构设计得不好,无论开发如何努力,都很难开发出一个高性能、高可用的软件产品。

“性能是设计出来的,而不是测试出来的”。下面我们看Netty的架构设计是如何实现高性能的。
(1)采用异步非阻塞的IO类库,基于 Reactor模式实现,解决了传统同步阻塞IO模式下一个服务端无法平滑地处理线性增长的客户端的问题。
(2)TCP接收和发送缓冲区使用直接内存代替堆内存,避免了内存复制,提升了IO读取和写入的性能。
(3)支持通过内存池的方式循环利用 ByteBuf,避免了频繁创建和销毁 ByteBuf带来的性能损耗。
(4)可配置的I/O线程数、TCP参数等,为不同的用户场景提供定制化的调优参数,满足不同的性能场景。
(5)采用环形数组缓冲区实现无锁化并发编程,代替传统的线程安全容器或者锁。
(6)合理地使用线程安全容器、原子类等,提升系统的并发处理能力。
(7)关键资源的处理使用单线程串行化的方式,避免多线程并发访问带来的锁竞争和额外的CPU资源消耗问题。
(8)通过引用计数器及时地申请释放不再被引用的对象,细粒度的内存管理降低了GC的频率,减少了频繁GC带来的时延增大和CPU损耗
无论是 Netty的官方性能测试数据,还是携带业务实际场景的性能测试,Netty在各个NIO框架中综合性能是最高的。

2.2、可靠性

作为一个高性能的异步通信框架,架构的可靠性是大家选择的一个重要依据。下面我们探讨Netty架构的可靠性设计。

2.2.1、链路有效性检测

由于长连接不需要每次发送消息都创建链路,也不需要在消息交互完成时关闭链路,因此相对于短连接性能更高。对于长连接,一旦链路建立成功便一直维系双方之间的链路,直到系统退出。
为了保证长连接的链路有效性,往往需要通过心跳机制周期性地进行链路检测。使用周期性心跳的原因是:在系统空闲时,例如凌晨,往往没有业务消息。如果此时链路被防火墙Hang住,或者遭遇网络闪断、网络单通等,通信双方无法识别岀这类链路异常。等到第二天业务高峰期到来时,瞬间的海量业务冲击会导致消息积压无法发送给对方,由于链路的重建需要时间,这期间业务会大量失败(集群或者分布式组网情况会好一些)。为了解决这个问题,需要周期性的心跳对链路进行有效性检测,一旦发生问题,可以及时关闭链路,重建TCP连接当有业务消息时,无须心跳检测,可以由业务消息进行链路可用性检测。所以心跳消息往往是在链路空闲时发送的。
为了支持心跳,Netty提供了如下两种链路空闲检测机制。
◎读空闲超时机制:当连续周期T没有消息可读时,触发超时 Handler,用户可以基于读空闲超时发送心跳消息,进行链路检测;如果连续N个周期仍然没有读取到心跳消息,可以主动关闭链路。
◎写空闲超时机制:当连续周期T没有消息要发送时,触发超时 Handler,用户可以基于写空闲超时发送心跳消息,进行链路检测;如果连续N个周期仍然没有接收到对方的心跳消息,可以主动关闭链路。
为了满足不同用户场景的心跳定制, Netty提供了空闲状态检测事件通知机制,用户可以订阅空闲超时事件、写空闲超时事件、读或者写超时事件,在接收到对应的空闲事件之后,灵活地进行定制。

2.2.2、内存保护机制

Netty提供多种机制对内存进行保护,包括以下几个方面。
◎通过对象引用计数器对Nety的 ByteBuf等内置对象进行细粒度的内存申请和释放,对非法的对象引用进行检测和保护。
◎通过内存池来重用 ByteBuf,节省内存。
◎可设置的内存容量上限,包括 ByteBuf、线程池线程数等。
AbstractReferenceCountedByteBuf的内存管理方法实现如图20-6、20-7所示。

【Netty权威指南】Netty架构剖析

ByteBuf的解码保护,防止非法码流导致内存溢出,代码如图20-8所示。

【Netty权威指南】Netty架构剖析

如果长度解码器没有单个消息最大报文长度限制,当解码错位或者读取到畸形码流时,长度值可能是个超大整数值,例如4294967296,这很容易导致内存溢出。如果有上限保护,例如单条消息最大不允许超过10MB,当读取到非法消息长度4294967296后,直接抛出解码异常,这样就避免了大内存的分配。

2.2.3、优雅停机

相比于Netty的早期版本,Nety5.0版本的优雅退出功能做得更加完善。优雅停机功能指的是当系统退出时,JVM通过注册的 Shutdown Hook拦截到退出信号量,然后执行退岀操作,释放相关模块的资源占用,将缓冲区的消息处理完成或者淸空,将待刷新的数据持久化到磁盘或者数据库中,等到资源回收和缓冲区消息处理完成之后,再退出。
优雅停机往往需要设置个最大超时时间T,如果达到T后系统仍然没有退出,则通过Kill -9 pid强杀当前的进程。
Netty所有涉及到资源回收和释放的地方都增加了优雅退出的方法,它们的相关接口如表20-1所示。
【Netty权威指南】Netty架构剖析

2.3、可定制性

Netty的可定制性主要体现在以下几点。
◎责任链模式: ChannelPipeline基于责任链模式开发,便于业务逻辑的拦截、定制和扩展
◎基于接口的开发:关键的类库都提供了接口或者抽象类,如果Nety自身的实现无法满足用户的需求,可以由用户自定义实现相关接口。
◎提供了大量工厂类,通过重载这些工厂类可以按需创建出用户实现的对象
◎提供了大量的系统参数供用户按需设置,增强系统的场景定制性。

2.4、可扩展性

基于Netty的基础NlO框架,可以方便地进行应用层协议定制,例如HTTP协议栈、Thrift协议栈、FTP协议栈等。这些扩展不需要修改 Netty的源码,直接基于Netty的二进制类库即可实现协议的扩展和定制。

目前,业界存在大量的基于Netty框架开发的协议,例如基于Netty的HTTP协议、Dubbo协议、 RocketMQ内部私有协议等。