付晶晶,熊前興,趙江濱
(1.武漢理工大學(xué) 計(jì)算機(jī)科學(xué)與技術(shù)學(xué)院,湖北 武漢430063;2.武漢理工大學(xué) 能源與動(dòng)力工程學(xué)院,湖北 武漢430074)
作為智能航道子系統(tǒng)之一的數(shù)字機(jī)務(wù)系統(tǒng),能夠?qū)C(jī)務(wù)設(shè)備實(shí)現(xiàn)智能化管理,是智能航道的重要組成部分,能夠?qū)Υ暗母B(tài)、航向航跡、主機(jī)、齒輪箱、發(fā)電機(jī)組、主配電板等設(shè)備的運(yùn)行數(shù)據(jù)進(jìn)行遠(yuǎn)程監(jiān)測(cè),其核心功能是實(shí)現(xiàn)船岸數(shù)據(jù)實(shí)時(shí)通信。系統(tǒng)能夠接收從船上傳來(lái)的實(shí)時(shí)數(shù)據(jù),能將動(dòng)態(tài)數(shù)據(jù)實(shí)時(shí)顯示到系統(tǒng)中,工作人員通過(guò)計(jì)算機(jī)就能看到船舶的實(shí)時(shí)動(dòng)態(tài)信息,進(jìn)而實(shí)現(xiàn)對(duì)船舶的遠(yuǎn)程控制。系統(tǒng)中數(shù)據(jù)來(lái)源有兩種,一種是船舶通過(guò)傳感器發(fā)送來(lái)的實(shí)時(shí)動(dòng)態(tài)數(shù)據(jù),如船舶的實(shí)時(shí)航行方向、經(jīng)緯度等;另一種是靜態(tài)基礎(chǔ)數(shù)據(jù),如船舶的名稱、所屬機(jī)構(gòu)及編碼、航行的經(jīng)緯度、機(jī)構(gòu)的名稱及編碼等。該系統(tǒng)采用B/S模型,應(yīng)用當(dāng)前比較流行的Jsp +Servlet 技術(shù),服務(wù)器采用的是Tomcat 服務(wù)器。采用這一模型固然方便,但是如此大量數(shù)據(jù)的頻繁訪問(wèn)必然造成網(wǎng)絡(luò)擁塞和服務(wù)器超載,導(dǎo)致客戶訪問(wèn)延遲增大。為了系統(tǒng)能夠快速響應(yīng)客戶需求,一個(gè)快速有效的解決方案就是在網(wǎng)絡(luò)傳輸上利用緩存技術(shù)使得Web 服務(wù)數(shù)據(jù)流能就近訪問(wèn)。經(jīng)過(guò)測(cè)試,該緩存策略能極大提高服務(wù)器端的響應(yīng)速度[1]。
通常情況下,數(shù)據(jù)是保存在數(shù)據(jù)庫(kù)或者硬盤文件中的,每次操作數(shù)據(jù)時(shí)都需要從數(shù)據(jù)庫(kù)或者硬盤上去獲取,速度很慢,會(huì)造成性能問(wèn)題[2],如果先將數(shù)據(jù)庫(kù)或者硬盤文件中將會(huì)被頻繁使用到的數(shù)據(jù)或者資源緩存到緩存區(qū)中,當(dāng)應(yīng)用程序需要這些數(shù)據(jù)時(shí),直接從緩存區(qū)中提取,就可以減少系統(tǒng)開(kāi)銷。數(shù)據(jù)緩存的目的是減少網(wǎng)絡(luò)中冗余數(shù)據(jù)的重復(fù)傳輸,使之最小化,從而提高數(shù)據(jù)的讀取速度。因?yàn)榉?wù)器與應(yīng)用客戶端之間存在著流量的瓶頸,所以頻繁讀取大容量數(shù)據(jù)時(shí),使用緩存來(lái)直接為客戶端服務(wù),就不必重復(fù)從數(shù)據(jù)庫(kù)中讀出,可以減少Web 服務(wù)器端與數(shù)據(jù)庫(kù)的數(shù)據(jù)交互,減少數(shù)據(jù)庫(kù)的訪問(wèn)量,從而大大提高程序的性能[3]。Web 內(nèi)容可以緩存在客戶端、代理服務(wù)器以及服務(wù)器端。
一個(gè)簡(jiǎn)單的數(shù)據(jù)緩存解決方法是把這些數(shù)據(jù)緩存到內(nèi)存里,每次操作時(shí),先到內(nèi)存里面找,如果有就直接使用,如果沒(méi)有就獲取它,并設(shè)置到緩存中,下一次訪問(wèn)時(shí)就直接從內(nèi)存中獲取,從而節(jié)省大量的時(shí)間。當(dāng)然,緩存是一種典型的空間換時(shí)間的方案[4]。在Java 中最常見(jiàn)的一種實(shí)現(xiàn)緩存的方式就是使用靜態(tài)Map[5]。
根據(jù)系統(tǒng)的特性以及可操作性,該系統(tǒng)將數(shù)據(jù)緩存在Web 服務(wù)器端,以內(nèi)存來(lái)?yè)Q取速度(在數(shù)據(jù)容量不是特別大時(shí)是適合的)[6],數(shù)字機(jī)務(wù)管理系統(tǒng)緩存設(shè)計(jì)思路如圖1 所示。
該系統(tǒng)中緩存實(shí)現(xiàn)的基本步驟是:
圖1 數(shù)據(jù)緩存設(shè)計(jì)思路
(1)在程序啟動(dòng)時(shí),直接將頻繁使用的基礎(chǔ)信息從數(shù)據(jù)庫(kù)讀到服務(wù)器內(nèi)存中。
(2)當(dāng)用戶請(qǐng)求相應(yīng)數(shù)據(jù)時(shí),服務(wù)器直接從這個(gè)內(nèi)存中讀取數(shù)據(jù)來(lái)響應(yīng)用戶,而不是從數(shù)據(jù)庫(kù)中去讀取。
(3)當(dāng)用戶更改了基礎(chǔ)信息后,直接將數(shù)據(jù)緩存的數(shù)據(jù)全部更新,以保證數(shù)據(jù)的一致性。
對(duì)于靜態(tài)基礎(chǔ)數(shù)據(jù),主要是機(jī)構(gòu)信息和船舶信息,需要在系統(tǒng)每個(gè)頁(yè)面左側(cè)導(dǎo)航以樹(shù)形結(jié)構(gòu)顯示出來(lái),如圖2 所示。每當(dāng)頁(yè)面跳轉(zhuǎn)并刷新時(shí),由于機(jī)構(gòu)和船舶信息量比較大,為了減少數(shù)據(jù)庫(kù)的訪問(wèn)量并提高效率,并不希望樹(shù)形導(dǎo)航重新從數(shù)據(jù)庫(kù)里面讀取數(shù)據(jù)來(lái)顯示,于是設(shè)計(jì)了一種緩存策略,每當(dāng)程序啟動(dòng)時(shí),將船舶和機(jī)構(gòu)信息全部讀到內(nèi)存中,頁(yè)面刷新時(shí),可以讀取這個(gè)內(nèi)存中的數(shù)據(jù)來(lái)顯示,以內(nèi)存換取速度。
圖2 系統(tǒng)頁(yè)面左側(cè)數(shù)據(jù)導(dǎo)航界面圖
具體實(shí)現(xiàn)過(guò)程如下:
首先設(shè)計(jì)了一個(gè)單例模式的類,即數(shù)據(jù)中心,它保存緩存的數(shù)據(jù),在應(yīng)用程序與數(shù)據(jù)庫(kù)之間,實(shí)現(xiàn)數(shù)據(jù)的緩存。設(shè)計(jì)單例模式的目的在于防止創(chuàng)建多個(gè)對(duì)象,每次讀取數(shù)據(jù)在一個(gè)對(duì)象中讀取,保證數(shù)據(jù)的同步性,同時(shí)還減少了系統(tǒng)因新建多個(gè)對(duì)象而產(chǎn)生的額外開(kāi)銷[7]。根據(jù)數(shù)據(jù)模型,各設(shè)計(jì)了一個(gè)包含所有信息列表的靜態(tài)Map。機(jī)構(gòu)數(shù)據(jù)結(jié)構(gòu)比較復(fù)雜,一個(gè)機(jī)構(gòu)下會(huì)包含若干機(jī)構(gòu),最多達(dá)到4 級(jí),機(jī)構(gòu)數(shù)據(jù)列表如下:
船舶數(shù)據(jù)列表如下:
船舶實(shí)時(shí)動(dòng)態(tài)信息列表如下:
這3 個(gè)Map 對(duì)象是保存數(shù)據(jù)的中心,在系統(tǒng)啟動(dòng)時(shí)將其實(shí)例化,實(shí)例化對(duì)象時(shí)會(huì)給Map 賦值,即從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)給其賦值,dataShipList數(shù)據(jù)不是從數(shù)據(jù)庫(kù)中讀取,由船舶動(dòng)態(tài)返回,通過(guò)接口去獲取。
其次,設(shè)置了一個(gè)監(jiān)聽(tīng)器Listener,監(jiān)聽(tīng)Tomcat 的啟動(dòng),Tomcat 啟動(dòng)時(shí)直接實(shí)例化這個(gè)對(duì)象,并將數(shù)據(jù)讀到內(nèi)存中。
在web.xml 文件中配置監(jiān)聽(tīng)器:
監(jiān)聽(tīng)器的實(shí)現(xiàn)如下:
在Tomcat 啟動(dòng)后,通過(guò)監(jiān)聽(tīng)器啟動(dòng)了一個(gè)線程,在這個(gè)myThread 線程中,實(shí)例化了數(shù)據(jù)中心對(duì)象,同時(shí)對(duì)于保存船舶實(shí)時(shí)信息的dataShip-List,其數(shù)據(jù)來(lái)源是通過(guò)接口從其他系統(tǒng)讀取來(lái)的,以Json 格式文件保存,需要解析這個(gè)Json 來(lái)給dataShipList 賦值,由于數(shù)據(jù)是不斷更新的,因此必須每隔一個(gè)時(shí)間間隔通過(guò)接口去獲取Json數(shù)據(jù)。其主要代碼如下:
這樣數(shù)據(jù)中心的數(shù)據(jù)全部都讀到內(nèi)存中了,當(dāng)客戶端請(qǐng)求數(shù)據(jù)時(shí),服務(wù)器不用從數(shù)據(jù)庫(kù)讀取而是從這個(gè)緩存里讀取,緩存的數(shù)據(jù)是個(gè)Map 列表,返回?cái)?shù)據(jù)時(shí)將其轉(zhuǎn)換成Json 格式的數(shù)據(jù)。使用Json 格式傳輸數(shù)據(jù)不僅傳輸效率要比Map 對(duì)象高,而且易于Jquery 解析[8]。該系統(tǒng)采用的是Fastjson 庫(kù),它將Map 對(duì)象轉(zhuǎn)換成Json 數(shù)據(jù)的速度提升到極致,超過(guò)所有Json 庫(kù)。在請(qǐng)求的Servlet 中的代碼如下:
動(dòng)態(tài)的船舶信息dataShipList 是在一些特定頁(yè)面需要時(shí)才返回,而機(jī)構(gòu)和船舶信息是每個(gè)頁(yè)面都需要的,用戶每次刷新頁(yè)面都會(huì)重新請(qǐng)求。
從以上過(guò)程可以看出,建立數(shù)據(jù)緩存對(duì)于數(shù)據(jù)的訪問(wèn)完全沒(méi)有問(wèn)題,而對(duì)于數(shù)據(jù)的增刪改,需要保持?jǐn)?shù)據(jù)庫(kù)與這個(gè)數(shù)據(jù)中心類的數(shù)據(jù)一致性。
當(dāng)用戶增加、刪除、更改一個(gè)船舶或者機(jī)構(gòu)信息時(shí),首先要更新數(shù)據(jù)庫(kù),而對(duì)于這個(gè)數(shù)據(jù)緩存中心,有兩種方案來(lái)更新其數(shù)據(jù),第一種是在用戶進(jìn)行增刪改操作時(shí),不僅對(duì)數(shù)據(jù)庫(kù)進(jìn)行相應(yīng)的操作,也要對(duì)這個(gè)數(shù)據(jù)中心的對(duì)象進(jìn)行操作,如圖3 所示;第二種是只進(jìn)行數(shù)據(jù)庫(kù)的操作,數(shù)據(jù)中心類的數(shù)據(jù)重新從數(shù)據(jù)庫(kù)中讀取,從而達(dá)到緩存中心里的數(shù)據(jù)與數(shù)據(jù)庫(kù)數(shù)據(jù)的一致性[9],如圖4 所示。兩種方式各有優(yōu)缺點(diǎn)。第一種方式在用戶進(jìn)行增刪改操作后,直接操作數(shù)據(jù)中心對(duì)象,這種方式不用重新從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù),而且有針對(duì)性地對(duì)對(duì)象更改,只需更改數(shù)據(jù)變化的地方,這種方式無(wú)疑是最好的,但是航道系統(tǒng)Map 對(duì)象的數(shù)據(jù)結(jié)構(gòu)是非常復(fù)雜的,導(dǎo)致操作非常復(fù)雜,Map 對(duì)象最多可達(dá)到4 級(jí)結(jié)構(gòu),如果修改了一個(gè)機(jī)構(gòu),不僅要判斷它的下級(jí)是否有船舶和機(jī)構(gòu),還要判斷上級(jí),如果有機(jī)構(gòu)還要判斷機(jī)構(gòu)的上下級(jí)是否有機(jī)構(gòu)或者船舶,有就進(jìn)行增加、修改或者刪除的操作,因?yàn)榧?jí)聯(lián)操作很復(fù)雜,這種方式不適合,它一般適合數(shù)據(jù)結(jié)構(gòu)比較簡(jiǎn)單的系統(tǒng)。第二種方式是重新刷新數(shù)據(jù)中心的數(shù)據(jù),這種方式?jīng)]有操作對(duì)象的復(fù)雜,實(shí)現(xiàn)方式很簡(jiǎn)單,但是修改了一處數(shù)據(jù)要全部進(jìn)行刷新,由于系統(tǒng)對(duì)數(shù)據(jù)修改量很少,并發(fā)要求不是很大,因此這種方式是最好的選擇。添加一個(gè)船舶信息后,更新緩存數(shù)據(jù),代碼如下:
DBcon.sqlnoquery(sql);//添加船舶,先更新到數(shù)據(jù)庫(kù)
RTModelManager.getInstance().initRTModels();
更改數(shù)據(jù)以后,調(diào)用RTModelManager 單例模式類的initRTModels()方法,重新從數(shù)據(jù)庫(kù)中讀出數(shù)據(jù),將緩存數(shù)據(jù)全部更新,實(shí)現(xiàn)緩存數(shù)據(jù)與數(shù)據(jù)庫(kù)數(shù)據(jù)的一致性,頁(yè)面刷新時(shí),即可得到更改后的最新數(shù)據(jù)[10]。
圖3 保持?jǐn)?shù)據(jù)一致性方法一
圖4 保持?jǐn)?shù)據(jù)一致性方法二
Java 的緩存還有很多實(shí)現(xiàn)方式,現(xiàn)在有很多專業(yè)的緩存框架,如OSCache、Ehcache、memcached 等,每種方式有自己的優(yōu)缺點(diǎn),只有找到適合自己系統(tǒng)的方式,才能提高系統(tǒng)的訪問(wèn)速度和吞吐量。該系統(tǒng)使用的這種緩存方式屬于單機(jī)緩存方案,讀寫訪問(wèn)在所有緩存策略中的性能最高,代價(jià)最小,在數(shù)據(jù)量不大且在并發(fā)性能要求不是很高的情況下是非常合適的。經(jīng)過(guò)測(cè)試,把頁(yè)面中需要頻繁訪問(wèn)的數(shù)據(jù)都緩存起來(lái),定時(shí)進(jìn)行更新,大大提升了系統(tǒng)的性能,減少了數(shù)據(jù)庫(kù)的訪問(wèn)量。
[1]周京暉. 數(shù)據(jù)緩存按需同步的設(shè)計(jì)與應(yīng)用[J]. 軟件,2013,34(5):6 -11.
[2]董黎剛.分布式系統(tǒng)中的調(diào)度與緩存技術(shù)[M]. 杭州:浙江工商大學(xué)出版社,2010:20 -70.
[3]顧榮慶,楊開(kāi)杰,徐汀榮.分布式數(shù)據(jù)緩存技術(shù)研究[J].計(jì)算機(jī)應(yīng)用與軟件,2011,28(6):202 -204.
[4]倪高鵬. 基于Memcached 的緩存系統(tǒng)設(shè)計(jì)與實(shí)現(xiàn)[D].大連:大連理工大學(xué)圖書館,2012.
[5]鄭鈞.Web 應(yīng)用系統(tǒng)架構(gòu):緩存架構(gòu)策略[EB/OL].[2014 - 01 - 24]. http://zhengjunwei2007. blog.163.com/blog/static/.
[6]肖紅鳳.基于數(shù)據(jù)中心的數(shù)據(jù)訪問(wèn)服務(wù)模型研究[D].大慶市:東北石油大學(xué)圖書館,2012.
[7]丁鯤,嚴(yán)浩,刁興春.分布式數(shù)據(jù)庫(kù)數(shù)據(jù)同步技術(shù)研究[J].海軍工程大學(xué)學(xué)報(bào),2004,28(5):100 -104.
[8]陳懷亮. 分布式數(shù)據(jù)緩存技術(shù)的研究與應(yīng)用[D].大連:大連理工大學(xué)圖書館,2011.
[9]劉清.高性能分布式數(shù)據(jù)緩存系統(tǒng)的研究與實(shí)現(xiàn)[D].南京:南京郵電大學(xué)圖書館,2011.
[10]李棟.Pushlet 和數(shù)據(jù)緩存在船舶動(dòng)態(tài)管理系統(tǒng)應(yīng)用的研究[D].大連:大連海事大學(xué)圖書館,2009.