电信软件技术架构演进
电信软件
从广义上看电信软件的范围非常广,细分实际可以分为两大类:系统软件和业务应用软件。系统软件包括路由器底层的信令机软件、手机操作系统等,业务应用软件主要包括客户关系管理CRM、网上营业厅、融合计费OCS和各类消息网关,例如短信网关、彩信网关等。 本文重点介绍Java在电信业务软件中的应用。
电信软件的技术演进
C和C++主导时代
在2005年之前,电信软件主要使用C和C++进行开发,由于C和C++开源框架非常少,加之那个时代开源社区并不成熟,大部分的软件系统都由各设备提供商自己研发,或者采购国际大厂的相关产品,例如Oracle、IBM的平台中间件和服务器软件。
电信软件大多数都部署在小机中,对外提供高性能、低时延、高并发的系统调用,协议栈大多数都是电信私有协议栈,对于部分有前台管理Portal的系统,往往基于原生的HTML或者Struts等Web框架开发,通过HTTP协议与后端进行交互,它的逻辑架构图1所示。
图1 华为电信软件V1版逻辑架构图
在那个时代,电信软件绝大多数都部署在高性能的服务器中,处理各种信令、电信私有协议的接入和解析、复杂业务逻辑处理,系统对处理性能、时延、多核处理的要求非常高。当时Java主流版本还是JDK1.4.2(1.4.X),它在传统的Web应用、电子商务网站和政企系统中得到了比较广泛的应用,但在电信领域并没有大的应用,主要原因如下:
Java主导时代
2005-2008年间,Java开始逐渐替代C/C++,成为电信软件开发的首选语言,在这期间最显著的特点就是涌现出了一大批成熟的Java开源框架,它客观上也促进了Java语言的推广。下面针对电信软件的特点,对典型技术在电信软件领域的应用进行剖析。
电信领域技术实战
JavaNIO异步非阻塞通信
随着开源Web容器Tomcat、JBoss的逐渐成熟,以及商业Web容器的推广和发展,Java应用作为高性能服务器模式在电信领域也得到了认可。
尽管单节点Java应用服务的性能远远比不上C/C++开发的应用服务,但通过多JavaInstance集群部署+硬件扩展+负载均衡器,综合性能仍然可以和之前C/C++构建的系统相媲美。
切换到Java语言之后,硬件的成本有一定增加,但软件的开发效率、运维效率等却得到了极大提升,综合来看,切换到Java语言之后成本降低了很多。
在那个年代,无论是HTTP、SOAP等公有协议还是电信行业的私有协议栈,大多数都采用同步阻塞I/O通信,受限于Java底层的I/O类库,使用Java开发的通信程序都存在同步阻塞的问题。
传统同步阻塞通信面临的主要问题如下:
传统同步阻塞通信的处理模型图如图2所示。
图2 同步阻塞通信模型处理模型图
从图2我们可以看出,每当有一个新的客户端接入,服务端就需要创建一个新的线程(或者重用线程池中的可用线程),每个客户端链路对应一个线程。当客户端处理缓慢或者网络有拥塞时,服务端的链路线程就会被同步阻塞,也就是说所有的I/O操作都可能被挂住,这会导致线程利用率非常低,同时随着客户端接入数的不断增加,服务端的I/O线程不断膨胀,直到无法创建新的线程。
同步阻塞I/O在电信行业发生了很多问题,这些问题只能在业务层做规避,效果也不是很好,直到JDK1.4.2提供NIO类库之后,问题才开始逐步得到解决。
NIO类库最大的特点就是支持异步非阻塞通信,它的原理如下:在I/O编程过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者I/O多路复用技术进行处理。I/O多路复用技术通过把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比,I/O多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。非阻塞I/O的模型如图3所示。
图3 I/O多路复用技术
JavaNIO类库的出现具有划时代的意义,总结如下:
目前在电信行业软件中,越来越多的Java应用程序开始使用JavaNIO进行异步非阻塞通信,例如使用NIO框架Netty开发RPC通信框架,使用Tomcat7.X的NIO通信模型来优化服务端的通信处理性能。
采用NIO通信的服务端线程模型如图4所示。
图4 NIO通信服务端线程模型
并发编程技术
硬件的发展和多任务处理
随着硬件特别是多核处理器的发展和价格的下降,多任务处理已经是所有操作系统必备的一项基本功能。在同一个时刻让计算机做多件事情,不仅仅是因为处理器的并行计算能力得到了很大提升,还有一个重要的原因是计算机的存储系统、网络通信等I/O性能与CPU的计算能力差距太大,导致程序的很大一部分执行时间被浪费在I/Owait上面,CPU的强大运算能力没有得到充分利用。
Java提供了很多类库和工具用于降低并发编程的门槛,提升开发效率,一些开源的第三方软件也提供了额外的并发编程类库方便Java开发者,使开发者将重心放在业务逻辑的设计和实现上,而不是处处考虑线程的同步和锁。
随着Java的发展,并发编程类库也经历了由功能单一、使用复杂到功能多样、使用简单的蜕变。在高性能、低时延的电信软件中,多线程并发编程无处不在,下面让我们一起熟悉下并发编程在电信软件中的应用。
Java的线程
并发的实现可以通过多种方式来实现,例如:单进程-单线程模型,通过在一台服务器上启多个进程实现多任务的并行处理。但在Java语言中,是通过单进程-多线程的模型进行多任务的并发处理。因此,我们有必要熟悉一下Java的线程。大家都知道,线程是比进程更轻量级的调度执行单元,它可以把进程的资源分配和调度执行分开,各个线程可以共享内存、I/O等操作系统资源,但又能够被操作系统发的内核线程或者进程执行。各线程可以独立的启动、运行和停止,实现任务的解耦。
主流的操作系统都提供了线程实现,目前实现线程的方式主要有三种,分别是:
由于虚拟机规范并没有强制规定Java的线程必须使用哪种方式实现,因此,不同的操作系统实现的方式也可能存在差异。对于Sun的JDK,在Windows和Linux操作系统上采用了内核线程的实现方式,在Solaris版本的JDK中,提供了一些专有的虚拟机线程参数,用于设置使用哪种线程模型。
JDK1.4的Wait和Notify
JDK1.5之前,并没有线程池类库,开发者需要自己创建线程、停止线程和销毁线程。同样也没有线程安全的容器和数据结构,存在多线程并发操作的地方,需要开发者自己使用同步块synchronized、wait和notify。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法或者代码块。同步的作用不仅仅是互斥,它的另一个作用就是共享可变性,当某个线程修改了可变数据并释放锁后,其他线程可以获取被修改变量的最新值。如果没有正确的同步,这种修改对其他线程是不可见的。
尽管synchronized很好用,但它是个重量级的锁,如果使用范围不当,会导致单点瓶颈,造成系统性能下降。电信软件往往需要启动较多数量的线程,如果系统存在不恰当的synchronized同步块或synchronized同步块过多,线程竞争越激烈,性能下降越厉害。
在JDK1.4时代,多线程并发编程的水平决定了软件的性能和质量,它对软件开发人员的技能要求非常高。
JDK1.5新增的并发编程类库
在JDK1.5的发行版本中,Java平台新增了java.util.concurrent类库,这个包中提供了一系列的线程安全集合、容器和线程池,利用这些新的线程安全类可以极大地降低Java多线程编程的难度,提升开发效率。
在JDK1.5的发行版本中,Java平台新增了java.util.concurrent类库,这个包中提供了一系列的线程安全集合、容器和线程池,利用这些新的线程安全类可以极大地降低Java多线程编程的难度,提升开发效率。新的并发编程包中的工具可以分为如下四类:
除了ThreadPoolExecutor,JDK还提供了专门用于执行定时任务的线程池ScheduledThreadPoolExecutor,利用它可以高效地调度和执行定时任务。相比于老版本JDK提供的Timer,性能更高、资源占用更少。
在电信软件中,往往需要对海量的消息进行超时控制,利用ScheduledThreadPoolExecutor可以同时执行海量定时器。
JDK1.5新的并发编程工具包中还新增了读写锁,它是个轻量级、细粒度的锁,合理地使用读写锁,相比于传统的同步锁,可以提升并发访问的性能和吞吐量,在读多写少的场景下,使用同步锁比同步块性能高一大截。
读写锁的使用总结如下:
CAS指令和原子类的应用:互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因此这种同步被称为阻塞同步,它属于一种悲观的并发策略,我们称之为悲观锁。随着硬件和操作系统指令集的发展和优化,产生了非阻塞同步,被称为乐观锁。简单的说就是先进行操作,操作完成之后再判断下看看操作是否成功,是否有并发问题,如果有就进行失败补偿,如果没有就算操作成功,这样就从根本上避免了同步锁的弊端。
目前,在Java中应用最广泛的非阻塞同步就是CAS,在IA64、X86指令集中通过cmpxchg指令完成CAS功能,在sparc-TSO中由case指令完成,在ARM和PowerPC架构下,需要使用一对Idrex/strex指令完成。
从JDK1.5以后,可以使用CAS操作,该操作由sun.misc.Unsafe类里面的compareAndSwapInt()和compareAndSwapLong()等方法包装提供。通常情况下sun.misc.Unsafe类对于开发者是不可见的,因此,JDK提供了很多CAS包装类简化开发者的使用,例如AtomicInteger等。
开源第三方框架
在电信软件领域,Java开源第三方框架得到了非常广泛的应用,下面我们以NIO框架Netty为例,看下它在电信领域的行业应用总结:
从业务功能需求角度分析,主要使用Netty作为高性能、低时延的TCP通信框架,用于构建分布式消息中间件,例如MQ、分布式服务框架、分布式缓存等。同时基于Netty提供的CodeC能力,开发基于Netty的异步非阻塞应用层协议,例如HttpServer/Client、RestfulServer/Client等。
李林锋
作者简介: 李林锋,2007年毕业于东北大学,2008年进入华为公司从事高性能通信软件的设计和开发工作,有7年NIO设计和开发经验,精通Netty、Mina等NIO框架和平台中间件,现任华为软件平台架构部架构师,《Netty权威指南》作者。目前从事华为下一代中间件和PaaS平台的架构设计工作。新浪微博Nettying微信:Nettying微信公众号:Netty之家
本文选自程序员电子版2015年5月B刊,该期更多文章请查看这里。2000年创刊至今所有文章目录请查看程序员封面秀。欢迎 订阅程序员电子版(含iPad版、Android版、PDF版)。