1. 华为电信软件技术架构演进
1.1. 电信软件
1.2. 华为电信软件的技术演进史
1.2.1. C和C++主导的第一代架构
在2005年之前,华为软件公司的核心系统主要以C和C++进行开发,由于C和C++开源框架非常少,加之那个时代开源 区并不成熟,大部分的系统都采用自研开发,包括协议栈、系统调度、数据访问层和日志。
大多数的软件都运行在服务端,对外提供高性能、低时延和高并发的系统调用,协议栈大多数都采用电信私有协议栈,对于部分有前台管理Portal的系统,往往基于原生的HTML或者Struts等WEB框架开发,通过HTTP协议与后端进行交互,它的逻辑架构图如下:
1.3. 架构演进中的技术
随着架构的演进,Java的版本也在不断升级,技术堆栈不断更新,EJB、Spring、RMI、MQ[消息队列]、Node.js、NIO、Hadoop等。在众多技术堆栈中,我印象最深的就是Java的NIO类库以及业界成熟NIO框架的使用,它在华为软件架构演进中发挥了重大作用,曾立下了汗马功劳。现在,以Netty为代表的NIO框架已经在华为平台产品和业务产品中得到了广泛的应用。
作为华为软件公司最早使用Java NIO技术进行平台开发、2009年即在全球商用成功的亲历者和实践者,我想跟大家分享下Java NIO框架在华为软件以及电信领域的应用和实践。
2. Java NIO 技术的引入
2.1. BIO带给我们深深伤痛
在2008年的时候,我参与设计和开发的一个电信系统在月初出帐期,总是发生大量的连接超时和读写超时异常,业务的失败率相比于平时高了很多, 表中的很多指标都差强人意。后来经过排查,发现问题的主要原因出现在下游 元的处理性能上,月初的时候BSS出帐,在出帐期间BSS系统运行缓慢,由于双方采用了同步阻塞式的HTTP+XML进行通信(SOAP),导致任何一方处理缓慢都会影响对方的处理性能。按照故障隔离的设计原则,对方处理速度慢或者不回应答,不应该影响系统的其他功能模块或者协议栈,但是在同步阻塞I/O通信模型下,这种故障传播和相互影响是不可避免的,很难通过业务层面解决。
受限于当时Tomcat和Servlet的同步阻塞I/O模型,以及在Java领域异步HTTP协议栈的技术积累不足,当时我们并没有办法完全解决这个问题,只能通过调整线程池策略和HTTP超时时间来从业务层面做规避。由于我们的系统是一个全国级的一级系统,需要对接周边各个 元,同时服务器资源十分有限,即便采用了高峰期间动态修改超时时间、优化线程池模型等多种措施,效果依然差强人意。
每当跟客户开会的时候,客户总会提起这个话题:别人响应慢,为啥会导致你的系统阻塞呢,可以返回处理其它消息啊我无法跟客户解释技术细节,因为同步阻塞I/O仅仅是Java I/O的一种实现,操作系统支持非阻塞I/O和异步I/O。
站在技术的角度,客户的需求是合理并且也是可以实现的,当时受限于经验以及其它技术原因,我们无法从根本上解决客户提出的问题,团队有种深深的挫败感,Java BIO同步阻塞通信导致的各种问题给我留下了一些心理阴影,一直挥之不去。
2.2. BIO模型存在的问题
传统同步阻塞通信面临的主要问题如下:
- 性能问题:一连接一线程模型导致服务端的并发接入数和系统吞吐量受到极大限制;
- 可靠性问题:由于I/O操作采用同步阻塞模式,当 络拥塞或者通信对端处理缓慢会导致I/O线程被挂住,阻塞时间无法预测;
- 序列化后的码流大小( 络带宽的占用);
- 序列化&反序列化的性能(CPU、内存等资源占用);
- 是否支持跨语言(异构系统的对接和开发语言切换);
- 高并发调用时的性能,是否随着线程并发数线性增长。
基于上述的指标,目前最常用的选择是:Google的ProtoBuf和Apache的Thrift。
Netty原生提供了对ProtoBuf序列化框架的支持,它的优点如下: - 在谷歌内部长期使用,产品成熟度高;
- 跨语言、支持多种语言,包括C++、Java和Python;
- 编码后的消息更小,更加有利于存储和传输;
- 编解码的性能非常高;
- 支持不同协议版本的前向兼容;
- 支持定义可选和必选字段。
Netty ProtoBuf 服务端开发示例如下:
// 配置服务端的NIO线程组 - 全局流量整形的作用范围是进程级的,无论你创建了多少个Channel,它的作用域针对所有的Channel。用户可以通过参数设置: 文的接收速率、 文的发送速率、整形周期;
- 单链路流量整形与全局流量整形的最大区别就是它以单个链路为作用域,可以对不同的链路设置不同的整形策略,整形参数与全局流量整形相同。
3.2.3. 其它可靠性措施
其它比较重要的可靠性措施如下: - 客户端连接超时控制策略;
- 链路断连重连策略;
- 链路异常关闭资源释放;
- 解码失败的异常处理策略;
- 链路异常的捕获和处理;
- I/O线程的释放。
Thrift相对复杂一些,需要将编解码框架从Thrift中剥离出来,然后利用Netty编解码框架的扩展性定制实现,在此不再赘述。
3.1.3. 收敛的Reactor线程模型
Java线程采用抢占的方式争夺CPU等资源,当系统线程数增大到一定量级之后,性能不仅没有提升,反而下降。
对于大型的电信应用,如果使用Tomcat等做Web容器,为了保证吞吐量和性能,HTTP线程池的最大线程数往往配置为1024。在系统运行期间我们Dump线程堆栈,发现大量的线程竞争,这不仅导致HTTP协议栈的性能下降,更影响其它业务处理线程的执行效率。
使用Netty之后,我们通过控制NioEventLoopGroup的NioEventLoop个数来收敛线程,防止线程膨胀。NioEventLoop聚合了一个多路复用器Selector,可以高效的处理N个Channel,它的线程模型如下:
3.3. 华为软件对Netty的优化
针对电信软件的特点,结合华为软件的实际业务需求,我们对Netty进行了优化,优化的策略如下:
- 能够通过Netty提供的扩展点实现的,通过扩展点实现,不自己造轮子;
- 不允许修改Netty源码,基于Netty提供的接口,开发华为自己的优化实现类;
- 华为优化实现类独立打包,对原Netty类库是二进制依赖,不修改Netty原类库;
- 服务端和客户端创建时,传递华为自己的实现类参数。
华为的主要优化点总结如下: - 安全性改造:满足华为公司安全红线、电信运营商的安全需求相关改造;
- 可靠性增强:消息发送队列的上限保护、链路中断时缓存中待发送消息回调通知业务、增加错误码、异常日志打印抑制、I/O线程健康度检测等;
- 可定位性增强:单链路的 络吞吐量、接收发送的速度、接收发送的总字节数、畸形码流检测机制、解码时延超大消息日志打印等。
文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树NIONIO概述92039 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!