Java类动态加载机制在铁路互联网售票中的设计与实现
2015-06-28杨立鹏
王 拓,杨立鹏
(1.中国铁道科学研究院 电子计算技术研究所,北京 100081;2.北京经纬信息技术公司,北京 100081)
Java类动态加载机制在铁路互联网售票中的设计与实现
王 拓1,杨立鹏2
(1.中国铁道科学研究院 电子计算技术研究所,北京 100081;2.北京经纬信息技术公司,北京 100081)
铁路互联网售票系统已经成为重要的售票渠道,部分高并发业务(比如余票查询、订单查询、常用联系人查询等)运行在分布式内存数据库集群中。由于业务逻辑维护优化的需要,业务代码需要频繁调整,通过重启服务的方式部署新代码新业务,耗时相当长。为提高运维效率,在研究Java类加载机制的基础上,本文探讨了适用于铁路互联网售票系统的代码动态部署模块的设计和实现过程,进行了测试与分析,并进行了总结。
铁路互联网售票系统;分布式内存数据库集群;Java类动态加载机制;Java虚拟机
铁路互联网售票系统自2011年正式上线以来,经过近些年的发展,已经成长为十分重要的铁路售票渠道。为了使铁路互联网售票系统能够应对超高并发的查询请求,余票查询、票价查询、订单查询等业务部署在分布式内存数据库集群中。集群数据规模达到千万级,甚至亿级。每个集群由数个Java虚拟机(JVM,Java Virtual Machine)组成,一个JVM即一个服务节点(Service Node)[1]。
由于业务逻辑调整维护的需要,部分业务代码需要频繁调整。按照以往的方式,需要停止集群服务、更换程序包、启动集群。余票信息、常用联系人信息、订单信息3项关键业务在单个集群中数据规模、访问量庞大,集群重启耗时较长,亟需实现一个无需重启集群就能动态部署业务代码的系统功能支撑模块,使其满足以下需求:
(1)能够适应铁路互联网售票系统,在线进行升级维护操作,用户无感知。
(2)能够适应分布式的环境,可实现多节点同时部署。
(3)能够完成支持修改方法内容、局部变量、类变量,支持新增、删除类。
本文设计并实现了应用动态部署模块,从整体系统、业务系统、基本目标、具体细节等方面满足上述需求。
1 Java类加载机制的原理
1.1 Java类加载器的体系结构
从Java虚拟机内部的角度来看,主要有3种类加载器。
1.1.1 启动类加载器Bootstrap ClassLoader
启动类加载器由C++语言编写。默认加载〈JAVA_HOME>lib目录中的类库。在启动JVM的时候,如果启动参数中指定了-Xbootclass,启动类加载器也会加载参数指定路径中的类库。
1.1.2 扩展类加载器Extension ClassLoader
扩展类加载器由Java语言编写。默认加载〈JAVA_HOME>libext目录中的类库,或者加载被java.ext.dirs系统变量所指定的路径中的所有类库[2]。
1.1.3 应用程序类加载器Application ClassLoader
应用程序类加载器由Java语言编写。默认加载用户类路径〈CLASSPATH>上所有目录中的类库。如果应用程序中没有自定义过类加载器,那么它就是程序中默认的类加载器。
1.2 Java类在JVM中的生命周期
类的整个生命周期按顺序分如图1所示的7个阶段。
图1 Java类生命周期的7个阶段
这7个阶段各自作用如下:
(1)加载
JVM通过类的全限定名来获取定义此类的二进制字节流,将此字节流所代表的静态存储结构转化为方法区的运行时数据结构,最后在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
(2)验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
(3)准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。在这一阶段中,并不处理实例变量。
(4)解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析阶段主要包括:类和接口的解析、字段解析、类方法解析、接口方法解析。
(5)初始化
类初始化阶段是类加载过程的最后一步,在这个阶段,JVM开始执行类中编写的Java程序代码。
(6)使用
使用阶段,程序在JVM中运行,完成代码逻辑含义。
(7)卸载
卸载阶段,某个类的对象不再被引用,这个类就会结束生命周期,其在方法区内的数据也会被卸载,从而结束类的整个生命周期。
2 设计与实现
2.1 设计原则
设计适用于铁路互联网售票系统的应用动态部署模块,需要考虑两个方面的因素。
(1)铁路互联网售票系统的特性。
铁路互联网售票系统建立在既有的铁路客票系统之上,整个系统庞大、复杂,对可靠性和稳定性要求极高。
(2)分布式内存数据库集群的构成
铁路互联网售票系统的余票查询、票价查询、常用联系人查询、订单查询等业务运行在分布式内存数据库集群中,集群中既做业务数据的存储,也做业务逻辑的执行。
考虑到以上业务和系统层面的因素,在设计适用于铁路互联网售票系统的应用动态部署模块时,应按照如下原则:
(1)按照C/S模型设计,客户端负责读取即将部署的jar包,并发送请求到服务端;服务端执行动态部署操作。
(2)多节点的分布式系统中,所有服务节点尽可能同时完成动态部署,且不能存在遗漏的节点。
(3)执行动态部署操作时,按照先清理旧程序,再加载新程序的步骤进行。
(4)动态部署操作支持业务逻辑以及Listener等代码。
(5)程序动态部署过程的耗时尽可能短,应在毫秒级内完成,避免对生产应用造成较大影响。
(6)需要动态部署的程序的数量不宜过多。大量的程序变化,甚至是架构的变化将有可能导致不可预知的错误。
2.2 实现方法
2.2.1 实现自定义ClassLoader
代码动态部署模块的核心是实现自定义的ClassLoader。
考虑到铁路客票系统中应用分布式内存数据库集群的业务普遍具有较大的数据规模,需要使模块的实现方式尽可能简单可靠,新代码的部署难度尽可能较低。
URLClassLoader类加载器用于从指向 JAR 文件和目录的 URL 的搜索路径加载类和资源。这里假定任何以 '/' 结束的 URL 都是指向目录的。如果不是以该字符结束,则认为该 URL 指向一个将根据需要打开的 JAR 文件。程序中创建的URLClassLoader实例的AccessControlContext线程将在后续加载类和资源时使用。为加载的类默认授予只能访问URLClassLoader创建时指定的URL的权限。
代码动态部署模块中自定义的ClassLoader需要重写父类如下方法:findClass(String name)、close()、loadClass(String name, boolean resolve),这样就实现了寻找指定类、删除指定类、加载指定类,完成了代码替换的功能。
代码动态部署模块把每个单独的类和接口编译成单独的一个.class文件,这些文件对于Java运行环境来说就是一个个可以动态加载的单元,这些文件只在需要使用程序代码时才会被加载。我们可以在不重新编译其它代码的情况下,只编译需要修改的单元,并把修改文件编译后的.class文件放到Java的路径当中,等到下次该Java虚拟机重新激活时,这个逻辑上的Java应用程序就会加载新修改的.class文件,从而更新自己的功能。
2.2.2 通过唯一标识来区分新旧class
类的唯一标识是代码版本准确性的根本。
代码动态部署机制利用“双亲委派机制”来实现,在Java中,一个类用其完全匹配类名作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识。
除此以外,在保证包名、类名一致的前提下,为快速准确地识别类,在代码动态部署模块中,将jar包转换为字节数组,并对此字节数组进行MD5编码,使用MD5码串作为当前这个jar包的唯一标识,当检测到新旧jar的MD5码相同时,则认为代码没有改变,否则用新jar包中的代码覆盖旧代码。
2.2.3 采用“C/S模式”实现热部署功能
“C/S模式”为完成代码替换奠定了高效集群部署的基础。
对于分布式内存数据库集群而言,代码的动态部署操作需要在系统的所有节点上准确地、快速地进行操作,不能有遗漏的节点,也不能有不一致的节点。利用分布式内存数据库的特性,创建当前集群(多个Server服务端)的单一Client客户端,可以有效地完成代码动态部署到集群的工作。
在动态部署过程中,新代码以jar包的形式存放在Client客户端的某一路径下,并由Client客户端发送到各个Server服务端上经过定制的类加载器中。定制的类加载器把class从字节数组恢复成类,实例化并执行。
3 测试与分析
3.1 测试场景说明
3.1.1 测试环境
分布式内存数据库集群服务器数量:2台服务器,X86平台。
每台服务器上运行2个内存数据库节点,集群规模共4个节点。应用的热部署工作在这4个节点上进行。
3.1.2 测试场景
考虑到集群规模、业务数据规模、业务逻辑复杂度等因素,测试工作选取具有代表性的余票查询业务进行。
选取的测试用例是2015年10月1日北京到上海的所有车次。在测试过程中,将记录节点(JVM进程)的pid和运行时间,以证明没有重启节点。
3.2 测试过程与结果分析
3.2.1 制定程序演化过程
根据测试场景,制定如表1所示的程序版本演化过程:
表1 程序版本演化过程表
(1)程序1.0版本为基础版本,即测试环境中正在运行的代码,除去支撑功能模块、服务模块等,业务逻辑模块大概有309个Java类型;
(2)程序1.1版本比上一版本减少一个类型;
(3)程序1.2版本比上一版本增加一个类型,恢复至原状;
(4)程序1.3版本修改了方法体;
(5)程序1.4版本修改了类结构;
(6)程序1.5版本同时修改了类结构和方法体。
按照子逻辑对代码进行分类,如图2所示。
图2 代码片段:余票查询逻辑模块调用顺序
在修改代码、动态部署之前,先检查分布式内存数据库的节点数量,可以看到,在逻辑上,4个节点是一个整体,进程号分别是:16123、16124、10850、10851。
选取16123进程,记录进程信息,如图3所示。
图3 测试过程:原始的16123进程信息
在测试环境中,查询2015年10月1日北京到上海的动车车次(D字头车次),结果如图4所示。
图4 测试过程:用例的原始效果
3.2.2 更新程序版本
随后通过对程序版本的更新,实现余票查询结果的控制。
(1)升级程序至1.1版本
在1.1版本的程序中,删除“downloadBoardSta tionAssignTickets”子逻辑的对应的Java类,动态部署到集群中,随后再次进行查询,结果如图5所示,无法查询余票信息。
图5 测试过程:动态部署程序1.1版本后的效果
(2)升级程序至1.2版本
在1.2版本的程序中,恢复“downloadBoardStati onAssignTickets”子逻辑对应的Java类,代码恢复至原状,执行查询,结果如图6所示。动态部署程序后,可以查询出余票了。尝试预订3张D71次车的一等座,预订成功。
图6 测试过程:动态部署程序1.2版本后的效果
(3)升级程序至1.3版本
在1.3版本的程序中,修改方法体,注释以下逻辑,使其不再生效:
a.downloadBoardStationAssignTickets
b.shareRuleBean
随后进行查询,结果与1.1版本类似,同样无法查询余票数量。
(4)升级程序至1.4版本
在1.4版本的程序中,修改类结构,将类变量logicSequence的类型由LinkedHashSet〈String>改为ArrayList〈String>,随后进行动态部署,结果如图7所示。与1.1和1.3版本类似,同样无法查询余票数量。可见,修改类结构并没有产生影响,程序没有报错。
图7 代码片段:1.4版本
(5)升级程序至1.5版本
在1.5版本的程序中,同时修改了类结构和方法体,将1.4修改的代码恢复原状:
随后动态加载程序,查询余票,结果如图8所示,余票数量能够正常查询,并成功预订车票2张。
图8 测试过程:动态部署程序1.5版本后的效果
3.2.3 结果分析
综合以上测试过程,代码能够被动态加载。从测试开始到结束,进程id没有变化,如图9所示,即没有重启集群服务,测试达到预期的结果。
图9 测试过程:动态部署程序1.5版本后的16123进程信息
4 结束语
在生产环境实践中,自2014年起,代码动态部署模块已经平稳可靠的运行在铁路互联网售票余票查询、票价查询等业务中。应用效果理想,实现了模块需求,提高了程序部署效率,降低了维护成本,增强了应用的灵活性和扩展性,为保障应急和业务更新奠定了稳定的基础。
随着社会经济发展速度的加快,用户总是不断的提出新的业务需求,软件系统更新的速度越来越快,同时对软件灵活性的要求也越来越高,应用动态部署的功能必将得到更加长远的发展,也会更加完善和稳定。
[1]朱建生,周亮瑾,单杏花,王明哲.新一代客票系统总体架构研究[J].铁路技术创新,2012(4).
[2]周逸勋. Java程序动态更新的研究[D]. 上海:上海交通大学,2010.
责任编辑 王 浩
Java class dynamic loading mechanism in Internet Ticketing and Reservation System
WANG Tuo, YANG Lipeng
( 1.Institute of Computing Technologies, China Academy of Railway Sciences, Beijing 100081, China; 2.Beijing Jingwei Information Technology Co., Beijing 100081, China )
Internet Ticketing and Reservation System has been an important channel that sold railway tickets. Some applications were using distributed in-memory database to solve the concurrent access pressure, such as ticket-queryservice, order-query-service, passenger-query-service, etc. Because some business logic code needed to be modif i ed frequently, all the service nodes had to be restarted to deploy new program, while this operation usually took a long time. To improve the eff i ciency of operation and maintenance, based on Java class dynamic loading mechanism, this paper explained the design and implementation of Java class dynamic loading mechanism which was suitable for Internet Ticketing and Reservation System, tested the system module and analyzed the running results of the modules, given a conclusion and some suggestions.
Internet Ticketing and Reservation System; distributed in-memory data base; Java class dynamic loading mechanism; Java virtual machine
U293.22∶TP39
A
1005-8451(2015)11-0030-05
2015-04-10
王 拓,研究实习员;杨立鹏,工程师。