付晶晶,熊前兴,赵江滨
(1.武汉理工大学 计算机科学与技术学院,湖北 武汉430063;2.武汉理工大学 能源与动力工程学院,湖北 武汉430074)
作为智能航道子系统之一的数字机务系统,能够对机务设备实现智能化管理,是智能航道的重要组成部分,能够对船舶的浮态、航向航迹、主机、齿轮箱、发电机组、主配电板等设备的运行数据进行远程监测,其核心功能是实现船岸数据实时通信。系统能够接收从船上传来的实时数据,能将动态数据实时显示到系统中,工作人员通过计算机就能看到船舶的实时动态信息,进而实现对船舶的远程控制。系统中数据来源有两种,一种是船舶通过传感器发送来的实时动态数据,如船舶的实时航行方向、经纬度等;另一种是静态基础数据,如船舶的名称、所属机构及编码、航行的经纬度、机构的名称及编码等。该系统采用B/S模型,应用当前比较流行的Jsp +Servlet 技术,服务器采用的是Tomcat 服务器。采用这一模型固然方便,但是如此大量数据的频繁访问必然造成网络拥塞和服务器超载,导致客户访问延迟增大。为了系统能够快速响应客户需求,一个快速有效的解决方案就是在网络传输上利用缓存技术使得Web 服务数据流能就近访问。经过测试,该缓存策略能极大提高服务器端的响应速度[1]。
通常情况下,数据是保存在数据库或者硬盘文件中的,每次操作数据时都需要从数据库或者硬盘上去获取,速度很慢,会造成性能问题[2],如果先将数据库或者硬盘文件中将会被频繁使用到的数据或者资源缓存到缓存区中,当应用程序需要这些数据时,直接从缓存区中提取,就可以减少系统开销。数据缓存的目的是减少网络中冗余数据的重复传输,使之最小化,从而提高数据的读取速度。因为服务器与应用客户端之间存在着流量的瓶颈,所以频繁读取大容量数据时,使用缓存来直接为客户端服务,就不必重复从数据库中读出,可以减少Web 服务器端与数据库的数据交互,减少数据库的访问量,从而大大提高程序的性能[3]。Web 内容可以缓存在客户端、代理服务器以及服务器端。
一个简单的数据缓存解决方法是把这些数据缓存到内存里,每次操作时,先到内存里面找,如果有就直接使用,如果没有就获取它,并设置到缓存中,下一次访问时就直接从内存中获取,从而节省大量的时间。当然,缓存是一种典型的空间换时间的方案[4]。在Java 中最常见的一种实现缓存的方式就是使用静态Map[5]。
根据系统的特性以及可操作性,该系统将数据缓存在Web 服务器端,以内存来换取速度(在数据容量不是特别大时是适合的)[6],数字机务管理系统缓存设计思路如图1 所示。
该系统中缓存实现的基本步骤是:
图1 数据缓存设计思路
(1)在程序启动时,直接将频繁使用的基础信息从数据库读到服务器内存中。
(2)当用户请求相应数据时,服务器直接从这个内存中读取数据来响应用户,而不是从数据库中去读取。
(3)当用户更改了基础信息后,直接将数据缓存的数据全部更新,以保证数据的一致性。
对于静态基础数据,主要是机构信息和船舶信息,需要在系统每个页面左侧导航以树形结构显示出来,如图2 所示。每当页面跳转并刷新时,由于机构和船舶信息量比较大,为了减少数据库的访问量并提高效率,并不希望树形导航重新从数据库里面读取数据来显示,于是设计了一种缓存策略,每当程序启动时,将船舶和机构信息全部读到内存中,页面刷新时,可以读取这个内存中的数据来显示,以内存换取速度。
图2 系统页面左侧数据导航界面图
具体实现过程如下:
首先设计了一个单例模式的类,即数据中心,它保存缓存的数据,在应用程序与数据库之间,实现数据的缓存。设计单例模式的目的在于防止创建多个对象,每次读取数据在一个对象中读取,保证数据的同步性,同时还减少了系统因新建多个对象而产生的额外开销[7]。根据数据模型,各设计了一个包含所有信息列表的静态Map。机构数据结构比较复杂,一个机构下会包含若干机构,最多达到4 级,机构数据列表如下:
船舶数据列表如下:
船舶实时动态信息列表如下:
这3 个Map 对象是保存数据的中心,在系统启动时将其实例化,实例化对象时会给Map 赋值,即从数据库中读取数据给其赋值,dataShipList数据不是从数据库中读取,由船舶动态返回,通过接口去获取。
其次,设置了一个监听器Listener,监听Tomcat 的启动,Tomcat 启动时直接实例化这个对象,并将数据读到内存中。
在web.xml 文件中配置监听器:
监听器的实现如下:
在Tomcat 启动后,通过监听器启动了一个线程,在这个myThread 线程中,实例化了数据中心对象,同时对于保存船舶实时信息的dataShip-List,其数据来源是通过接口从其他系统读取来的,以Json 格式文件保存,需要解析这个Json 来给dataShipList 赋值,由于数据是不断更新的,因此必须每隔一个时间间隔通过接口去获取Json数据。其主要代码如下:
这样数据中心的数据全部都读到内存中了,当客户端请求数据时,服务器不用从数据库读取而是从这个缓存里读取,缓存的数据是个Map 列表,返回数据时将其转换成Json 格式的数据。使用Json 格式传输数据不仅传输效率要比Map 对象高,而且易于Jquery 解析[8]。该系统采用的是Fastjson 库,它将Map 对象转换成Json 数据的速度提升到极致,超过所有Json 库。在请求的Servlet 中的代码如下:
动态的船舶信息dataShipList 是在一些特定页面需要时才返回,而机构和船舶信息是每个页面都需要的,用户每次刷新页面都会重新请求。
从以上过程可以看出,建立数据缓存对于数据的访问完全没有问题,而对于数据的增删改,需要保持数据库与这个数据中心类的数据一致性。
当用户增加、删除、更改一个船舶或者机构信息时,首先要更新数据库,而对于这个数据缓存中心,有两种方案来更新其数据,第一种是在用户进行增删改操作时,不仅对数据库进行相应的操作,也要对这个数据中心的对象进行操作,如图3 所示;第二种是只进行数据库的操作,数据中心类的数据重新从数据库中读取,从而达到缓存中心里的数据与数据库数据的一致性[9],如图4 所示。两种方式各有优缺点。第一种方式在用户进行增删改操作后,直接操作数据中心对象,这种方式不用重新从数据库中查询数据,而且有针对性地对对象更改,只需更改数据变化的地方,这种方式无疑是最好的,但是航道系统Map 对象的数据结构是非常复杂的,导致操作非常复杂,Map 对象最多可达到4 级结构,如果修改了一个机构,不仅要判断它的下级是否有船舶和机构,还要判断上级,如果有机构还要判断机构的上下级是否有机构或者船舶,有就进行增加、修改或者删除的操作,因为级联操作很复杂,这种方式不适合,它一般适合数据结构比较简单的系统。第二种方式是重新刷新数据中心的数据,这种方式没有操作对象的复杂,实现方式很简单,但是修改了一处数据要全部进行刷新,由于系统对数据修改量很少,并发要求不是很大,因此这种方式是最好的选择。添加一个船舶信息后,更新缓存数据,代码如下:
DBcon.sqlnoquery(sql);//添加船舶,先更新到数据库
RTModelManager.getInstance().initRTModels();
更改数据以后,调用RTModelManager 单例模式类的initRTModels()方法,重新从数据库中读出数据,将缓存数据全部更新,实现缓存数据与数据库数据的一致性,页面刷新时,即可得到更改后的最新数据[10]。
图3 保持数据一致性方法一
图4 保持数据一致性方法二
Java 的缓存还有很多实现方式,现在有很多专业的缓存框架,如OSCache、Ehcache、memcached 等,每种方式有自己的优缺点,只有找到适合自己系统的方式,才能提高系统的访问速度和吞吐量。该系统使用的这种缓存方式属于单机缓存方案,读写访问在所有缓存策略中的性能最高,代价最小,在数据量不大且在并发性能要求不是很高的情况下是非常合适的。经过测试,把页面中需要频繁访问的数据都缓存起来,定时进行更新,大大提升了系统的性能,减少了数据库的访问量。
[1]周京晖. 数据缓存按需同步的设计与应用[J]. 软件,2013,34(5):6 -11.
[2]董黎刚.分布式系统中的调度与缓存技术[M]. 杭州:浙江工商大学出版社,2010:20 -70.
[3]顾荣庆,杨开杰,徐汀荣.分布式数据缓存技术研究[J].计算机应用与软件,2011,28(6):202 -204.
[4]倪高鹏. 基于Memcached 的缓存系统设计与实现[D].大连:大连理工大学图书馆,2012.
[5]郑钧.Web 应用系统架构:缓存架构策略[EB/OL].[2014 - 01 - 24]. http://zhengjunwei2007. blog.163.com/blog/static/.
[6]肖红凤.基于数据中心的数据访问服务模型研究[D].大庆市:东北石油大学图书馆,2012.
[7]丁鲲,严浩,刁兴春.分布式数据库数据同步技术研究[J].海军工程大学学报,2004,28(5):100 -104.
[8]陈怀亮. 分布式数据缓存技术的研究与应用[D].大连:大连理工大学图书馆,2011.
[9]刘清.高性能分布式数据缓存系统的研究与实现[D].南京:南京邮电大学图书馆,2011.
[10]李栋.Pushlet 和数据缓存在船舶动态管理系统应用的研究[D].大连:大连海事大学图书馆,2009.