正如在“O/X Mapping的故事續集”中提到的,當你的系統如期上線,一切順利運行,準備接受客戶和領導的讚譽的時候,性能問題便一如既往地如期而至。原因很簡單,你的系統做得太好了,人們是如此的喜歡它,熱切地期望着親自去體驗那些激動人心的新功能,於是人潮蜂擁而入。附近停車場的紛紛爆滿根本無法阻擋他們的熱情,聚攏而來的人羣把滿載的地鐵和公交車都擠得水泄不通,交通開始陷入癱瘓。然而,這些可憐的人們並不知道同樣的事情在他們期待已久的系統中也發生了,網絡和處理器都是100%的負荷,虛擬內存已經消耗殆盡,消息隊列中的等待項目還在瘋狂地增長。管理員開始發號施令,彪悍的門衛堵住了大門,漂亮的接待員把人羣依次分組,耐心地向人們解釋爲什麼他們只能分批進入系統。於是,性能監視器上的曲線慢慢平緩下來,隊列中的消息還是一條條地不斷被處理,每個事務的狀態也在按部就班地發生着變化。監控室裏的IT人員終於鬆了一口氣,沾沾自喜地向他們的經理彙報,看,我們的系統還不錯吧,雖然會變慢一點,這麼高的負荷還能穩定運行。直到一個用戶尖叫起來,我剛剛輸入的數據找不到了;旁邊一個用戶還驚訝地發現,他個人信息頁面上的大頭相竟被換成了他們家小狗的照片。這些平時看起來不可理喻的問題,在高壓力下不可理喻地發生了,這些問題是如此的偶然,而且無法重現,卻總在你不注意的時候突然冒出來。開發和測試人員提出各種猜測和可能的辦法,試圖繞開這個問題,但就像敲鼴鼠的遊戲一樣,棒槌落在一個洞口的時候,鼴鼠又從另一個洞口冒出來。無奈的是,事先精心設計的性能監視器和日誌系統裏面,都無法看到這個問題,每次報告問題的都是那些你曾經的最忠實的用戶。直到有一位耐心的工程師打開一頁3年前寫的並經過3000輪測試的代碼,才發現裏面生成ID的算法在複雜的併發線程環境下會出現ID重複。更可怕的是,包含這個算法的組件,已經在30000個終端上運行。。
很遺憾只能使用這種墨菲式的開局,畢竟自古行文都講究個不破不立,而且要破也要走極端,弄得有點危言聳聽,矯枉過正,否則你的觀點就不會得到足夠的重視。正如性能問題一樣,尤其在項目剛開始的時候,迫於功能和進度上的種種壓力,很多技術取捨和設計考量都會把性能暫時放在一個次要的位置。對一般的業務信息系統來說,很多人常常會犧牲性能來換取更靈活的體系結構以應付需求的頻繁變化,性能通常被認爲是在項目後期或者實施的時候,通過系統軟件的調優和硬件的升級可以解決的問題。這有時的確無可厚非,但對於集成系統,情況可能跟這些特定的業務系統不太一樣。比如PACS主要需要關注大數據量傳輸的性能和可靠性;RIS需要關注工作流和業務狀態控制;收費系統需要關注交易的完整性。而所有這些問題,在集成系統中都可能會碰到。因爲集成系統是爲這些業務系統服務的,所有這些業務系統的需求會或多或少的投射到集成平臺上,最後要求集成平臺做成三頭六臂,無所不能。比如,儘管你的集成平臺是基於異步消息實現的,用戶硬是需要同步操作的時候,你也不得不通過等待一個消息往返來進行等效,類似的機制也可以支持工作流定義,分佈式事務等等。我們再往前一步,當所有這些工作你都可以做到近乎完美,所有業務系統都依賴於你的時候,你就得考慮自己會不會成爲整個系統的性能瓶頸了。相信在很多關於SOA的文章裏面,只要講得足夠深入,而單不是擺弄一些蠱惑大衆的概念,都應該會提到如何來提高ESB數據吞吐能力的一攬子方法。然後就可以對一些很小的技術問題也要搞出一套計劃-分析-實施-反饋的過程模型來確保你不會出什麼差錯,還有一堆的checklist去檢查你在這個小問題上的能力成熟度,最後還美其名曰這是一種充分考慮到各種技術風險的基於墨菲法則的設計。
---
我們這裏不需要這麼複雜,下面一些方法可能不繫統不全面也不夠專業,但慢慢積累,留備今後吹牛之用,還是值得總結一下的。這些方法的前提是系統功能是堅固可靠的,比如代碼裏不存在象上面那樣的線程安全問題(儘管這個問題可能在系統處於某種性能狀態下才會出現)。這些問題應該是用其他的軟件工程方法來防範,而不是下面這些方法所能解決的了。下面的方法所能解決的問題的典型表現就是系統功能正常但是運行得很慢,比如一條消息從一個系統要過很長時間才傳到另外一個系統。
要處理性能問題,當然首先要識別性能瓶頸,這時候日誌以及相關的監視系統就是你最好的朋友。這聽起來好像有點亡羊補牢的味道,爲什麼我們不能在需求或者設計階段先驗地預防性能問題呢。如果你有足夠的基於事實的經驗,這當然可以,最可怕的是在項目的初始階段,根據自己的想象或者過時的經驗,而且沒有經過充分原型試驗,武斷地臆測出性能瓶頸,導致設計偏差,勞力又傷神。與其這樣,還比如在關鍵的地方,主要的業務邏輯判斷和需要大量計算的步驟前後加上寫日誌的代碼,以便你在測試(尤其是單元測試)中儘早發現性能問題,如果單元測試發現不了,這些日誌在後面的系統測試和壓力測試中也會起到很大作用。但正如前面例子裏說的,有些時候日誌裏並不能看出什麼問題。不要難過,不要彷徨,更不能隨意懷疑和猜測,趕緊回去改代碼,在你曾經隨意懷疑和猜測過的地方都寫上足夠多的日誌,必要的話每個邏輯路徑/角落都加上日誌,然後再運行,你就會很快發現問題。因此,爲了讓日誌真正發揮作用,下面的一些實踐可能會有幫助,尤其是排在前面的那些。如果說解決性能問題能有什麼先驗的方法,這些可能是最實在的了。
除了日誌以外,有時你可能還得寫一些程序,比如做一些特殊的工具出來進行測試,才能找出性能瓶頸。作爲一個嚴謹的工程師,這些工作會比臆測花費更多的時間,但卻是非常值得的。當然,真正決定做這些耗時費力的事情之前,先沉住氣,一個人或者幾個人一起,好好讀一讀有嫌疑的代碼,可能會更快地發現問題,然後就可進行試驗驗證加以確認。重視代碼,以及寫代碼的人,永遠是個好習慣,不管你是工程師還是管理者。
---
當然有人會追問,怎麼才能知道哪些代碼有嫌疑,那就看你自己的經驗和對系統的瞭解程度了。也許多年以後,會有一本書叫做,性能瓶頸的幾大罪狀,把常見的性能問題整理成模型供大家膜拜。但在這之前,有一個最簡單的事實,就是隻要有高進低出,就一定會阻塞,這跟堵車和洪水的原理完全一致。比如在集成系統中,一個消息從一個數據庫到另一個數據庫需要經過好幾個隊列,好幾個接口,那你就依次打開每個隊列的數據和每個接口的日誌,從這些記錄中可以查看相鄰兩條記錄的時間差,從而得知其數據傳輸速率,挨個比較這些速率,便可得知數據在哪個地方出現了擁堵。
---
高進低出的事實,很自然地導出解決性能問題的兩類辦法,減低入口速率和提高出口速率。這裏的速率是指單位時間內的數據流量,因此速率的調控又可以從控制總數據量和提高時間利用率兩方面着手。對於後者,又可以用空間換時間,既把並行操作分攤到多個計算單元上進行。這樣說比較抽象,有忽悠人的嫌疑,下面具體化一下,主要針對集成中最常見的異步消息系統,希望以後還是多發現一些類似的具體技巧和方法,而不是一堆抽象的概念和理論:工程師的頭腦往往就是這麼的庸俗。
更多關於性能問題的解決辦法,還可以在程序員雜誌上一篇關於MySpace網站的文章裏找到。不過工程上的很多辦法都是針對特定場景的,只能借鑑並獲得啓發,卻難以複製和推廣。那些能用普適辦法來解決的問題,一般都可以藉助現成的工具了。事實上,最普適的辦法還是組建一個高效的團隊,遇到問題的時候,有人跟你一起討論和分析,以上的一些辦法也是大家的成果,而且可操作性還算比較強。還有一個辦法,也是大家的成果,但不知道什麼時候才能實施了。
---
國內醫院的高峯時段大多在每天上午10點左右,每週視醫院性質不同可能在週一左右,這個時候整個醫療信息系統都在高負荷運轉,能平穩度過這個時段的系統,才能算是真正符合中國國情的系統。如果有性能方面的問題,也常常在這個時間發生,因此大家查問題也只需要關注這段時間的日誌即可。爲什麼我們不能象疏導交通一樣,把病人流量分攤到其他時段去呢。原因很簡單,病人如果下午來就診,可以避過高峯,但卻掛不上號了。衆所周知的看病難,實際上這就是病人和醫生之間的一種高進低出。就跟房地產商捂盤一樣,擁堵的經濟學結果就是價格的提高,這裏的價格漲幅還沒有考慮見不得光的那一部分。於是大家提出小病到社區,大病到中心,社區中心雙向轉診。如何實施呢,很多方法,很多人會得益,不少人也會受損,最後能實施得如何呢,讓我們拭目以待。