系統最佳化例項一則

suke04發表於2017-06-16
2016年初剛剛從CSC辭職回老部門沒多久,就聽說了部門裡有個系統有些問題,最主要的就是反應在系統效能上。然後沒多久,該系統的兩任Tech lead都跳槽了。再然後這個有問題的系統也劃到我名義下來兼管了。之後就經歷了一段黑暗時期,每兩三天系統不是崩潰就是必須重新啟動,各種各樣的問題,三天兩頭資料庫鎖死,記憶體溢位。最後,上頭終於同意撥了一筆專項基金用於該系統的最佳化。如此,最重要的錢解決了,那麼專案也就可以開始進行了。

為了便於理解,系統的結構還是需要略微簡單的介紹一下的。
1. IBM Webseal 
負責負載均衡兩條不同資料中心的伺服器和粘性會話。
2. IBM WebSphere Application Server: 
部署在不同的資料中心,每個資料中心有兩個邏輯伺服器,一個負責邏輯顯示層,一個負責業務邏輯層。然後每一個邏輯伺服器下面有兩個例項。 這樣總共構成了兩條線的邏輯伺服器,每條線有4個例項。
3. IBM DB2 
作為資料庫。
4. 
整個程式是建立在Java EE上面的。介面是用部門內部開發的WComponent(GitHub上可以找到)來做的。邏輯層混合了Spring + EJB + MDB,持久層使用的是Hibernate+ 動態SQL +資料庫觸發器。

當閱讀了系統的框架文件後,並且做了系統的壓力測試後,發現有五個方面存在一些效能上的問題。
1. 
系統結構
2. 
資料庫
3. 
中介軟體設定
4. 
系統程式
5. 
業務要求的不合理性

基本上這個列表裡面幾乎包括了這個系統的所有的東西。這裡我來簡單介紹一下某些特定的問題和一些解決方案:

1. 
系統結構

大概有8個外部系統與這個有問題的系統透過企業服務匯流排(Enterprise Service Bus or ESB)JMS進行資料交換。JMS是以soap資訊標準來實現的。其中有兩個系統是整個部門的核心繫統。系統A是一個IBM主機系統,已經有不少的年頭了。系統A透過三種方式來傳輸JMS資料給問題系統:程式本身,服務轉換,以及資料庫觸發器。系統A大概每秒鐘傳輸25JMS到問題系統。系統B是一個客戶記錄系統大概每秒鐘產生15JMS傳輸到問題系統。透過資料分析發現以下問題:
·        由於系統A/B的設計問題,大量重複JMS被髮送到問題系統進行處理。
·        由於設計原因,系統A/B更新一條主記錄會產生多個JMS對應同一條主記錄下面的多個子記錄,並且幾乎在同一時間傳送出去。由於這些JMS屬於同一個主記錄(KEY),當問題系統多個例項下的MDB接受到JMS,需要更新資料的時候經常會遇到該條資料被鎖死的情況。導致後面的JMS必須等資料庫更新完成後才能繼續處理。
由於修改系統A/B的可能性微乎其微(從系統複雜系,改動費用,以及修改系統A/B對其他系統的影響)。第一個問題我們從ESB入手,透過資料分析,將重複的資訊直接在ESB就過濾掉了。第二個問題的解決方案是透過兩個步驟修改問題系統。第一步,根據主鍵值將JMS分配到特定的節點,保證同一主鍵值的JMS被同一個WebSphere伺服器的同一個節點處理。第二步,將即時處理系統改成為批處理系統,批處理的間隔時間根據需求設定的比較短一點。綜合第一步和第二部,一方面減少了大量的資料庫讀寫操作,另一方面也可以保證收到的JMS可以根據生成的時間順序來進行處理。而且,資料庫資料被鎖死的情況也不會再發生了。

2. 資料庫

對於資料庫最佳化我想有一定經驗的開發人員都會有一點了解了,這裡我們就不講那些最基本的東西了。當然有一些比較複雜最佳化方案也只有專業的DBA能夠全面的瞭解,而我有幸的遇到了一個非常不錯的DBA。問題系統有很多系統生成的SQL以及開發人員特意撰寫的SQL,我們這裡來分類說一下。

·        儲存框架。問題系統使用hibernate作為儲存框架。在某一個hibernate mapping中使用了union-subclass 的策略,也就是對於每個具體類,產生一個表。本身這個策略是沒有什麼問題的,可是當用到特定的情況下,問題就來了。第一,繼承的具體類別比較多。第二,當每個具體類有幾百萬條記錄時,因為自動生成的SQL會使用 A union B union C union D … 導致整個系統的讀取變得奇慢無比。在專案剛剛開始的時候因為繼承具體類比較少,而且資料庫記錄也比較少,在兩三個具體類,每個類上萬條至上幾十萬條資料的時候系統效能沒有什麼影響。但是隨著具體類的增加,資料記錄的增加,問題系統的效能慢慢的出現了問題。為了儘量減少系統資料庫的改變,最後使用了joined-subclass來解決了這效能問題。透過使用Joined-subclass,生成的SQL採用了join而不是union,執行效率大大提高。(圖中B在最佳化的時候被取消了,因為B的定義不明確,而A可以直接作為B1/B2/B3的超類。)


·        SQL最佳化。SQL最佳化可以完全寫一本書了,在這裡我只是略微帶一下了。基本上來說,問題系統的很多SQL沒有考慮到整個表掃描的耗費。原先資料表上萬上十萬的資料全表掃描可能沒有什麼感覺,但是當資料表有了幾百萬甚至上千萬條記錄時,一定要避免全表掃描。我們做了一些統計,特別是當有很多資料的幾個表結合在一起的時候,當有全表掃描的時候,同樣效果的SQL執行速度要比不需要掃描的SQL慢成百上千倍。另外,另外一個經驗教訓就是千萬不要將太多的商業邏輯封裝到SQL裡面,而問題系統正式犯了這一個錯誤。問題系統有些動態生成的SQL近四五百行,複雜程度極高,導致幾乎整個開發小組沒有人願意最佳化這些SQL。這時候幸好我前面所提的DBA給了整個開發小組極大的幫助。

3. 
中介軟體設定

問題系統使用IBM WebSphere Application Server8.5作為中介軟體。在問題系統長時間執行之後,我們發現資料庫連線雖然回到了資料庫連線池,但是當系統嘗試獲得一個資料庫連結的時候,WAS並沒有可用的資料庫連結提供給系統。剛開始的時候開發小組以為在系統的某處可能存在異常處理不到位導致資料庫連線沒有回到連線池。開發組用了很長的時間做了系統原始碼分析,可是沒有發現任何可疑的異常處理而導致資料庫連線丟失。最後透過WAS的資料庫分析模組發現資料庫連線是被返回到連線池中了,但是該連線處於不可用狀態。最後發現罪魁禍首是WASStaleConnectionException。而在WAS的資料庫設定裡面,系統管理員將資料庫連線池清除政策設定成FailingConnectionOnly,而不是整個連線池。最後透過將連線池清除策略設定為整個池後,我們解決了這個問題。

4. 
系統程式

問題系統本身有一個批處理功能用來處理大量資料,這個批處理功能在一個全域性事務(Global transaction)下面。這個全域性事務時間已經從預設的120秒提升到了300秒,但是有時還是有超時現象發生。等仔細研究了系統程式以後,發現這個批處理功能在某一時間只在某一節點上執行,而其他另外三個節點是空閒的。透過修改系統程式,把這個批處理功能所需要處理的資料均勻分佈到四個節點上同時進行操作後,該批處理功能速度提升近四倍。

5. 
業務要求的不合理性

問題系統有一個最嚴重的問題就是應許終端使用者建立自己的批處理命令,並且應許終端使用者設定自己想要執行該批處理命令的時間。透過資料分析發現,兩年半之內終端使用者總共建立了7000多個批處理命令,而大概有3000多個都是設定執行在早上700點。然後大概有2000多個是在凌晨000,剩下的2000個左右的批處理命令比較均勻的分佈在其他不同的22個時段。當技術小組檢視當年的使用者需求文件的時候,有一條使用者需求就是“使用者可以自己設定批處理命令的執行時間”。這個使用者要求本身沒有什麼問題如果這個是一個個人使用的單機系統,但是當作為一個多使用者系統,系統的資源是共享的,這條業務需要本身需要一個約束。透過與使用者代表的溝通,最後根據系統效能,這個約束條件被加入了開發文件,限定了在每個時段最多有1000個批處理命令。總共全天可以有24000個批處理命令可以執行。

總結

當我們做系統最佳化的時候,我們不能只考慮到系統本身,必須從多方面,多角度來看。當我們透過壓力測試知道了某一個系統瓶頸時候,我們必須分析這個瓶頸是如何造成的。有很多時候這個系統瓶頸並不是由於系統自身導致的,而是由於不合理/不清楚的需求,或者系統互動的設計問題,系統整合的架構問題導致的。沒有從這些方面著手,系統內部再怎麼最佳化也是有侷限性的。

另外一點,在最佳化系統的過程中,開發人員往往會發現當一個瓶頸被最佳化好了以後,另一個瓶頸又出現了。這是很正常的一個過程。最佳化系統就像是一段旅程,每旅遊完一個景點後就會有下一個景點在前面等著你。何時這段旅程能夠完成就看最佳化後的系統是否達到了非功能性要求的標準。

作者華傑, 從事IT工作15年,做過程式設計師,首席軟體工程師,架構師,IT技術顧問,現為澳大利亞移民和邊境保護局Tech lead.
LinkedIn:
個人電子郵件:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/402792/viewspace-2140866/,如需轉載,請註明出處,否則將追究法律責任。

相關文章