林平榮,施曉權,楊俊欽,楊少冬,林哲源
(廣州大學 華軟軟件學院 軟件研究所,廣州 510990)
軟件開發技術的不斷發展以及業務需求的不斷變更,使越來越多的軟件系統成為遺留系統.遺留系統指對新技術或業務需求不再適應,但由于替換和修改成本過高而仍在被使用的計算機系統或應用程序[1].遺留系統大部分為早期開發并投入使用[2],具有如下特征:(1)業務邏輯復雜,維護難;(2)架構陳舊且高度耦合,牽一發而動全身,效率低;(3)開發人員流動性大;(4)系統文檔缺失;(5)業務邏輯與代碼邏輯較為混亂.對于企業或其他組織而言,處理遺留系統面臨選擇,要么拋棄遺留系統開發更適應新軟硬件技術和新業務需求的系統,要么保留繼續運行使用.鑒于新系統開發的人力物力成本較高,且有一定風險,同時遺留系統包含大量數據和業務邏輯信息,是企業相當重要的企業資源[3],完全拋棄會造成資源的丟失,所以盡管遺留系統存在諸多問題,仍處于運行狀態[4].
隨著時間的推移,仍在運行的遺留系統的用戶數以及數據量不斷增加,暴露出來的問題越來越多,尤其是性能和安全問題.經過文獻檢索,發現有大量的關于Web 應用系統性能和安全方面的優化方案被提出.這些方法雖然在某種程度上有一定的優化效果,但大部分并不是在遺留系統背景下所提出,許多方法并不完全適用,即使是針對遺留系統在工程方面的文獻[4–7]也較少涉及安全方面的優化.基于此,本文將在大型遺留系統特定背景下,多角度挖掘遺留系統性能和安全方面的痛點,綜合考慮各方面優化技術,但不贅述窮舉大部分文獻提及的通用方法,最后給出具體優化方案.
遺留系統當時的開發時期并沒有像現在各種開發技術的多樣與規范.前端開發技術主要以表格布局進行排版,以切片進行內容展示,以微軟操作系統自帶的IE 瀏覽器作為瀏覽網頁的載體.隨著人們的需求與技術的發展,優雅的界面、響應的速度、數據的安全愈加獲得人們的重視.
(1)隨著各個新的基于不同引擎的瀏覽器的加入,遺留系統的Web 前端出現了瀏覽器兼容性問題,訪問頁面有排版錯亂、按鈕點擊無反應等現象.究其原因主要在各個廠商的瀏覽器內核對同一段代碼解析不一,導致頁面呈現效果不統一,操作效果無法被解析.為了解決系統在各個瀏覽器的兼容問題,可以選擇優雅降級和漸進增強.嘗試從低版本瀏覽器到高版本瀏覽器的逐步優化的過程,結合漸進增強的優化方式,即能保證遺留系統存在的功能不被破壞又能在新一代的主流瀏覽器中進行操作.
(2)遺留系統的頁面布局皆為表格布局,而現在編寫代碼更多的是講究規范化和語義化,且在非表格化數據顯示的模塊下并不提倡用表格標簽做其他處理,在使用table 標簽布局會使網頁瀏覽的速度變慢,這與table 標簽的渲染有關[8].可以用div 加載方式進行渲染,即讀即加載,同時引入異步加載局部更新技術.局部更新只針對需要重新加載的局部信息,可以有效提升響應速度,提高用戶體驗,服務器壓力也會減少.
(3)遺留系統中的CSS 文件和JavaScript 文件代碼編寫大多以覆蓋為主.無論是HTML 文件還是CSS文件,代碼里都存在著一些被廢棄的標簽和樣式寫法,文件編寫沒有進行一個整理和歸類,導致代碼冗余.采用現代前端開發技術的工程化能對遺留系統的腳本進行優化,有利于代碼的更好維護與管理.前端工程化是以宏觀的角度、更遠的思維看待和開發項目的一種思想,而模塊化和組件化則是工程化的表現形式,如圖1所示.模塊化是將一個功能當做一個模塊進行開發,實現高效復用和分開管理,不僅JavaScript 代碼能實現模塊化,CSS 樣式也可以實現模塊化.常見的JavaScript模塊化方案有AMD、CommonJS、ES6 Module 等,而CSS 模塊化則有less、sass、stylus 等預處理器進行實現.組件化則是將頁面上的可視區域和交互區域分成各個組件,每個組件都是獨立的,每個組件包含模板、樣式和邏輯.不同的頁面根據內容不同,對各個組件進行自由組合.模塊化和組件化能對代碼實現高效復用和降耦管理,這也是現代前端開發技術倡導前端工程化的原因之一.

圖1 模塊化和組件化示意圖
數據庫優化的主要目的就是最大限度地降低數據響應時間和提高數據庫的吞吐量[9].一個應用程序的數據庫性能,通常是指數據的增、刪、改、查耗時性能,而耗時主要是SQL 語句從執行開始到完成所消耗的時間,這個過程主要指標有:數據和索引等讀寫的I/O消耗時間、CPU 消耗時間、總的執行消耗時間.以微軟的SQL Server 數據庫為例,可以通過3 種工具(SQL Server Profiler、查詢窗口、SQL 活動監視器)進行相應的性能查詢分析.
一般情況下可以通過優化SQL 語句、創建索引等手段來進行數據庫優化,然而在排除硬件資源充足、SQL 語句和索引創建合理的情況下,有可能發現性能提升仍然不夠理想,分析深層次原因是數據庫碎片積累過多且沒有定期進行整理.
遺留系統運行年限較長,信息數據、索引數據等都在不斷增加,相應存儲頁數量也不斷增長,數據頁上也容易形成碎片(即部分數據的大小超出了當前頁的剩余空間,導致無法在當前繼續增加數據,只能新開一個頁).在運維的過程中只注重索引創建卻忽視了索引碎片的維護,從而導致性能的直線下降.可以修復表的數據及索引碎片,把相關數據文件重新整理,但也不能過于頻繁,可根據表的讀寫頻率按月、季、年來進行修復.
碎片整理前,如果監測到某條SQL 語句執行過慢,通過查詢其中某個表索引碎片,如圖2所示,發現其中邏輯掃描碎片和區掃描碎片都超過70%,掃描密度只有18%,并且平均頁密度也只有58.94%,這意味著“ROOM”表的數據頁存在很多碎片,其性能會損耗一部分在碎片當中.這個時候單獨查詢表“ROOM”,其邏輯讀取(已經查詢過并且沒有數據變更,數據已被放到內存中)就達到72 次.

圖2 整理前邏輯掃描碎片
在掃描碎片整理后,發現掃描密度變為85.71%,掃描邏輯碎片降低為2.17%,平均頁密度提升到99.25%,并且其邏輯讀取次數降低為48 次,如圖3所示.也就是在碎片較低的時候,其邏輯讀取次數相應的降低,性能損耗降低了,查詢性能就提高了.

圖3 整理后邏輯掃描碎片
早期開發的遺留系統基本以單體形態部署在服務器上.隨著用戶量及數據量的增加,系統性能瓶頸逐漸暴露,用戶體驗越來越差.常規做法就是加大硬件投入,使用性能更好的服務器,但受限于系統采用的陳舊技術,硬件資源使用率不是很高,并不能很好地發揮服務器全部性能.比如一些使用JDK1.4 開發的Web 系統,它并不支持使用泛型,所有的數據對象只能使用Object接收,在對象轉換之間花費了大量的不必要計算,而在多線程只能使用傳統的線程池方式實現.使用JDK1.5以上開發的Web 系統在數據對象轉換的開銷要比前者少得多,性能也就優于前者,但因此升級JDK 版本會導致許多代碼需要重寫,代價太大.
為了應對傳統的單體應用模式無法承擔大量并發業務請求的問題,服務器集群技術應運而生.為了能在服務器集群中合理分配業務,使各個服務器都發揮應有的性能,負載均衡機制及均衡算法成為了關鍵[10].Nginx 作為中間服務器,可以友好的切入到大型遺留系統,實現動態拓展而不須改變系統本身,從而改進系統的性能,增加用戶體驗.遺留系統可以采用Nginx 負載均衡技術,使用ip_hash 算法,將特定用戶的請求分發給特定的服務器,通過動態拓展服務器,增加系統的承載能力,動態拓展示意圖如圖4所示.

圖4 Nginx 動態拓展示意圖
使用此方法可以在很大程度上緩解并發壓力,但是對數據庫服務器的性能也有了更高要求,因此對數據庫也要進行優化,同樣是采用數據庫動態拓展技術,根據使用的數據庫不同可以選擇不同的集群捆綁中間件,比較常用的有阿里B2B 團隊的Cobar、360 團隊的Atlas 等等.
遺留系統陳舊的技術架構很難支撐日益復雜的業務線,無法滿足系統新的性能要求,另外如JDK、Weblogic 等未及時升級換代,導致技術革新寸步難行,只能在原架構基礎上繼續堆砌新業務,逐漸演化成一個大型遺留系統.為了提升性能及日后嵌入更多的新需求,選擇合適的技術架構進行重構,保證架構平滑演進是必須考慮的.此外,還有一種方法就是對架構進行重寫,但很難確保系統的所有業務邏輯都被正確認識和實現,而且成本和風險很高.為了穩定現有業務的正常開展,選擇在維護現有系統的基礎上,同時進行新技術的重構工作是比較好的方式,這樣既保障了業務的迭代需求,又能進行新架構的重構.
針對目前大型遺留系統,架構升級不能一蹴而就,要依據不同功能以及業務邏輯,完成系統級別的拆分,同時對于第三方服務進行解耦,拆分出來獨立部署,并且實現數據庫的主從分離.獨立擁有拆分的DB,避免業務引發連鎖故障問題,系統間通過Hessian 實現RPC通信.
第1 步.將遺留系統拆分出多個業務邏輯相對獨立的子系統,DB 暫不拆分,多套系統繼續共用一個DB,只是根據業務邏輯劃分依賴的表,不同業務邏輯系統不能相互訪問,只能訪問歸屬自己的表,進而保證原系統業務不受影響,同時也保障新拆分的業務系統工作得以繼續進行.第2 步.通過系統層面的拆分后,接下來就是DB 層面的拆分,將各個系統依賴的表獨立拆分,分別放到不同的RDS 數據庫,做到物理層隔離.這兩個步驟充分將大型遺留系統解耦,獨立拆出服務化組件,供其他服務調用.
有了合理的服務化拆分,可輕易演化為微服務架構.微服務可以將單體應用細化為可相互協作、配合的一組小服務,使得服務間開發自由、獨立部署、易于維護[11].微服務架構形式在滿足系統功能之外,對系統的非功能特性也有顯著提升[12].將那些容易引起性能問題的業務拆分獨立微服務組件,微服務都圍著具體業務進行構建,由專人研發和維護,結合架構拆分,實施微服務架構.實施過程中,會產生很多微服務以及子系統,各個系統的配置信息都以明文形式在配置文件中,固定化了各種定時任務的執行規則,難以集中管理,可以選擇Disconf 和Elastic-Job 分別作為分布式配置管理、任務調度.
通過以上步驟實施,系統與微服務組件已非常容易擴展.以服務為中心,全面構建微服務組件,大型遺留系統可以具備高可用、高性能等特性.隨著服務越來越多越復雜,服務鏈路調用必須加以跟蹤,以便快速發現調用過程中需要優化的地方,可以引入美團點評的APM 工具Cat 實現實時監控,與Dubbo 快速整合,通過全局鏈路ID 實現鏈路跟蹤功能.雖然對大型遺留系統進行拆分,但還未做到快速彈性擴展.接下來,拆好的微服務與Docker 容器結合,抵御業務高峰期沖擊,快速自動擴展服務器,低峰時期,可以自動回收服務器.整個微服務架構如圖5所示.

圖5 微服務架構圖
2017年6月1 號《中華人民共和國網絡安全法》正式頒布實施,各大企業、高校對各自現有運作的系統安全問題更加重視.遺留系統使用的技術過于陳舊導致一些安全問題修復難度增加.
以基于JDK1.4 開發的遺留系統為例,系統采用Cookie 技術來維持會話,通過使用js 進行XSS 腳本攻擊,可以輕易地獲取Cookie 信息.如果Cookie 中設置了HttpOnly 屬性,那么通過js 腳本將無法讀取到Cookie 信息,這樣能有效地防止XSS 攻擊,增加Cookie的安全性.但是由于JDK1.4 無法設置HttpOnly 這一屬性,因此在原系統層面無法解決該問題,只能引入中間件攔截用戶請求,進行相關處理后再請求系統,即給系統加多一層防火墻來保護系統的安全.采用Nginx來進行攻擊防護,通過在Nginx 的proxy 模式將客戶端的請求攔截,利用HttpOnly 防止XSS 攻擊獲取Cookie 信息,SameSite 防止 CSRF 攻擊和用戶追蹤,再結合ngx_lua_waf 實現安全web 應用防火墻.通過該模塊可以防止ApacheBench 之類壓力測試工具的攻擊、防止SQL 注入、fuzzing 測試、XSS、SSRF 等Web攻擊,亦可屏蔽常見的掃描黑客工具,屏蔽異常的網絡請求、屏蔽圖片附件類目錄php 執行權限、防止webshell上傳等.
遺留系統采用HTTP 協議開發,HTTP 協議以明文方式發送內容,不提供任何方式的數據加密.如果攻擊者截取了Web 瀏覽器和網站服務器之間的傳輸報文,就可以直接讀取其中的信息,特別是在傳輸賬號密碼這一過程,使得用戶信息存在泄露的可能.采用SSL 加密可以在數據傳輸過程進行加密,讓用戶信息的安全得到保障.通過在Nginx 配置SSL 證書還可以將HTTP請求強制定向為更安全的HTTPS 請求,從而提高系統的安全性.
增加Nginx 中間件可以進一步提高系統安全,但是由于系統本身存在的一些漏洞,Nginx 中間件無法全面保護系統.如遺留系統中到處可見的明文GET、POST請求,每一個參數都暴露在客戶端,用戶只需要拼接特定的URL 就可以進行越權操作.拼接URL 越權訪問有兩種方式,分別為橫向越權和縱向越權.為了防止攻擊者嘗試訪問相同權限用戶資源的橫向越權現象發生,建立用戶和目標資源的綁定關系.只有通過綁定關系的用戶才可以訪問或者在請求參數進行關鍵參數間接映射,避免直接訪問;對于低權限越級訪問高權限資源的縱向越權,采用RBAC 訪問控制機制,定義不同角色的訪問權限,每個用戶都有特定的權限,當用戶在執行某個動作或者進行某些行為時,通過角色判定是否允許進行操作.此外,將請求路徑和參數用MD5 算法按一定的規則進行加密,再返回給客戶端處理,防止通過拼接參數進行越權操作.
廣州大學華軟軟件學院信息管理系統于2004年開發建設,現有招生、教務、科研、財務、學工、人事、后勤七大管理模塊,至今已運行16年,累計源代碼將近60 萬行,涉及數據表400 多張,服務全校15000多名師生.系統開發時間早,架構技術較為陳舊,功能模塊之間耦合度高,屬于典型的大型遺留系統.隨著用戶量和數據量不斷攀升,系統的性能和安全面臨很大的考驗.根據系統暴露的問題,結合本文提出的優化方案對系統進行優化驗證.
前端:從兼容、渲染、規范、響應數據等進行不同層次的優化.優化后系統不僅兼容IE9 及以上版本的瀏覽器,在谷歌瀏覽器、火狐瀏覽器等主流瀏覽器下布局也得以改正且可添加主流瀏覽器支持的樣式效果,同時將JavaScript 代碼更改為通用寫法與短路寫法,在主流瀏覽器下按鈕失效現象也得以解決.在表格布局方面,將table 標簽的代碼進行重構,將語義化的標簽代替用于布局的table 標簽,保留用于展示數據表格的table 標簽,代碼清晰,方便閱讀,而且網頁從渲染到展示內容的速度也得到一定程度的改善.在工程化的思想下進行重構,對代碼整體進行一個規范和梳理,文件不僅體積減小而且后期代碼管理也更加便利,同時對需要頻繁加載數據的頁面采用異步加載進行局部更新,服務器傳輸壓力得以減小且響應數據的效率也得到相應的提高.
數據庫:針對排課、教室資源查詢、考勤登記等響應速度較慢的功能模塊進行分析與優化,找出耗時較高的SQL 語句,然后重建數據庫中涉及相關表的索引并不定期對數據庫進行索引碎片整理,代碼層面則對業務數據的存儲邏輯進行優化.采用JMeter 工具分別對優化前后各模塊的響應時間進行測試與采集,具體如表1所示,可以看出優化之后響應時間明顯縮短.

表1 優化前后響應時間對比
服務器與架構:主要針對系統的選課模塊進行了優化.選課模塊優化前存在較大的性能問題,在并發數3000 左右就會出現異常或導致系統宕機.現通過服務器動態擴展方式對選課模塊進行優化,改變傳統單體應用模式,同時進行微服務化演進.優化之后,用戶并發數3000 時沒有出現異常,逐漸增加用戶數量后系統表現也較為穩定,可以滿足10 000 人同時選課的需求,并且服務器內存、磁盤IO、CPU 得到了充分的利用,達到了系統運行預期效果.
安全:優化之前我們按照廣東省教育廳公布的《關于開展信息系統安全等級保護工作的通知》的工作要求[13],采用安全掃描工具AppScan 對系統進行掃描,同時開展了人工滲透測試,發現系統存在SSRF、反射型XSS 等諸多中、高危漏洞.結合本文提出的優化方案,通過引入Nginx 中間件,進行了HTTPS 的部署升級,解決了通信加密的問題;通過Nginx 過濾,解決了XSS 攻擊、SSRF 漏洞、SQL 注入等問題;通過小跨越的升級框架版本,解決Apache Struts 歷史漏洞問題.圖6和圖7分別是AppScan 在優化之前和之后對系統進行安全掃描的風險圖.

圖6 優化前的風險圖

圖7 優化后的風險圖
由圖7可以看到優化之后已經沒有了中、高危風險,有效防止黑客模仿用戶身份執行不合法行為,證明了安全優化方案的有效性.不足之處在于在修補漏洞的同時衍生了一些低危漏洞,但對于整體系統而言安全方面提升到了更高一個等級.
本文先闡述了大型遺留系統在運行過程中可能存在的性能與安全問題,然后在大型遺留系統特定背景下,多角度挖掘遺留系統性能和安全方面的痛點,分別從前端、數據庫、服務器、系統架構、系統安全五個方面給出具體優化方案,最后通過廣州大學華軟軟件學院信息管理系統進行了優化策略驗證.本文優化方案具有一定的代表性和典型性,可以有效提高遺留系統的性能與安全等級,對于大型遺留系統的優化改造具有一定參考意義.