Java NIO通信框架在电信领域的实践
华为电信软件技术架构演进
Java NIO框架在技术变迁中起到的关键作用
C和C++主导的第一代架构
Spring + Struts + Tomcat 的第二代架构
Java高性能服务端
以SOA为中心的第三代架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。
如何整合异构系统,实现高效企业集成,也是一个巨大的挑战,此时,企业服务总线(ESB)是个不错的选择。
以分布式、云化为核心的第四代架构
基于X86架构的廉价硬件 + 分布式软件的模式在互联网行业得到了大规模应用,分布式架构日趋成熟。
传统SOA架构中的一些缺陷逐步暴露,例如企业集成总线ESB是实体总线,性能线性扩展能力有限;硬件负载均衡器的压力越来越大,不断扩容导致硬件成本增加;随着业务规模的不断增长,传统的数据库、配置中心等逐渐成为单点瓶颈等。
我们需要通过新的分布式架构来解决电信软件面临的成本高、性能无法线性增长等问题,以分布式技术为核心构建的华为分布式中间件应用而生,它主要包括如下组件:
1) 高性能、低时延的分布式服务框架;
2) 分布式消息队列MQ;
3) 分布式缓存;
4) 分布式数据库访问中间件,支持跨库操作,支持异构数据库;
5) 软负载SLB;
6) 分布式日志采集和检索(Flume + ELK);
7) 分布式实时流式计算框架;
8) 分布式消息跟踪系统;
9) 其它……
第四代技术架构以分布式、云化为核心,相比于前三代架构,它的核心特性如下:
1) 采用分布式技术构建,所有的中间件都没有单点,支持线性增长和弹性伸缩;
2) 以微服务架构为核心,打造电信领域的DevOps(结合华为PaaS平台);
3) 由传统的SOA Governance 向微服务治理和自治演进,提升服务治理效能;
4) 分布式日志采集 + 实时流式计算框架,更快的故障定界,提升大规模、分布式系统中的运维效率;
5) 业务和数据的拆分,分而治之,通过分布式中间件服务向业务屏蔽拆分细节;
6) 架构云化带来的巨大优势:资源池提升硬件利用率、DevOps提升开发和运维效率、应用和服务的自动弹性伸缩、应用和服务故障自动恢复、高HA、自动化运维等。
【以Netty为代表的NIO框架】
传统同步阻塞通信面临的主要问题如下:
1) 性能问题:一连接一线程模型导致服务端的并发接入数和系统吞吐量受到极大限制;
2) 可靠性问题:由于I/O操作采用同步阻塞模式,当网络拥塞或者通信对端处理缓慢会导致I/O线程被挂住,阻塞时间无法预测;
3) 可维护性问题:I/O线程数无法有效控制、资源无法有效共享(多线程并发问题),系统可维护性差
从上图我们可以看出,每当有一个新的客户端接入,服务端就需要创建一个新的线程(或者重用线程池中的可用线程),每个客户端链路对应一个线程。当客户端处理缓慢或者网络有拥塞时,服务端的链路线程就会被同步阻塞,也就是说所有的I/O操作都可能被挂住,这会导致线程利用率非常低,同时随着客户端接入数的不断增加,服务端的I/O线程不断膨胀,直到无法创建新的线程。
同步阻塞I/O导致的问题无法在业务层规避,必须改变I/O模型,才能从根本上解决这个问题。
基于Reactor模型统一调度的长连接和短连接协议栈,无论是性能、可靠性还是可维护性,都可以“秒杀”传统基于BIO开发的应用服务器和各种协议栈,这种指标差异本质上是一种技术代差。
【从Java 原生NIO到NIO框架】
JAVA 原生NIO类库的复杂性
现在我们总结一下为什么不建议开发者直接使用JDK的NIO类库进行开发,具体原因如下:
(1)NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等;
(2)需要具备其他的额外技能做铺垫,例如熟悉java多线程编程。这是因为NIO编程涉及到Reactor模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的NIO程序;
(3)可靠性能力补齐,工作量和难度都非常大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题,NIO编程的特点是功能开发相对容易,但是可靠性能力补齐的工作量和难度都非常大;
(4)JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官方声称在JDK1.6版本的update18修复了该问题,但是直到JDK1.7版本该问题仍旧存在,只不过该BUG发生概率降低了一些而已,它并没有被根本解决。
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可以被及时修复,同时,更多的新功能会加入;
7) 经历了大规模的商业应用考验,质量得到验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业得到成功商用,证明了它已经完全能够满足不同行业的商业应用了。
【Netty的非阻塞I/O调度模型】
【高性能的序列化框架】
在华为软件,对于序列化框架的选择,我们遵循如下几个原则:
1) 序列化后的码流大小(网络带宽的占用);
2) 序列化&反序列化的性能(CPU、内存等资源占用);
3) 是否支持跨语言(异构系统的对接和开发语言切换);
4) 高并发调用时的性能,是否随着线程并发数线性增长。
基于上述的指标,目前最常用的选择是:Google的ProtoBuf和Apache的Thrift。
Netty原生提供了对ProtoBuf序列化框架的支持,它的优点如下:
1) 在谷歌内部长期使用,产品成熟度高;
2) 跨语言、支持多种语言,包括C++、Java和Python;
3) 编码后的消息更小,更加有利于存储和传输;
4) 编解码的性能非常高;
5) 支持不同协议版本的前向兼容;
6) 支持定义可选和必选字段。
Thrift相对复杂一些,需要将编解码框架从Thrift中剥离出来,然后利用Netty编解码框架的扩展性定制实现,在此不再赘述。
【收敛的Reactor线程模型】
如果使用Tomcat等做Web容器,为了保证吞吐量和性能,HTTP线程池的最大线程数往往配置为1024。
在系统运行期间我们Dump线程堆栈,发现大量的线程竞争,这不仅导致HTTP协议栈的性能下降,更影响其它业务处理线程的执行效率。
【其它优化】
为了进一步提升性能,降低时延,我们还采用了其它一些优化措施,总结如下:
1) 使用Netty 4的内存池,减少业务高峰期ByteBuf频繁创建和销毁导致的GC频率和时间;
2) 在程序中充分利用Netty提供的“零拷贝”特性,减少额外的内存拷贝,例如使用CompositeByteBuf而不是分别为Head和Body各创建一个ByteBuf对象;
3) TCP参数的优化,设置合理的Send和Receive Buffer,通常建议值为64K – 128K;
4) 软中断:如果Linux内核版本支持RPS(2.6.35以上版本),开启RPS后可以实现软中断,提升网络吞吐量;
5) 无锁化串行开发理念:使用Netty 4.X版本,天生支持串行化处理;业务开发过程中,遵循Netty 4的线程模型优化理念,防止人为增加线程竞争。