Netty高性能架构之道

1. 引言

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

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

输入图片说明

2. 为何选择Netty

Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是数一数二的,它已经获得成百上千的商用项目验证,例如Hadoop的RPC框架avro使用Netty做为底层通讯框架;不少其余业界主流的RPC框架,也使用Netty来构建高性能的异步通讯能力。后端

经过对Netty的分析,咱们将它的优势总结以下:api

  • API使用简单,开发门槛低;浏览器

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

  • 定制能力强,能够经过ChannelHandler对通讯框架进行灵活地扩展;安全

  • 性能高,经过与其余业界主流的NIO框架对比,Netty的综合性能最优;网络

  • 成熟、稳定,Netty修复了已经发现的全部JDK NIO BUG,业务开发人员不须要再为NIO的BUG而烦恼;多线程

  • 社区活跃,版本迭代周期短,发现的BUG能够被及时修复,同时,更多的新功能会加入;架构

  • 经历了大规模的商业应用考验,质量获得验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业获得成功商用,证实了它已经彻底可以知足不一样行业的商业应用了。

3. 架构分析

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

输入图片说明

3.1 Reactor 通讯调度层

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

3.2 职责链 PipeLine

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

3.2 职责链 业务逻辑处理层

第三层:业务逻辑处理层,能够分为两类:

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

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

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

4. 全方位对比

4.1 I/O模型

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

输入图片说明

它的弊端有不少:

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

  • 可靠性问题:因为I/O操做采用同步阻塞模式,当网络拥塞或者通讯对端处理缓慢会致使I/O线程被挂住,阻塞时间没法预测;

  • 可维护性问题: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带来的性能、吞吐量和可靠性问题。

4.2 线程调度模型

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

  • Reactor单线程模型:Reactor单线程模型,指的是全部的I/O操做都在同一个NIO线程上面完成。对于一些小容量应用场景,可使用单线程模型。

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

  • 主从Reactor多线程模型:主从Reactor线程模型的特色是服务端用于接收客户端链接的再也不是个1个单独的NIO线程,而是一个独立的NIO线程池。利用主从NIO线程模型,能够解决1个服务端监听线程没法有效处理全部客户端链接的性能不足问题。

事实上,Netty的线程模型并不是固定不变,经过在启动辅助类中建立不一样的EventLoopGroup实例并经过适当的参数配置,就能够支持上述三种Reactor线程模型。

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

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

输入图片说明

4.3 序列化方式

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

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

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

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

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

输入图片说明

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

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

5. Netty架构剖析

5.1 可靠性

Netty面临的可靠性挑战:

  • 做为RPC框架的基础网络通讯框架,一旦故障将致使没法进行远程服务(接口)调用。

  • 做为应用层协议的基础通讯框架,一旦故障将致使应用协议栈没法正常工做。

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

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

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

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

5.1.1 链路有效性检测

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

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

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

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

输入图片说明

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

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

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

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

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

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

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

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

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

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

5.1.2 流量整形

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

  • 防止因为上下游网元性能不均衡致使下游网元被压垮,业务流程中断;

  • 防止因为通讯模块接收消息过快,后端业务线程处理不及时致使的“撑死”问题。

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

输入图片说明

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

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

  • 全局流量整形:全局流量整形的做用范围是进程级的,不管你建立了多少个Channel,它的做用域针对全部的Channel。用户能够经过参数设置:报文的接收速率、报文的发送速率、整形周期。

  • 链路级流量整形:单链路流量整形与全局流量整形的最大区别就是它以单个链路为做用域,能够对不一样的链路设置不一样的整形策略。

5.1.3 优雅停机

Netty的优雅停机三部曲:

  • 再也不接收新消息

  • 退出前的预处理操做

  • 资源的释放操做

输入图片说明

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

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

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

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

  • 若是使用flush作批量消息发送,须要将积攒在发送队列中的待发送消息发送完成;

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

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

5.2 安全性

Netty面临的安全挑战:

  • 对第三方开放

  • 做为应用层协议的基础通讯框架

输入图片说明

安全威胁场景分析:

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

  • 应用层协议的安全性。做为高性能、异步事件驱动的NIO框架,Netty很是适合构建上层的应用层协议。因为绝大多数应用层协议都是公有的,这意味着底层的Netty须要向上层提供通讯层的安全传输功能。

5.3.1 SSL/TLS

Netty安全传输特性:

  • 支持SSL V2和V3

  • 支持TLS

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

SSL单向认证流程图以下:

输入图片说明

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

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

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

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

5.3.2 安全特性

经过Netty的扩展特性,能够自定义安全策略:

  • IP地址黑名单机制

  • 接入认证

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

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

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

5.3 扩展性

经过Netty的扩展特性,能够自定义安全策略:

  • 线程模型可扩展

  • 序列化方式可扩展

  • 上层协议栈可扩展

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

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

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

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

6. FAQ

6.1 如何解决端口冲突问题?

【问题】

据我以前了解到,Java的NIO selector底层在Windows下的实现是起两个随机端口互联来监测链接或读写事件,在Linux上是利用管道实现的;我有遇到过这样的需求,须要占用不少个固定端口作服务端,若是在Windows下,利用NIO框架(Mina或Netty)就有可能会形成端口冲突,这种状况有什么好的解决方案吗?

【回答】

你说的问题确实存在,Linux使用Pipe实现网络监听,Windows要启动端口。目前没有更好的办法,建议的方式是做为服务端的端口能够规划一个范围,而后根据节点和进程信息动态生成,若是发现端口冲突,能够在规划范围内基于算法从新生成一个新的端口。

6.2 如何释放端口?

【问题】

将Spring与Netty作了整合,使用Spring的Service开启 Netty主线程,可是中止整个运行容器的时候,Netty的TCP Server端口不能释放?退出处理时,有什么好的办法释放Netty Server端口么?

【回答】

实际上,由谁拉起Netty 主线程并不重要。咱们须要作的就是当应用容器退出的时候(Spring Context销毁),在退出以前调用Netty 的优雅退出接口便可实现端口、NIO线程资源的释放。请参考这篇文章:http://www.infoq.com/cn/articles/netty-elegant-exit-mechanism-and-principles

6.3 Netty是否适合写Web通讯?

Netty不是Web框架,没法解析JSP、HTML、JS等,可是它能够作Web 通讯,例如可使用Netty重写Tomcat的HTTP/HTTPS 通讯协议栈。

6.4 Netty是如何实现串行无锁设计?

【问题】

Netty的串行无锁化设计,如何在串行和并行中达到最优?

【回答】

为了尽量提高性能,Netty采用了串行无锁化设计,在IO线程内部进行串行操做,避免多线程竞争致使的性能降低。表面上看,串行化设计彷佛CPU利用率不高,并发程度不够。可是,经过调整NIO线程池的线程参数,能够同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列-多个工做线程模型性能更优。Netty的NioEventLoop读取到消息以后,直接调用ChannelPipeline的fireChannelRead(Object msg),只要用户不主动切换线程,一直会由NioEventLoop调用到用户的Handler,期间不进行线程切换,这种串行化处理方式避免了多线程操做致使的锁的竞争,从性能角度看是最优的。

相关文章
相关标签/搜索