Netty高性能架构的理解之道

Netty的简单介绍

Netty 是一个 NIO client-server(客户端服务器)框架,使用 Netty 可以快速开发网络应用,例如服务器和客户 端协议。 Netty 提供了一种新的方式来使开发网络应用程序,这种新的方式使得它很容易使用和有很强的扩展性。 Netty 的内部实现时很复杂的,但是 Netty 提供了简单易用的 api 从网络处理代码中解耦业务逻辑。 Netty 是完全基 于 NIO 实现的,所以整个 Netty 都是异步的。

简单点说就是Netty提供了一个简单,间接的方法来操作网络之间的通讯。

使用 Netty 能够做什么?

 开发异步、非阻塞的 TCP 网络应用程序;

 开发异步、非阻塞的 UDP 网络应用程序;

 开发异步文件传输应用程序;

开发异步 HTTP 服务端和客户端应用程序;

提供对多种编解码框架的集成,包括谷歌的 Protobuf、Jbossmarshalling、Java 序列化、压缩编解码、XML 解码、字符串编解码等,这些编解码框架可以被用户直接使用;

提供形式多样的编解码基础类库,可以非常方便的实现私有协议栈编解码框架的二次定制和开发;

基于职责链模式的 Pipeline-Handler 机制,用户可以非常方便的对网络事件进行拦截和定制;

所有的 IO 操作都是异步的,用户可以通过 Future-Listener 机制主动 Get 结果或者由 IO 线程操作完成之后主动 Notify 结果,用户的业务线程不需要同步等待;

IP 黑白名单控制;

打印消息码流;

流量控制和整形;

性能统计;

基于链路空闲事件检测的心跳检测

  ……

Netty 在哪些行业得到了应用? 

互联网行业:随着网站规模的不断扩大,系统并发访问量也越来越高,传统基于 Tomcat 等 Web 容器的垂直架构已经无法满足需求,需要拆分应用进行服务化,以提高开发和维护效率。从组网情况看,垂直的架构拆分之后,系统采用分布式部署,各个节点之间 需要远程服务调用,高性能的 RPC 框架必不可少,Netty 作为异步高性能的通信框架,往往作为基础通信组件被这些 RPC 框架使用。

典型的应用有:阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通信,Dubbo 协议默认使用 Netty 作为基础通信组件,用于实现各进程节点之间的内部通信。它的架构图如下:

图、Dubbo 节点间调用关系图

其中,服务提供者和服务消费者之间,服务提供者、服务消费者和性能统计节点之间使用 Netty 进行异步/同步通信。

除了 Dubbo 之外,淘宝的消息中间件 RocketMQ 的消息生产者和消息消费者之间,也采用 Netty 进行高性能、异步通信。

除了阿里系和淘宝系之外,很多其它的大型互联网公司或者电商内部也已经大量使用 Netty 构建高性能、分布式的网络服务器。

游戏行业:无论是手游服务端、还是大型的网络游戏,Java 语言得到了越来越广泛的应用。Netty 作为高性能的基础通信组件,它本身提供了 TCP/UDP 和 HTTP 协议栈,非常方便定制和开发私有协议栈。账号登陆服务器、地图服务器之间可以方便的通过 Netty 进行高性能的通信,架构示意图如下:

图、Netty 在游戏服务器架构中的应用

大数据领域:经典的 Hadoop 的高性能通信和序列化组件 Avro 的 RPC 框架,默认采用 Netty 进行跨节点通信,它的 Netty Service 基于 Netty 框架二次封装实现。

大数据计算往往采用多个计算节点和一个/N个汇总节点进行分布式部署,各节点之间存在海量的数据交换。由于 Netty 的综合性能是目前各个成熟 NIO 框架中最高的,因此,往往会被选中用作大数据各节点间的通信。

企业软件:企业和 IT 集成需要 ESB,Netty 对多协议支持、私有协议定制的简洁性和高性能是 ESB RPC 框架的首选通信组件。事实上,很多企业总线厂商会选择 Netty 作为基础通信组件,用于企业的 IT 集成。

通信行业:Netty 的异步高性能、高可靠性和高成熟度的优点,使它在通信行业得到了大量的应用。

使用传统的 Socket 开发挺简单的,我为什么要切换到 NIO 进行编程呢?

首先我们看下传统基于同步阻塞 IO(BIO)的线程模型图:


图、 同步阻塞 IO(BIO)线程模型图

由上图我们可以看出,传统的同步阻塞 IO 通信存在如下几个问题:

线程模型存在致命缺陷:一连接一线程的模型导致服务端无法承受大量客户端的并发连接;

性能差:频繁的线程上下文切换导致 CPU 利用效率不高;

可靠性差:由于所有的 IO 操作都是同步的,所以业务线程只要进行 IO 操作,也会存在被同步阻塞的风险,这会导致系统的可靠性差,依赖外部组件的处理能力和网络的情况。

采用非阻塞 IO(NIO)之后,同步阻塞 IO 的三个缺陷都将迎刃而解:

Nio 采用 Reactor 模式,一个 Reactor 线程聚合一个多路复用器 Selector,它可以同时注册、监听和轮询成百上千个 Channel,一个 IO 线程可以同时并发处理N个客户端连接,线程模型优化为1:N(N < 进程可用的最大句柄数)或者 M : N (M通常为 CPU 核数 + 1, N < 进程可用的最大句柄数);

由于 IO 线程总数有限,不会存在频繁的 IO 线程之间上下文切换和竞争,CPU 利用率高;

所有的 IO 操作都是异步的,即使业务线程直接进行 IO 操作,也不会被同步阻塞,系统不再依赖外部的网络环境和外部应用程序的处理性能。

 由于切换到 NIO 编程之后可以为系统带来巨大的可靠性、性能提升,所以,目前采用 NIO 进行通信已经逐渐成为主流。

为什么不直接基于 JDK 的 NIO 类库编程呢?


我们通过 JDK NIO 服务端和客户端的工作时序图来回答下这个问题:

 图、JDK NIO 服务端创建和通信序列图

即便抛开代码和 NIO 类库复杂性不谈,一个高性能、高可靠性的 NIO 服务端开发和维护成本都是非常高的,开发者需要具有丰富的 NIO 编程经验和网络维护经验,很多时候甚至需要通过抓包来定位问题。也许开发出一套 NIO 程序需要 1 个月,但是它的稳定很可能需要 1 年甚至更长的时间,这也就是为什么我不建议直接使用 JDK NIO 类库进行通信开发的一个重要原因。

下面再一起看下 JDK NIO 客户端的通信时序图:它同样非常复杂。

 图、JDK NIO 客户端创建和通信序列图

不选择JAVA原生NIO和IO的原因

基于IO的经典同步堵塞模型:

经典的IO模型也就是传统的服务器端同步阻塞I/O处理(也就是BIO,Blocking I/O)的经典编程模型,当我们每得到一个新的连接时,就会开启一个线程来处理这个连接的任务。之所以使用多线程,主要原因在于socket.accept()、socket.read()、socket.write()三个主要函数都是同步阻塞的,当一个连接在处理I/O的时候,系统是阻塞的,如果是单线程的话必然就挂死在那里;但CPU是被释放出来的,开启多线程,就可以让CPU去处理更多的事情。

因此这个模型最本质的问题在于,严重依赖于线程。但线程是很”贵”的资源,主要表现在:

线程的创建和销毁成本很高,在Linux这样的操作系统中,线程本质上就是一个进程。创建和销毁都是重量级的系统函数。

线程本身占用较大内存,像Java的线程栈,一般至少分配512K~1M的空间,如果系统中的线程数过千,恐怕整个JVM的内存都会被吃掉一半。

线程的切换成本是很高的。操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用。如果线程数过高,可能执行线程切换的时间甚至会大于线程执行的时间,这时候带来的表现往往是系统load偏高、CPU sy使用率特别高(超过20%以上),导致系统几乎陷入不可用的状态。

容易造成锯齿状的系统负载。因为系统负载是用活动线程数或CPU核心数,一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大。

基于NIO的异步模型:

NIO是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,而且已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。

不使用NIO的原因:

NIO的类库和API繁杂。需要很多额外的技能做铺垫。例如需要很熟悉Java多线程编程、Selector线程模型。导致工作量和开发难度都非常大。

扩展 ByteBuffer:NIO和Netty都有ByteBuffer来操作数据,但是NIO的ByteBuffer长度固定而且操作复杂,许多操作甚至都需要自己实现。而且它的构造函数是私有,不能扩展。Netty 提供了自己的

ByteBuffer 实现, Netty 通过一些简单的 APIs 对 ByteBuffer 进行构造、使用和操作,以此来解决 NIO 中的一些限制。

NIO 对缓冲区的聚合和分散操作可能会操作内存泄露,到jdk7才解决了内存泄露的问题

存在臭名昭著的epoll bug,导致Selector空轮询:这个bug会导致linux上导致cpu 100%

 为什么选择Netty

API使用简单,开发门槛低。

功能强大,预置了多种编解码功能,支持多种协议开发。

定制能力强,可以通过ChannelHadler进行扩展。

性能高,对比其它NIO框架,Netty综合性能最优。

经历了大规模的应用验证。在互联网、大数据、网络游戏、企业应用、电信软件得到成功,很多著名的框架通信底层就用了Netty,比如Dubbo

稳定,修复了NIO出现的所有Bug。

切换IO和NIO,因为IO和NIO的API完全不同,相互切换非常困难。

类似的框架对比:

与Mina相比有什么优势?

1、都是Trustin Lee的作品,Netty更晚;

2、Mina将内核和一些特性的联系过于紧密,使得用户在不需要这些特性的时候无法脱离,相比下性能会有所下降,Netty解决了这个设计问题;

3、Netty的文档更清晰,很多Mina的特性在Netty里都有;

4、Netty更新周期更短,新版本的发布比较快;

5、它们的架构差别不大,Mina靠apache生存,而Netty靠jboss,和jboss的结合度非常高,Netty有对google protocal buf的支持,有更完整的ioc容器支持(spring,guice,jbossmc和osgi);

6、Netty比Mina使用起来更简单,Netty里你可以自定义的处理upstream events 或/和 downstream events,可以使用decoder和encoder来解码和编码发送内容;

7、Netty和Mina在处理UDP时有一些不同,Netty将UDP无连接的特性暴露出来;而Mina对UDP进行了高级层次的抽象,可以把UDP当成”面向连接”的协议,而要Netty做到这一点比较困难。

在此我向大家推荐一个架构学习交流群。交流学习群号:575745314 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

Netty多种IO方式的比较:

1、BIO(同步阻塞IO)

使用ServerSocket绑定IP地址和监听端口,客户端发起连接,通过三次握手建立连接,用socket来进行通信,通过输入输出流的方式来进行同步阻塞的通信

每次客户端发起连接请求,都会启动一个线程

线程数量:客户端并发访问数为1:1,由于线程是JAVA虚拟机中非常宝贵的资源,一旦线程数急剧增加,系统性能会急剧下降,导致线程栈溢出,创建新的线程失败,并最终导致宕机

所以在JDK1.4之前,人们想到了一种方法,即PIO方式

2、PIO(伪异步阻塞IO)

使用线程池来处理客户端的请求

客户端个数:线程池最大线程数=M:N,其中M远大于N

在read和write的时候,还是IO阻塞的,只是把每个线程交由线程池来控制管理

3、NIO(异步阻塞IO)

用NIO方式处理IO

使用多路复用器Selector来轮询每个通道Channel,当通道中有事件时就通知处理,不会阻塞

使用相当复杂

4、AIO(真正的异步非阻塞IO)

NIO2.0引入了新的异步通道的概念,不需要使用多路复用器(Selector)对注册通道进行轮询即可实现异步读写,从而简化了NIO编程模型

使用Netty框架进行编程步骤

1、构建事件处理池

2、使用引导程序关联事件处理池、通道、事件处理器

3、绑定端口服务

4、等待操作完成

5、关闭事件处理池

几种IO的功能和特性对比

按照书上的例子码了一遍:

服务端:

服务端处理器:

客户端:

客户端处理器:

Netty的高性能架构之道

Netty是一个高性能、异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。

作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也基于Netty的NIO框架构建。

为什么选择Netty

Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,它已经得到成百上千的商用项目验证,例如Hadoop的RPC框架avro使用Netty作为底层通信框架;很多其他业界主流的RPC框架,也使用Netty来构建高性能的异步通信能力。

通过对Netty的分析,我们将它的优点总结如下:

1.API使用简单,开发门槛低;

2.功能强大,预置了多种编解码功能,支持多种主流协议;

3.定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;

4.性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;

5.成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;

6.社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入;

经历了大规模的商业应用考验,质量得到验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业得到成功商用,证明了它已经完全能够满足不同行业的商业应用了。

在此我向大家推荐一个架构学习交流群。交流学习群号:575745314 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

Netty架构分析

Netty 采用了比较典型的三层网络架构进行设计,逻辑架构图如下所示:

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

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

第三层:业务逻辑处理层,可以分为两类:

1.纯粹的业务逻辑 处理,例如订单处理。

2.应用层协议管理,例如HTTP协议、FTP协议等。

接下来,我从影响通信性能的三个方面(I/O模型、线程调度模型、序列化方式)来谈谈Netty的架构。

I/O模型

传统同步阻塞I/O模式如下图所示:

它的弊端有很多:

1.性能问题:一连接一线程模型导致服务端的并发接入数和系统吞吐量受到极大限制;

2.可靠性问题:由于I/O操作采用同步阻塞模式,当网络拥塞或者通信对端处理缓慢会导致I/O线程被挂住,阻塞时间无法预测;

3.可维护性问题:I/O线程数无法有效控制、资源无法有效共享(多线程并发问题),系统可维护性差;

几种I/O模型的功能和特性对比:

Netty的I/O模型基于非阻塞I/O实现,底层依赖的是JDK NIO框架的Selector。

Selector提供选择已经就绪的任务的能力。简单来讲,Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。

一个多路复用器Selector可以同时轮询多个Channel,由于JDK1.5_update10版本(+)使用了epoll()代替传统的select实现,所以它并没有最大连接句柄1024/2048的限制。这也就意味着只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端,这确实是个非常巨大的技术进步。

使用非阻塞I/O模型之后,Netty解决了传统同步阻塞I/O带来的性能、吞吐量和可靠性问题。

线程调度模型

常用的Reactor线程模型有三种,分别如下:

1.Reactor单线程模型:Reactor单线程模型,指的是所有的I/O操作都在同一个NIO线程上面完成。对于一些小容量应用场景,可以使用单线程模型。

2.Reactor多线程模型:Rector多线程模型与单线程模型最大的区别就是有一组NIO线程处理I/O操作。主要用于高并发、大业务量场景。

3.主从Reactor多线程模型:主从Reactor线程模型的特点是服务端用于接收客户端连接的不再是个1个单独的NIO线程,而是一个独立的NIO线程池。利用主从NIO线程模型,可以解决1个服务端监听线程无法有效处理所有客户端连接的性能不足问题。

事实上,Netty的线程模型并非固定不变,通过在启动辅助类中创建不同的EventLoopGroup实例并通过适当的参数配置,就可以支持上述三种Reactor线程模型。

在大多数场景下,并行多线程处理可以提升系统的并发性能。但是,如果对于共享资源的并发访问处理不当,会带来严重的锁竞争,这最终会导致性能的下降。为了尽可能的避免锁竞争带来的性能损耗,可以通过串行化设计,即消息的处理尽可能在同一个线程内完成,期间不进行线程切换,这样就避免了多线程竞争和同步锁。

为了尽可能提升性能,Netty采用了串行无锁化设计,在I/O线程内部进行串行操作,避免多线程竞争导致的性能下降。表面上看,串行化设计似乎CPU利用率不高,并发程度不够。但是,通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列-多个工作线程模型性能更优。

序列化方式

影响序列化性能的关键因素总结如下:

1.序列化后的码流大小(网络带宽占用)

2.序列化&反序列化的性能(CPU资源占用)

3.并发调用的性能表现:稳定性、线性增长、偶现的时延毛刺等

对Java序列化和二进制编码分别进行性能测试,编码100万次,测试结果表明:Java序列化的性能只有二进制编码的6.17%左右。

Netty默认提供了对Google Protobuf的支持,通过扩展Netty的编解码接口,用户可以实现其它的高性能序列化框架,例如Thrift的压缩二进制编解码框架。

不同的应用场景对序列化框架的需求也不同,对于高性能应用场景Netty默认提供了Google的Protobuf二进制序列化框架,如果用户对其它二进制序列化框架有需求,也可以基于Netty提供的编解码框架扩展实现。

Netty架构剖析之可靠性

Netty面临的可靠性挑战:

1.作为RPC框架的基础网络通信框架,一旦故障将导致无法进行远程服务(接口)调用。

2.作为应用层协议的基础通信框架,一旦故障将导致应用协议栈无法正常工作。

3.网络环境复杂(例如手游或者推送服务的GSM/3G/WIFI网络),故障不可避免,业务却不能中断。

从应用场景看,Netty是基础的通信框架,一旦出现Bug,轻则需要重启应用,重则可能导致整个业务中断。它的可靠性会影响整个业务集群的数据通信和交换,在当今以分布式为主的软件架构体系中,通信中断就意味着整个业务中断,分布式架构下对通信的可靠性要求非常高。

从运行环境看,Netty会面临恶劣的网络环境,这就要求它自身的可靠性要足够好,平台能够解决的可靠性问题需要由Netty自身来解决,否则会导致上层用户关注过多的底层故障,这将降低Netty的易用性,同时增加用户的开发和运维成本。

Netty的可靠性是如此重要,它的任何故障都可能会导致业务中断,蒙受巨大的经济损失。因此,Netty在版本的迭代中不断加入新的可靠性特性来满足用户日益增长的高可靠和健壮性需求。

链路有效性检测

Netty提供的心跳检测机制分为三种:

1.读空闲,链路持续时间t没有读取到任何消息;

2.写空闲,链路持续时间t没有发送任何消息;

3.读写空闲,链路持续时间t没有接收或者发送任何消息。

当网络发生单通、连接被防火墙Hang住、长时间GC或者通信线程发生非预期异常时,会导致链路不可用且不易被及时发现。特别是异常发生在凌晨业务低谷期间,当早晨业务高峰期到来时,由于链路不可用会导致瞬间的大批量业务失败或者超时,这将对系统的可靠性产生重大的威胁。

从技术层面看,要解决链路的可靠性问题,必须周期性的对链路进行有效性检测。目前最流行和通用的做法就是心跳检测。

心跳检测机制分为三个层面:

1.TCP层面的心跳检测,即TCP的Keep-Alive机制,它的作用域是整个TCP协议栈;

2.协议层的心跳检测,主要存在于长连接协议中。例如SMPP协议;

3.应用层的心跳检测,它主要由各业务产品通过约定方式定时给对方发送心跳消息实现。

心跳检测的目的就是确认当前链路可用,对方活着并且能够正常接收和发送消息。做为高可靠的NIO框架,Netty也提供了基于链路空闲的心跳检测机制:

1.读空闲,链路持续时间t没有读取到任何消息;

2.写空闲,链路持续时间t没有发送任何消息;

3.读写空闲,链路持续时间t没有接收或者发送任何消息。

流量整形

流量整形(Traffic Shaping)是一种主动调整流量输出速率的措施。Netty的流量整形有两个作用:

1.防止由于上下游网元性能不均衡导致下游网元被压垮,业务流程中断;

2.防止由于通信模块接收消息过快,后端业务线程处理不及时导致的“撑死”问题。

流量整形的原理示意图如下:

流量整形(Traffic Shaping)是一种主动调整流量输出速率的措施。一个典型应用是基于下游网络结点的TP指标来控制本地流量的输出。流量整形与流量监管的主要区别在于,流量整形对流量监管中需要丢弃的报文进行缓存——通常是将它们放入缓冲区或队列内,也称流量整形(Traffic Shaping,简称TS)。当令牌桶有足够的令牌时,再均匀的向外发送这些被缓存的报文。流量整形与流量监管的另一区别是,整形可能会增加延迟,而监管几乎不引入额外的延迟。

Netty支持两种流量整形模式:

1.全局流量整形:全局流量整形的作用范围是进程级的,无论你创建了多少个Channel,它的作用域针对所有的Channel。用户可以通过参数设置:报文的接收速率、报文的发送速率、整形周期。

2.链路级流量整形:单链路流量整形与全局流量整形的最大区别就是它以单个链路为作用域,可以对不同的链路设置不同的整形策略。

在此我向大家推荐一个架构学习交流群。交流学习群号:575745314 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

优雅停机

Netty的优雅停机三部曲:

1.不再接收新消息

2.退出前的预处理操作

3.资源的释放操作

Java的优雅停机通常通过注册JDK的ShutdownHook来实现,当系统接收到退出指令后,首先标记系统处于退出状态,不再接收新的消息,然后将积压的消息处理完,最后调用资源回收接口将资源销毁,最后各线程退出执行。

通常优雅退出需要有超时控制机制,例如30S,如果到达超时时间仍然没有完成退出前的资源回收等操作,则由停机脚本直接调用kill -9 pid,强制退出。

在实际项目中,Netty作为高性能的异步NIO通信框架,往往用作基础通信框架负责各种协议的接入、解析和调度等,例如在RPC和分布式服务框架中,往往会使用Netty作为内部私有协议的基础通信框架。 当应用进程优雅退出时,作为通信框架的Netty也需要优雅退出,主要原因如下:

尽快的释放NIO线程、句柄等资源;

如果使用flush做批量消息发送,需要将积攒在发送队列中的待发送消息发送完成;

正在write或者read的消息,需要继续处理;

设置在NioEventLoop线程调度器中的定时任务,需要执行或者清理。

Netty架构剖析之安全性

Netty面临的安全挑战:

对第三方开放

作为应用层协议的基础通信框架

安全威胁场景分析:

对第三方开放的通信框架:如果使用Netty做RPC框架或者私有协议栈,RPC框架面向非授信的第三方开放,例如将内部的一些能力通过服务对外开放出去,此时就需要进行安全认证,如果开放的是公网IP,对于安全性要求非常高的一些服务,例如在线支付、订购等,需要通过SSL/TLS进行通信。

应用层协议的安全性。作为高性能、异步事件驱动的NIO框架,Netty非常适合构建上层的应用层协议。由于绝大多数应用层协议都是公有的,这意味着底层的Netty需要向上层提供通信层的安全传输功能。

SSL/TLS

Netty安全传输特性:

1.支持SSL V2和V3

2.支持TLS

3.支持SSL单向认证、双向认证和第三方CA认证。

SSL单向认证流程图如下:

Netty通过SslHandler提供了对SSL的支持,它支持的SSL协议类型包括:SSL V2、SSL V3和TLS。

单向认证:单向认证,即客户端只验证服务端的合法性,服务端不验证客户端。

双向认证:与单向认证不同的是服务端也需要对客户端进行安全认证。这就意味着客户端的自签名证书也需要导入到服务端的数字证书仓库中。

CA认证:基于自签名的SSL双向认证,只要客户端或者服务端修改了密钥和证书,就需要重新进行签名和证书交换,这种调试和维护工作量是非常大的。因此,在实际的商用系统中往往会使用第三方CA证书颁发机构进行签名和验证。我们的浏览器就保存了几个常用的CA_ROOT。每次连接到网站时只要这个网站的证书是经过这些CA_ROOT签名过的。就可以通过验证了。

可扩展的安全特性

通过Netty的扩展特性,可以自定义安全策略:

1.IP地址黑名单机制

2.接入认证

3.敏感信息加密或者过滤机制

IP地址黑名单是比较常用的弱安全保护策略,它的特点就是服务端在与客户端通信的过程中,对客户端的IP地址进行校验,如果发现对方IP在黑名单列表中,则拒绝与其通信,关闭链路。

接入认证策略非常多,通常是较强的安全认证策略,例如基于用户名+密码的认证,认证内容往往采用加密的方式,例如Base64+AES等。

Netty架构剖析之扩展性

通过Netty的扩展特性,可以自定义安全策略:

1.线程模型可扩展

2.序列化方式可扩展

3.上层协议栈可扩展

4.提供大量的网络事件切面,方便用户功能扩展

Netty的架构可扩展性设计理念如下:

5.判断扩展点,事先预留相关扩展接口,给用户二次定制和扩展使用;

6.主要功能点都基于接口编程,方便用户定制和扩展。

推荐阅读更多精彩内容