標籤:

性能測試必知的21件事:認清性能問題

近年來大家都開始關注移動應用性能管理和性能監測,我們找到一位國外資深的開發者對性能的相關理論,希望各位喜歡。

1. 公理化方法

當我在1989年加入 oracle 公司時,解決性能問題(人們通常說的 oracle 調優)是很困難的。 只有少部分人聲稱他們很擅長這個,很多人都去諮詢他們。 當時,我進到 oracle 調優這個領域時,我完全沒準備好。 最近我又開始對 mysql 進行調優,這看起來和我20年前在 oracle 公司做的差不多。

它讓我想起了當我13歲剛接觸代數學時是多麼的困難。 在那個年齡我只能依靠「數學直覺」來解決類似 3x + 4 = 13 這樣的方程。 問題是我們之中大部分人都沒有所謂的「數學直覺」。 我記得當看到這樣的問題: 3x + 4 = 13 求解x,只能採用試錯法偶然發現 x 應該是3。

試錯法給我的感覺雖然能解決一些簡單的方程式,但很慢而且不爽。 一旦等式稍有變化如 3x + 4 = 14,試錯法就不能適應。 那麼該怎麼辦呢?當時我沒有好好思考過,直到15歲時James R. Harkey指引我走上正確的道路。

Harkey 先生教會我使用公里方法來解決代數方程問題。 他給我們展示了一系列的步驟(還給了我很多家庭作業進行練習)。 做作業時除了記錄下這些步驟,還要寫下我們是如何思考的。 這樣我們不僅自己想的很清楚,而且通過一系列可靠的,可重複的步驟來向閱讀我們作業的人證明了我們確實搞明白了。

Harkey 先生看到的我的作業像下面這樣:

3.1x + 4 = 13 待求解方程3.1x + 4 - 4 = 13 - 4 減去相等的值3.1x = 9 加法逆運算,化簡3.1x ∕ 3.1 = 9 ∕ 3.1 除以相等的值x ≈ 2.903 乘法逆運算,化簡求解

這就是 Harkey 先生教導的適用於代數學、幾何學、三角學和微積分的公理化方法。 由一系列符合邏輯的、可證明和審計的小步驟組成。 這是我第一次真正從數學中學到的東西。

自然,當時我沒能認識到其中的價值,但證明作為一種技能對我後來的成功至關重要。 我發現在生活中,知道一件事很重要,但能向別人講清楚(證明)更重要。 沒有好的證明技能,就很難成為一名好的顧問、好的領導甚至好的員工。

我在上世紀90年代中期的目標是為 oracle 性能優化創建一套類似的、嚴格的公理化方法。 後來我將其擴展到了 oracle 之外,建立了一套適用於所有計算機軟體性能優化的公理化方法。 好吧,我發現並非所有人都喜歡這種說法,那我們換一種說法:

我們的目標就是幫助你想清楚如何優化你的軟體系統性能。

2. 什麼是性能?

假如你去 google 下 performance 這個關鍵字,可能會得到5億個鏈接。 其中涉及的內容範圍可能從自行車比賽到可怕的員工審查流程(如今很多公司已經學會了避免這個流程)。 但假如我去 google 下 performance 這個關鍵字,大部分的首頁鏈接都會與這篇文章的主題有關:計算機軟體執行無論何種任務所花費的時間。

任務這個詞是一個很適合的開始。 任務是一個面向業務的工作單元。 任務能夠嵌套:列印發貨單是一個任務,列印一張發貨單(一個子任務)也是一個任務。 當一個用戶說起性能時,他通常指的是系統執行一系列任務所花費的時間。 響應時間是任務的執行時長,用每個任務的時間來度量,像:每點擊秒數。 例如我用 google 搜索關鍵字 performance 的響應時間是 0.24 秒。 這個數據來自我的瀏覽器渲,它渲染完google網頁花費的時間,那麼很明顯,這量化了我對 google 性能的直覺感知。

一些人對另外一個性能指標很感興趣:吞吐量。 吞吐量是在一個特定時間段內完成的任務的計數,例如:每秒點擊數。 通常為一群人提供服務比為個別人提供服務的人更關心吞吐量。 例如,一個獨立會計會更關心日報的響應時間是否會導致今晚需要加班,而會計部的經理更關心系統的是否能支撐所有的會計處理完今天的數據。

3. 響應時間 VS. 吞吐量

通常來講,響應時間和吞吐量是一個倒數關係(響應時間越長吞吐越低),但這並不確切。 實際情況更微妙、複雜一些。

例: 假如,在一些性能基準測試中,你的系統的測量結果是每秒能處理1000個任務,那麼用戶的平均響應時間是多少? 你可能會說平均響應時間等於 1 / 1000 = .001 秒/每任務,但它真不是這樣的。 假如在你的系統內部擁有1000個相同的、獨立的、並行的服務執行通道,每個通道都在等待請求到來並提供服務。 在這種情況下,每個請求其實花費了1秒。 現在我們知道,平均響應時間其實應該在每任務0秒到1秒之間。 但是我們不能僅僅從吞吐量的測量數據中推導出平均響應時間。 (事實上存在數學模型從吞吐量推導出平均響應時間,但這個模型要求更多的輸入參數,而不僅僅是吞吐量) 你必須單獨測量它。

反過來說也是一樣的,你應該能從我上面給出的例子中得到啟發。 下面是一個更有趣的例子。

例: 你的客戶要求一個新任務必須滿足在單核 cpu 的計算機上達到每秒100的吞吐量。 假如這個新任務在客戶的系統上執行一次需要 0.001 秒。 那麼你的程序能夠滿足客戶要求的吞吐量么? 你可能會說,跑一次這個任務只需要千分之一秒,那麼在一秒內完成100次顯然是綽綽有餘的。 恩,是的,你很正確,假如這個任務被很好的串列化了。 例如,你的程序處理100個任務執行請求是在一個循環中,一個接一個的執行,那就是正確的。 但是如果這100個任務到達你的系統是完全隨機的來自100個不同的用戶,那該怎麼辦呢? cpu 調度器和串列資源(oracle 的閂和鎖,內存可寫緩衝區訪問)這些糟糕的實際情況會嚴格限制你的並發吞吐量低於每秒100。 最終,你可能會達到客戶的期望也可能達不到。 你不能僅僅從響應時間推導出吞吐量,你必須單獨測量吞吐量。

所以,響應時間和吞吐量不是那麼簡單的互為倒數關係。 你想要知道這兩個指標,就必須一起測量它們。

那麼響應時間和吞吐量到底哪一個更重要呢? 在一些場景下,說哪一個都是合理的。 但在大多數情況下,兩者都同樣重要。 因為,對系統來說它的業務需求通常是這樣的,在大於99%的情況下響應時間要少於1秒,並且能支持10分鐘內持續不低於1000的吞吐量。

4. 百分比指標

在上一節,我用了「大於99%」這樣的描述來表達對響應時間的期望。 但大部分人可能更習慣於這樣的描述:「平均響應時間少於 r 秒」。 但從經驗的角度,使用百分比方式更好。

例: 假如你對一些每天運行在你的計算機上的任務的響應時間的容忍極限是1秒。 進一步假設表1列出了該任務執行10次的測量值。 這兩個列表的平均響應時間都是1秒。 哪一個你認為更好?

你看到雖然兩個列表擁有同樣的平均響應時間,但本質上差別很大。 ListA 90%的響應時間是低於1秒的,而ListB只有60%的時間是低於1秒的。 從用戶體驗的角度來說,ListB表明會有40%的用戶會感到不滿意,而ListA僅有10%的不滿意率,雖然它們平均響應時間相同。 ListA 90%的響應時間是0.987秒,而ListB 90%的響應時間是1.273秒。 因此使用百分比描述的響應時間比平均響應時間包含更多的信息量。

99.9%的跟蹤貨運單的任務必須在0.5秒內完成。

5. 問題診斷

最近我被邀請去解決的一些性能問題的描述都是些關於響應時間的。 如:「過去只用不到1秒的時間就能完成X任務,但是現在卻需要20秒。」 當然,一些真正的問題隱藏在其他一些問題描述的表象背後,例如:「我們的系統變的很慢,完全沒法用了。」

雖然我經常碰到類似這樣的表述,但並不意味著你應該這樣去描述問題。 首先你得清晰得描述問題本身,才可能把它們弄清楚。 一個好辦法是去詢問,你想要達到得目標狀態是怎樣的呢? 找到一些細節,你可以用量化的方式來表達它們。 例如:執行 X 任務大部分情況下都超過20秒,希望能在95%的情況下小於1秒。

理論上這聽起來很棒,但要是你的用戶根本沒有很具體的可以量化的目標呢? 或者你的用戶根本就不知道怎樣去量化,更糟糕的情況是你的用戶如果還有一些完全不切實際的期望怎麼辦? 你如何知道到底什麼是「可能的」,什麼是「不切實際的」?

好吧,下面我們繼續探討這些問題。

6. 序列圖

序列圖是一種UML(統一建模語言)中定義的圖形種類,用於表達對象間交互的發生順序。 序列圖特別適合用於可視化的表達響應時間。 在圖1中,我們展示了一個由瀏覽器、應用伺服器和資料庫構成的簡單應用系統的序列圖。

假如我們擴展下序列圖的表示,讓請求和響應之間距離表示服務該請求的消耗時長。 在圖2中我展示了一個擴展後的序列圖。

通過圖2,你可以很直觀的看到到底是哪個部分消耗了最多的時間。 你能直觀的感受到整個響應時間在各個部分的構成。 序列圖很好的幫助人們從概念上直觀的理解一個任務如何在系統各個部分之間順序流轉的。 序列圖也能很好的表達並行執行的任務。 序列圖也是一個很棒的工具用於在業務層次分析性能問題。

序列圖是很好的描述性能問題的概念工具,但要把性能問題分析清楚我們還需要其他的。 序列圖的問題是,假設有個任務花費了2468秒才執行完成(大約41分鐘8秒)。 在這41分鐘里,應用伺服器和資料庫大約交互了322968次。

把這個過程畫成序列圖大概就是下面圖3的樣子:

在應用伺服器和資料庫之間有如此之多的箭頭,以至於你完全看不清細節了。 我們可能需要花費數周才能畫完這個圖,但這並不是一個有效的方法。 序列圖雖然很好的概念可視化了任務的執行流和時間流,但要仔細分析清楚響應時間的問題我們還需要別的工具。

7. 性能剖析

對於像上述這種擁有大量調用交互的情況,序列圖不能很好的描述。 我們需要一種更方便的聚合視圖來更容易的理解到底哪個部分消耗了最多的時間。 表2給出了一個性能剖析的例子。 性能剖析是對響應時間的表格化分解,按響應時長倒序排列如下。

例:表2中的性能剖析是很初級的,但它能告訴你top 8 的慢任務佔用了2468秒。 從中你大概可以得到每個函數的響應時長佔比。 也可以從中算出每個函數調用的平均響應時間。

從表2的數據表明,大約70.8%的響應時間消耗在了 DB:Fetch() 這個方法上。 如果你進一步深入方法調用中會發現 App:await_db_netIO() 方法與 DB:Fetch() 的一一對應關係。 於是能知道每個部分消耗了多少時間,通過性能剖析你開始能夠明確的回答像這樣的問題: 「這個任務需要運行多長時間?」從第5節你可以知道,對問題診斷的第一步來說這是一個很重要的問題。

8. 阿姆達爾定律

性能剖析能幫你分析清楚性能問題。 即便基恩·阿姆達爾在1967年沒有告訴我們阿姆達爾定律,你也可以在看到性能剖析表格時自己歸納出來。

阿姆達爾定律指出:

系統中對某一部件採用更快執行方式所能獲得的系統性能改進程度,取決於這種執行方式被使用的頻率,或所佔總執行時間的比例所以如果你嘗試改進的部分只佔總響應間的5%,那麼對總響應時間的提高最多不會超過5%。 這意味著你改進的部分在性能剖析列表中排位越高(假設它們按倒序排列),你獲得的收益就越大。 但這並不意味著你一定要按照性能剖析列表的順序從高到低進行改進,這裡你還需要考慮改進的成本問題。

例: 看下錶3,它基本和表2一樣。 表3額外給出了你實施最好的改進方案所能達到的效果以及相應的實施成本。

那麼你應該先實現哪一項改進呢?

阿姆達爾法則告訴我們改進第一項的潛在收益最大,大約可以減少851秒(34.5% * 2468秒)。

但改進第一項真的非常昂貴,那麼改進第二項也許能產生更多的凈收益。

這才是我們真正需要改進的瓶頸所在,儘管它僅能節省大約303秒。

你首先實施哪一項改進,歸結於你對成本評估有多大把握, 簡單省事的改進的措施是否考慮了改進可能造成的系統風險? 一個很簡單的改進,例如調整了某個參數,取消了一個索引可能會潛在的破壞了一些目前性能表現良好的功能,而你又完全沒考慮倒。 靠譜的成本評估則是展現你技術能力的另一個領域了。

另一個因素值得考慮的是,你可以通過一些小的成功來積累政治資本。 也許一些便宜低風險的改進並不能帶來響應時間的大幅度降低,但可以通過跟蹤記錄這些小改進來印證你對響應時間提升的預測。 在神話和迷信統治了數十年的軟體性能領域,這些對性能的預測和印證的跟蹤記錄,可以影響你的同事(工程師、經理、客戶)並建立自己的信譽,然後你才可能實施更昂貴的改進方案。

最後提醒一句:當你不斷取得勝利並建議實施成本更高、風險更大的改進措施時,可千萬別掉以輕心。 信任是很脆弱的,你做了很多事情才取得信任,但可能只是因為一次粗心大意的錯誤就會摧毀它。

9. 偏斜度

當你使用性能剖析時,你會反覆遇到類似這樣的衍生問題。

例: 從表2中可以看到一共調用了322,968次 DB:fetch()方法,花費了1748.229秒。 假如我們將調用量降低一半,那麼響應時間會降低多少?

答案絕對不會是降低一半,花點時間思考下面這個更簡單點的例子。

例: 調用4個方法花費了4秒鐘,那麼減少為調用2個方法花費多少時間?

答案依賴於我們省掉的調用到底是哪些方法。 你可能這樣假設了,每個方法的平均調用時間就是 4 / 4 = 1 秒。 但我可沒在問題描述中告訴你的,每個方法的調用耗時是一樣的。

例: 假設下面兩種可能性,每個列表列出了4個方法調用的響應時間

A = {1, 1, 1, 1}

B = {3.7, .1, .1, .1}

在列表A中響應時間是一致的,所以無論我們省掉了哪兩個調用,最後響應時間都能縮短到2秒。

但在列表B中,到底省掉哪兩個方法調用對響應時間的影響是有很大差別的。

如果我們去掉頭兩個調用,響應時間縮短為0.2秒,提升了95%。

但如果我們去掉的是後兩個調用,響應時間變為3.8秒,僅僅提升了5%。

偏斜度表達在一組值中的非一致性程度。 正是因為偏斜度的存在,所以你沒法準確的回答我在本節開頭的問題。 讓我們再回頭看看這個例子,在不知道偏斜度的前提下,你只能回答響應時間可能減少的範圍是在0到1748.229秒之間,這也是你能提供的最好的回答了。 儘管如此,假設你有一些額外的信息例如表4所示,你就能對最好和最壞的情況進行估算。 進一步說,假如你有了表4中信息就會很聰明的去特別優化響應時間在0.01秒到0.1秒之間的那47,444個調用。

10. 風險最小化

前面的章節我提到過,當修復一個任務性能問題時可能破壞另一個任務的性能,讓我想起了一件曾經在丹麥發生的事。

這個故事很短:

場景:

在丹麥的巴勒魯普自治市(M?l?v)的一張橡木餐桌前,大約10個人圍坐一起,在用筆記本工作和相互交流。

Carry:夥計們我快熱死了,你們不介意我打開窗戶放點冷空氣進來吧?

Carel-jan: 為什麼你不把你的厚毛衣脫了呢?

完。

在這裡, 有個樂觀的人都知道的一般性原則在發揮效力:

「當大家都很舒適除了你以外,那麼你首先應該確保影響自己的東西是否正常, 否則你可能去搞亂一些全局的東西導致每一個人都受影響」

正是這個原則,當因為幾個寫的很爛的java應用程序有人建議我去調整oracle的網路包大小時讓我感到很害怕。 這些很爛的程序產生了很多不必要的資料庫調用,自然也產生了很多不必要的網路等待。 當其他一切正常除了這幾個爛程序,那麼最安全的做法是將調整的範圍本地化,只需要去調整這幾個爛程序就好了。

11. 效率

即便依賴此系統進行工作的所有人都很痛苦,你依然需要首先專註於業務上最優先需要修正的程序部分。 讓程序工作的儘可能的高效是一個很好的切入點。 在不增加容量,不犧牲必須的業務功能的前提下,效率是能夠節省下來的任務總執行時間的倒數。

換句話說,效率就是從反面對浪費進行的度量。 下面是一些經常發生在資料庫應用中浪費的例子。

例: 一個中間層程序為插入資料庫的每條記錄創建了一條獨立的SQL語句。 它執行了10,000次資料庫預編譯語句調用,導致了10,000次網路I/O調用。 其實它可以只使用一條預編譯語句,從而節省9,999次網路I/O調用。

例: 一個SQL語句訪問資料庫緩衝區緩存7,428,322次獲得了698行的結果集。 使用一個額外的過濾預測,只返回了終端用戶真正想要看見的7行結果,只需訪問緩衝區緩存52次。

確實如果一個系統存在一些全局性的問題(不良索引、錯誤參數、弱弱的硬體配置)導致了一大片任務執行的低效率,你應當修正它。 但不要嘗試調優系統去滿足低效的程序。 有很多辦法來調優低效的程序本身。 即使這個程序是商業的現成的軟體,那麼和你的軟體供應商一起去優化程序本身比你去優化系統讓其儘可能的高效從長期來說會更受益。

讓程序變的更高效會讓工作在這個系統上的每一個人都受益巨大。 很容易看到浪費的減少對任務響應時間的貢獻。 但依然有很多人不明白為什麼提升這部分程序的性能會導致一種副作用,讓看起來完全不相關的另一個程序性能變差。 其實這是系統負載在作祟。

12. 負載

負載是並發任務執行時引發的資源競爭。 負載正是我們為什麼不能在性能測試中捕捉到所有性能問題的原因,而這些問題以後會在生產環境發生。 負載的一個測量指標是使用率。 使用率反應了資源按時間分片的使用情況。 當某個資源使用率上升時,那麼請求該資源服務的用戶就不等不經歷更長的響應時間。 任何一個在城市的高峰期開車的人都經歷過類似現象。 當交通變的嚴重擁堵時,你不得不在收費站前等待更長的時間。

你的汽車在開闊的道路上能開上每小時60英里,但在擁堵的路上只能以每小時30英里的速度行駛,而軟體不會像汽車這樣真的變慢。 軟體按照固定的同樣的速度執行,每個時鐘周期總是執行同樣數量的指令,但響應時間會隨著系統資源變的繁忙而嚴重退化。

負載上升系統變慢的原因有兩個:隊列延遲和相關性延遲。 下面我會進一步講述。

13. 隊列延遲

負載和響應時間之間在數學上的相關性大家都很熟悉了。 一個叫做」M/M/m」的數學模型將響應時間和負載關聯起來應用於一些特定的需求場景下。 M/M/m模型的一個假設前提是你的系統模型擁有理論上的完美擴展性。 這個假設非常類似於我們在初級物理學課程中經常提到的光滑表面(無摩擦力)假設。

雖然M/M/m模型假設的條件有些不現實,如完美的可擴展性,但從中依然可以學到很多。 圖4使用M/M/m模型展示了負載和響應時間之間的關係。

從圖4,你從數學的角度看到了系統在不同負載條件下給你的感受。 低負載下的響應時間和無負載基本一樣。 當負載上升時,你能感受到響應時間有一個輕微、平緩的退化。 這種平緩的變化不會造成什麼麻煩,但隨著負載繼續上升響應時間開始以一種急劇的方式退化,這可要造成大麻煩了。

就是這樣一個等式:R = S + Q

服務時間(S)就是任務的執行時間。

隊列延遲(Q)就是任務在隊列中等待機會獲得消費某個資源的時間。

所以當你在 Taco Tico(美國和墨西哥邊境的快餐連鎖)訂餐時,你的訂單響應時間(R)就包括了等待服務員來餐桌邊接收訂單的等待時間,這就是隊列延遲等待(Q),而服務時間(S)就是從訂單交到服務員時到食物送到你手上的等待時間。 同樣,任務的響應時間在有負載和無負載的系統之間是有差別的。

14. 拐點

提及性能,你想要達到兩個目標

1. 你想要獲得最快的響應時間:你不想任務的完成需要太長的時間。

2. 你想要獲得最大的吞吐量:同一時間能支持更多人執行他們的任務。

不幸的是這兩個目標是相互矛盾的。 優化達到第一個目標需要你最小化系統的負載,而達到第二個目標則要最大化系統負載,二者不可兼得。 在這兩者之間的某個負載值就是系統的最優負載。

處於最優負載平衡點的資源使用率的值,我稱其為「拐點」。 系統中某種資源達到「拐點」後,那麼吞吐量被最大化了而對響應時間只有很小的負面影響。 從數學上來講,「拐點」就是響應時間除以資源利用率所得結果最小的值。 「拐點」有個很好的屬性,就是位於從原點畫一條直線正好與響應時間曲線相切的位置。 在一個仔細繪製的M/M/m圖中,你能很容易的利用這個屬性找到「拐點」,如下圖5所示。

關於M/M/m模型「拐點」的另一個很好的屬性是你只需要知道一個參數就可以計算出它。 這個參數就是系統中並行的、相同的和獨立的服務通道數。 服務通道是一種資源,它們共享一個隊列,其他資源像收費站或者SMP(Symmetric multiprocessing 對稱多處理)結構的計算機中的CPU都是類似的概念。

在M/M/m模型中,斜體小寫的m表示系統建模時服務通道數。 對任意一個系統來說,計算「拐點」都是很困難的,好在我已經給你計算出來了(見表5)。 表5中列出了一些常見的服務通道數的「拐點」值。(此時你也許想知道在M/M/m隊列模型中另外兩個M代表什麼。它們與進入請求時間和服務時間的隨機性假設有關。 更多請參考Kendall』s Notation或進一步參考Optimizing Oracle Performance_11)

為何「拐點」如此重要? 對於那些請求隨機到達的系統,如果資源負載持續超過「拐點」,那麼響應時間和吞吐會因為負載的輕微變化而嚴重波動。 所以:

對於請求隨機到達的系統而言,保持負載低於拐點是至關重要的。

15. 拐點的相關性

那麼「拐點」的概念是不是真的如此重要呢? 畢竟,我曾經告訴過你M/M/m模型建立在一個理想的烏托邦理念之上,那就是系統擁有完美的可擴展性。 我知道你正在想什麼:你想的都是錯的。

M/M/m模型告訴我們,即便你的系統擁有完美的可擴展性,你依然會遭遇巨大的性能問題只要系統的平均負載超過了圖表10中給出的拐點。 那麼現實中你的系統不可能比M/M/m假設的理論系統更完美。 所以,你的系統的真實「拐點」會比我在表5中給出的更小。(我在這裡對拐點值使用了複數形式,因為你可以基於CPU來建立拐點模型,同時也可以基於你的磁碟、網路I/O等等)

再次說明:

1. 你的系統中的每一項資源都有一個「拐點」。

2. 你的系統「拐點」都是小於或等於表5中給出的理論值,你的系統擴展的完美性越差,「拐點」越小。

3. 對於請求隨機到達的系統,如果資源負載持續超過「拐點」,你將遭遇性能問題。

所以,保持負載低於拐點是至關重要的。

16. 容量規劃

理解了「拐點」可以減少容量規劃的複雜性,可以這樣來規劃:

1. 某項資源的容量就是在高峰期能輕鬆的運行你的任務而資源使用率不會超過「拐點」。

2. 保持資源利用率低於「拐點」,那麼系統表現就基本不會給你帶來大的「驚喜」。

3. 但是,如果你系統中任何一項資源超出了它們的「拐點」,你就會遭遇性能問題,無論你是否意識到。

4. 如果你遭遇性能問題,不要糾結於數學模型上,要修正這些問題要麼重新安排下負載分配,要麼減少負載,要麼增加容量。

這就是將性能管理過程和容量規劃結合起來的辦法。

17. 隨機到達

你可能已經注意到了,我在前文經常提及「隨機到達」這個說法,為什麼它如此重要? 現在一些系統擁有的特徵你可能不會具備,如:完全確定的作業調度。 另外一些系統被配置為接受任務的方式像是機器人模式,如每秒接受一個任務,十分固定,當然現在這些系統很少見了。

我這裡說的一秒一個任務,並不是說平均一秒一個任務,例如第一秒2個任務,而下一秒0個任務。 我指的是均勻的一秒來一個任務,類似汽車工廠組裝線上機器人的工作模式。

如果任務到達系統是完全確定的,就是說你完全能預知下一個請求什麼時候到達,那麼讓資源的使用率超過「拐點」必然不會引發性能問題。 對於一個任務到達很確定的系統,那麼你的目標應該是將資源利用到100%,而不是讓它們排隊等待。 拐點對於隨機到達的系統如此重要的原因是,隨機任務請求往往會聚集並引發短暫的資源使用脈衝式上升。 這些脈衝式上升需要足夠的剩餘容量來消化它們,所以當脈衝發生時可能就會引發隊列延遲並導致響應時間的明顯起伏。

短暫的脈衝並導致資源使用率超過「拐點」也還好,只要不要持續達到數秒時間。 這個數秒到底應該是多少秒呢? 我相信(當然我沒試過去證明)這個時間最好不要超過8秒。(來自著名的互聯網8秒原則) 如果你無法滿足在特定百分比下響應時間和吞吐量對用戶的承諾,那麼很顯然系統脈衝上升持續時間太長了。

18. 相關性延遲

你的系統肯定不具備理論上的完美擴展性。 儘管我從沒分析過你的系統,但我敢打賭無論你的系統是什麼樣的也不具備「M/M/m」理論模型假設的完美擴展性。 那麼相關性延遲正是你的建模不可能完美的原因。 執行任務時花在對共享資源訪問的協商和通信的時間就是相關性延遲。 和響應時間、服務時間、隊列延遲一樣,相關性延遲也可以在任務的執行中被測量,例如每點擊秒數。

這裡我並不想描述預測相關性延遲的數學模型。 但如果你分析過你的任務執行情況,你可以了解什麼時候相關性延遲會發生。 在oracle中,一些同步的事件正是相關性延遲的例子:

入隊列(enqueue)

緩衝忙等待(buffer busy waits)

閂鎖釋放(latch free)

你不能使用M/M/m來對相關性延遲進行建模。 因為M/M/m模型假設了你的m個服務通道是完全並行的、等同的和獨立的。 這個模型假設在一個先進先出(FIFO)隊列中,只要你等待的時間足夠長,在你之前的請求已出隊列並得到服務,那麼最終你也會得到服務。 但是相關性延遲不是這樣工作的。

例: 假設在一個HTML數據表單上,有個按鈕是「更新」,點擊它會執行一條SQL更新語句。 另外一個按鈕是「保存」,點擊它執行事務提交將剛才的更新保存下來。 如果一個應用是這樣做的,我可以保證它的性能是非常糟糕的。 這是因為這樣一種設計,讓下面的場景成為可能的,實際上這也是必然可能的。 一個用戶先點擊了「更新」,發現到了午餐時間,然後就去吃飯了,過了兩小時下午回來再點擊保存。

對於想要更新同一行的其他任務來說,這是一個災難。 其他任務不得不等待獲取行鎖,更糟的情況下甚至是頁鎖。 直到原來鎖定的用戶想起繼續點擊「保存」。 或者DBA來殺掉原來鎖定用戶的會話,這樣的話當然又會給原用戶造成錯覺,他以為他更新了一行實際卻沒有,這可糟透了。

在這個例子中,不管系統繁忙與否,一個任務就在那無所事事的等待鎖的釋放。 它依賴了系統資源利用率之外的一些隨機性因素。 這就是為什麼你不能使用M/M/m模型來對其進行建模。 這也是為什麼在一個單元測試環境下的性能測試結果不足以用來決策是否應該在生產環境添加一些新的代碼。

19. 性能測試

我們談到的隊列延遲、相關性延遲引發了一個很困難的問題。 你如何對一個新的應用進行足夠的測試,讓你信心滿滿的認為它不為因為性能問題而對你的生產程序造成破壞。 你可以去建模,也可以去測試。 但是,為所有你可以預見的生產問題去建立模型和測試是極其困難的,在你真正遭遇這些問題之前。

因此,一些人看到了這樣窘境,因此申辯說那麼就乾脆別測試了。

千萬別被這樣的心態所困擾。

下面幾點是很確定的:

1. 在程序進入生產環境之前,如果你嘗試去發現一些問題你肯定會比那些完全不去做的找到更多的問題。

2. 在預發布的測試中,你不可能發現所有的問題。

3. 所以你需要一些可靠並有效的方法來解決這些在預發布測試中漏掉的問題。

在完全不測試和完整的生產環境模擬測試之間,存在一個適度測試量的平衡點。 當然對於一家飛機製造商來說,適度測試量肯定多於一家銷售棒球帽的公司。 但千萬別完全跳過性能測試。 至少,當你在生產環境遭遇不可避免的性能問題時,一份性能測試計劃將使你成為一名更稱職的診斷專家(更清晰的思考者)。

20. 測量

人們能感知到的就是吞吐量和響應時間。 吞吐量很容易測量,相對來說測量響應時間要稍微困難些。(還記得吧,吞吐量和響應時間可不是簡單的倒數關係) 用個秒錶來計時終端用戶的行為並不困難,但你不會從中得到你真正想要的關於為什麼響應時間如此之大的細節。

不幸的是,人們總是測量他們容易測量的,而不是他們應當測量的。 當我們需要測量的東西不容易測量時,我們就把注意力轉移到那些容易得到測量數據上了。 這是個錯誤。 那些並不是你真正需要的測量,但看起來似乎和你真正需要的有些相關又容易去執行的測量,我們稱之為「替代指標」。 一些「代指標」例子包括像子程序調用計數和子程序執行耗時的採樣數據。 對於「替代指標」,很遺憾在我的母語中沒有更好的語句來表達我的想法,但有一個大家都熟悉的現代表達方式:

「替代指標真是噁心(Surrogate measures suck.)」

不幸的是,」噁心「在這裡並不表示它「沒用」。 要是替代指標真的沒用就好了,那就沒人會使用它們了。 問題就在於替代指標有時是有用的,這讓使用替代指標的人相信它們總是有用,但實際並不是這樣。

替代指標有兩個嚴重的問題:

1. 它們可能在系統不正常時告訴你系統一切正常,這在統計學上叫做第一型錯誤,假陽性。

2. 它們也可能在系統正常時告訴你系統出問題了,這在統計學上叫做第二型錯誤,假陰性。

這兩類錯誤浪費了人們許多的時間。

當你去評測一個真實系統方方面面,你能否取得成功在於你能從那個系統中獲得多少有效的測量數據。 我曾有幸在oracle的市場部門工作過,那時許多軟體供應商圍繞著我們積極的參與,這才使得正確的測量系統成為可能。 讓軟體開發者使用oracle提供的工具是另外一回事了,至少我們的產品中具備這樣的能力(記錄有效的測量數據)。

21. 性能是一項功能特性

性能是軟體程序的一項功能功能特性,就像在你的bug跟蹤系統中很方便的將「Case 1234」這樣一個字元串自動鏈接到編號1234的bug案例上。 性能像所有其他軟體功能一樣,不是湊巧得到的。 你需要去設計和構建它。 要想獲得好的性能,你不得不去仔細的思考、研究、學習,寫出額外的代碼來測試和支持它。

儘管如此,像所有其他功能特性一樣,在項目初期你還在調研、設計和編寫代碼時你不可能知道性能到底會怎樣。 對大多數應用(可能是絕大多數,這個說法可能有爭議)而言性能都是未知的,直到它們投入實際使用階段。

那麼留給你的問題就是:

「因為在上線前你不可能知道你的應用性能表現到底怎樣,因此你需要在編寫應用時考慮怎樣很容易的在生產環境修復性能問題。」

正如大衛·加文(David Garvin)告訴我們的,容易測量的東西也更容易管理(來自《建立一個學習型組織》1993年發表於《哈佛商業評論》) 那麼要寫一個在生產環境容易修復問題的應用首先要做的就是要容易在生產環境測量。 大多數時候,當我提到生產環境的性能測量時人們會陷入一種焦慮狀態,他們很擔心性能測量帶來的入侵效應。 他們立刻對採集哪些數據做出了妥協,只留下那些」替代指標「(更容易採集的)在數據採集表上。 擁有額外數據採集代碼的軟體會變的比沒有這些代碼的更慢么?

我喜歡湯姆·凱特(Tom Kyte)以前對這個問題的回答。 他估計額外的性能度量代碼給oracle帶來不超過10%性能損失。 他接著向那些氣惱的提問者作出解釋,正是因為從這些性能測量代碼獲取的數據讓oracle公司進一步將產品性能改進提升了不止10%,這超出了性能測量代碼本身引發的開銷。

我認為很多軟體供應商他們通常花費了太多時間來優化他們的性能測量代碼路徑使其更高效,而不是首先搞清楚怎麼讓這些代碼有效果。

高德鈉(Donald Knuth)曾在1974說過的一句話印證了這個觀點:

「過早優化是一切罪惡的根源」。

軟體設計者將性能測量代碼整合進他們的產品中更有可能創建一個高性能的應用,更重要的是這個應用會不斷變的更快。


推薦閱讀:

在 Linux 上檢測 IDE/SATA SSD 硬碟的傳輸速度
A/B測試如何推動業務決策
8種策略——教你如何玩轉端到端的移動測試

TAG:性能 | 測試 |