架構視角的性能優化,該怎么做?

0 評論 3933 瀏覽 7 收藏 31 分鐘

性能是一個很籠統的詞,我們需要一個完整的方法,對于性能問題進行鑒別、分析、解決。本文作者探討的就是如何在復雜的系統下進行優化的方法論,讓硬件投入劃得來,讓系統保障可靠的同時無比絲滑。一起來看一下吧。

前言

首先我們的系統通常是非常復雜的。無論你的系統是一個單體應用;還是做了n多解耦、分層、拆分的工作,單元邏輯足夠簡單的分布式應用;但是對于一個功能視角來看,仍然非常復雜,反而分布式環境下問題要比單體應用還要復雜一個量級。

本文要說的就是:如何在復雜的系統下進行優化,讓我們的硬件投入劃得來,讓我們的系統保障可靠的同時無比的絲滑。

性能是一個很籠統的詞兒,很多時候直接性能優化三板斧,只是誤打誤撞的在解決問題,我們需要一個完整的方法,對于性能問題進行鑒別、分析、從而解決。本文要探討的就是這部分“方法論”,讓性能優化的ROI最大化。

一、什么叫性能好?

要分析性能問題,首先我們得有一個鑒別標準,比如:tps承載量、qps承載量、延時、平均響應時間、每秒IO數量(IOPS)、負載、飽和度等等,甚至有些公司內會有一些內部約定的指標,不同的場景下可能直接使用的指標是不同的(這樣做是合理的,更簡單且直接的描述性能),但是如果要把性能優化的理論吃透,更從容的解決未知問題,需要找到性能描述最本質的東西。

個人認為,對于不同場景進行分類,描述我們系統最恰當的最原始的指標就是吞吐量(對于容量做考量)和響應時間(對于時間做考量),我們要優化的就是這兩點。

tps、qps、IOPS這些一定程度上就是在側面描述吞吐,而平均響應時間、延時同樣是吞吐量的重要影響部分,對于大部分場景,我們追求的往往是吞吐量。而某些場景會最大化追求延時降級,而非吞吐量。

看到這里可能會好奇,吞吐和響應時間不是有對應關系嗎,為啥是兩個指標?

由于單位資源的限定,即時相同的功能,減少響應時間就是在增大吞吐,而某些情況多犧牲部分資源消耗減少響應時間卻又減少了吞吐(比如極多步并發處理),圍繞微批處理思路增大延時卻又增大了吞吐(比如trigger buffer),具體場景具體分析哈,總之要么高吞吐、要么低延遲,更多的時候是在取折中。

架構視角的性能優化

話題拉回來,我們要知道什么是性能好,才能評判系統,才能進行優化,才能具體執行??傮w來說:

以吞吐、延時為基準。

  1. 性能問題的優化是自上而下的,首先確定系統整體的性能目標及評判標準;然后整體評判
  2. 其次性能問題一定是可拆分的,若干的劣化共同導致了“差”;
  3. 優化的每個子環節應該有自身的目標,而不是對于整體目標的簡單拆分。根據子目標進行分布評判,有的追求低延時、有的追求高吞吐、有的要兼顧。
  4. 子目標以對整體賦能為標準,避免局部最優解

如何鑒別和判斷:我們拿到吞吐量、響應時間的數據之后,有N多的呈現方式。比如說平均值、標準方法、百分位數、中位數。

對于性能問題來講平均值參考意義不大,只能大致呈現系統水準,僅在系統整體性能差的情況下有幫助,而大多時候的性能分析,通常更關注極端百分位或者異常值,因為這些bad case往往代表了性能的潛在風險及劣化趨勢。

二、如何優化

知道好壞,接下來談如何優化。要談優化,首先避免犯錯,即使不懂成體系的優化理論,常見的坑肯定是熟知的,比如說慢SQL、for-each call、大量重復運算、各種手工暴力解法。這里不過多贅述了,大家對這些常見的性能坑一定很熟悉。

1. 性能優化的思路

我們的應用程序執行是分若干層級的,從應用程序(內部又可以分為業務程序、中間件程序、庫函數),到系統庫,再到系統調用、最后到內核、再到硬件。

就優化角度而言,越靠近工作執行的地方性能優化帶來的收益就越大,直白點就是越靠近我們業務邏輯的優化,越有效。按理說越底層的東西應該對系統產生的影響越大,那為什么這么說呢?

架構視角的性能優化

就影響程度而言,越靠近工作執行的地方,所能對系統產生的影響就越大。比如優化10條sql的底層存儲,”10條sql”-> “1條sql” -> “無同步sql”差異。就實現角度和專業角度而言,越靠近具體工作執行的地方,實現較蠢的可能性越高。

而這些可以匯總為:對于系統而言會有“技術選型”、“不合理使用問題”等問題,就經驗和概率來講,99%的問題是因為這部分,如果把所有的性能問題進行匯總,然后看具體分布圖,不難發現,幾乎都分布在上層(應用層)。說幾個問題大家感受一下,慢sql(應用層)、mysql檢索復雜度過高需要優化(中間件)、磁盤IO操作不合理(系統調用)、磁盤太慢(硬件層)等問題發生的概率和分布。

總體來說,果斷承認“99%的問題是因為我一時犯蠢或者不太聰明”,才能開始逐漸正視性能問題和啟動性能優化工作。所以整體的性能優化,應該是自頂而下的,由我及外的。

2.“慢”在哪里?

性能問題要求一定是可觀測的、可量化的,但是我們的系統不一定是可觀測的,或者無法具體量化。這就導致面臨性能低下時,就跟面對一個黑盒盲區一樣,只知道差,卻束手無策。

這時就需要對于我們的系統進行剖析,系統必定由n多部分構成(比如各種子服務,各種function,各種中間件)除系統構成外,我們更應該知道系統工作時的構成(每條鏈路的運行視圖 — 實時運行狀態),然后分析哪些地方出了問題。

首先,對于我們的系統進行層層剖析:

架構視角的性能優化

尤其是對于做工程的或者做業務的,我們的整體架構大多是分布式或者微服務架構,如果進行層級、調用鏈路的拆分通常是如上圖所示。

我們要做的就是對于系統有一個更加全面的認知,最起碼要回答如下問題,可以不用非常細節,至少要知道一個概貌:

  • 部署環境&硬件設施是什么樣子
  • 由哪些服務構成
  • 用了哪些中間件(&版本 &適配器 &本地優化 &調參)
  • 怎么用的中間件(比如redis做緩存,怎么用的,鏈路如何)
  • IO鏈路是什么樣子(&對應的協議 &網絡處理模型 &放大倍數)
  • 調用鏈路是同步還是異步&有沒有做批處理
  • 怎么用的中間件
  • 每個server功能是什么樣子

在有了整體的概貌之后,就可以對系統進行一定的剖析了,看每一步或者關鍵步驟是否是支持我們觀測的,并且觀測手段是可以自動化監控,還是巡檢機制,還是case by case。再然后就能可以根據目標對于這些步驟或者說環節進行量化分析。(最好自動化哈)至此大概率就知道我們的系統具體慢在了哪里。

三、對影響因素有個大體的感知

先說響應時間,首先我們要對速度有一個基礎的概念,數據沒那么絕對,但是差不多就是這個數量級先硬件相關:

  • 一個CPU周期:0.3ns
  • L1、L2、L3緩存訪問:0.9ns、2.8ns、12.9ns
  • 內存訪問:120ns
  • 固態硬盤:150us
  • 機械硬盤:1ms
  • 一次同機房調用:1ms
  • 一次跨城網絡傳輸:30ms
  • 一次跨專線地域傳輸:100 – 200ms

再包裝下看日常的操作耗時:

  • 一條sql call(索引合理,數據量正常):1-2ms
  • 一次RPC調用:1-10ms
  • 一次redis操作(無大key、熱key):1-2ms
  • 一次kafka send(帶buffer):1ms以內
  • 一次http請求:5ms

對于CPU的消耗程度,如果把CPU看作是穩定的(相對有點粗暴哈),消耗程度通??梢杂肅PU占用時間來表述(onCPU、offCPU)。比如完成一次md5需要多少時鐘、執行磁盤IO需要多少時鐘、一次網絡IO需要多少時鐘等等。

而這個東西具體的表象其實就是cpu使用率,我們不難可以看到各個進程、各個現成的占用程度,如果再掛一下火焰圖不難看出哪些操作對CPU消耗更高及對應占比。

架構視角的性能優化

各個響應時間和CPU消耗程度是對吞吐和響應時間最本質的影響,要進行性能優化就是對于CPU占用和響應時間(處理延時)進行優化,各種方法減少CPU動作、減少延時等待,應用層的優化就是奔著這些目的去的。

1. 優化延時

應用層面 關于延時我們該如何進行優化呢?整體來看的話:減少操作、減少等待。說幾個常見的策略:

  • 能否減少操作,可以對于一次請求、一次任務的執行步驟進行梳理,分一下類,核心工作是哪些,哪些工作是可以從核心鏈路摘除的。
  • 能否有復雜度更低的動作,比如檢索算法是否可以優化、序列化算法是否可以優化、是否有更高效的數據結構和算法。
  • 能否打時間差,前置處理或后置處理,比如初始化動作前置(不光是對象初始化,業務動作也行,可以提前計算、提前開戶等),后置比如異步化處理,發獎券之類的
  • 能否緩存&復用,請求處理的結果、中間過程、或者socket鏈接是否可以直接復用,各種池化技術。
  • 能否并行,多步并行處理,減少整體耗時,衡量下并行帶來的收益,是否比實現并行的代價高。
  • 能否非阻塞并發處理,減少等待,避免大量的等待操作,業務處理的劣根性大多源于此。
  • 能否提前失敗,對于易失敗請求,提前進行失敗性檢查,避免正常資源的占用,引起其他請求等待。
  • 能否繼續優化中間件操作,是否有更高效的中間件選型,操作上是否有更多的優化空間。
  • 能否無”鎖“化處理,鎖是導致等待的核心因素之一,但這里是泛指可能讓我們線程發生阻塞的情況,盡可能的減少同步鏈路中的等待機制可以讓我們的響應時間更容易得到保證。

以余額支付系統交互信息流程舉個例子(省了很多步驟,粗略描述):

架構視角的性能優化

嘗試優化一下

架構視角的性能優化

系統層面(假設包含虛擬機),只是舉幾個例子

垃圾回收中斷是否影響過大,poll還是epoll、水平觸發還是邊緣觸發,fsync還是flush,線程的CPU親和,hardware 是否有特異性優化,SSD還是機械硬盤,設配新舊程度。

2. 優化吞吐

接下來看下吞吐的相關優化,吞吐量指的是現有系統在單位時間內能夠處理的請求或者任務數量。而對于吞吐的優化通常有兩點:

  • 單位資源內能夠具備更大的吞吐量,提高系統資源利用率。
  • 保證吞吐量沒有瓶頸,一定程度上,可以無限橫向擴展。

先看第一個問題,如何讓單位資源內承載更多的吞吐。

影響吞吐的因素有很多,比如前面剛剛提到的響應時間,關于響應時間和吞吐量的關系前面已經描述過了,大部分場景下適當的減少延時對于吞吐的影響是線性增長的,如何降低響應時間參照前一段的方案(大部分是正向的),唯一需要注意的是,在有限的資源下,過度的優化是會帶來吞吐下降的,要做的是盡可能在不帶來CPU增長的情況降低響應時間。

常用的技巧技巧批處理,比如多次IO是否可以進行合并,批處理一下,減少多次鏈接開銷,這樣對整體吞吐帶來的威力是很大的。對同步批處理,一定程度上也算是對于響應時間的調整,對于整體平均響應時間是在下降的,但是對于頭部請求響應時間是被劣化的。

但通常的使用往往是異步化處理 + 批處理,IO 緩沖區、buffer triger、insert buffer、kafka send buffer 都是這個思路。帶來的吞吐和響應時間提升通常是炸裂的。

接下來看第二個問題,解決吞吐瓶頸。

當完成單位資源的吞吐優化之后,按理說有訴求就水平擴容即可,但是實時并非如此,經常會出現一個“熱點”成為系統的吞吐瓶頸,尤其是在OLTP系統中,熱點會以各種各樣的方式出現,但是通常都和狀態強相關,比如說全局庫存、熱點用戶、熱點機器等等。

如果要優化,這里就不得不提CAP理論了,在數據多節點分布的情況下,為了保障一致性,系統整體的可用性必然會受到影響,可用性下降,最大的表現其實就是有效吞吐的下降。突破瓶頸,持續放大吞吐,那就適當的犧牲一下數據的一致性吧。

架構視角的性能優化

要做的是把熱點數據進行割裂,然后犧牲一致性,也就是把“熱點”進行sharding,讓熱點也能進行進行水平擴容,然后在這些短暫的不一致之上打補丁,盡快達成最終一致。

四、負載對于性能的影響

1. 劣化現象

除了我們代碼的實現、系統的構成會對系統有一定的影響,瀕臨負載極限的時候不管是吞吐量、響應時間都會受到一定程度的影響。

對吞吐而言,在機器資源一定的情況下,并不是隨負載一直線性變化的,到達一定的臨界值后如果持續增加負載,吞吐通常會由線性增長進入緩慢增長的,如果再持續的增加負載,會出現一定的劣化現象(比之前的吞吐量還會降低),這在JVM等虛擬機之上運行的應用,或者在池化排隊機制的加持下愈發嚴重和明顯。

架構視角的性能優化

對響應時間而言,在機器資源一定的情況下,也不會隨始終保持耗時不變,同樣在到達負載臨界值時,會發成程度不一的劣化現象,有的響應時間增長迅速,有的則呈慢速下降。

架構視角的性能優化

2. 劣化的原因

這是為什么呢,因為在負載到達一定閾值時會觸發程度不一的系統資源搶占問題,比如大量的排隊等待處理,此時上下文切換會更加的頻繁,并且對于某些JVM等虛擬機之上的運行的應用還會帶來一定程度上的內存清理(分配和回收)及維護代價進而導致競爭更加激烈,也就導致了性能被進一步劣化。

這也是為什么很多中間件會限制最大鏈接數、最大線程數、最大排序序列的原因,而對于我們業務應用程序而言也是一樣的,在進行池化處理排隊機制時一定要明確合適拒絕。

并且我們應該對于我們的的服務加以保護,以此保證負載不會出現這種劣化現象,比如說限流、限并發,比如說tomcat把參數調整的合理一些,讓負載,比如說CPU最大不超過60%、70%。

3. 這里有個大坑 – 百分之90的問題是因為這個

除了到達負載臨界值時會出現這個問題,在“驟發的流量”面前這個問題也非常的凸顯,因為大量的初始化工作(鏈接建立、線程池擴容、client初始化等等),因為卡頓問題或者延遲變高等問題導致排隊激化,同時機器負載也會驟然上升,導致負載臨界問題或者時臨界值提前到來。日??幢O控,突發流量時的尖刺,90%的原因都是這個。

這一點一定要額外注意,大流量前充分預熱、保持緩存熱度、高性能場景放棄懶加載,極端情況下適當擴大活躍線程數、鏈接?;疃际鞘种匾?。

架構視角的性能優化

4. 日常的負載要保持多少

日常應用的負載通常由服務器資源的瓶頸負載所決定,CPU爭搶會劣化,內存爭搶發生換頁情況更糟糕。那我們日常的負載到底應該在多少合適,這里以CPU負載為例來進行推導:

通常經驗值是60%或者70%,在對穩定性要求極高的場景可能會控制的更低一些,這個值是怎么得來的?

架構視角的性能優化

首先CPU負載超過90%就會有概率發生劣化現象,CPU并非你想象的那么穩定性能方面會有抖動的;

其次即使隔離部署,操作系統之上運行了不止你一個應用程序的進程,可能會有一些重操作的client,比如log拉取,如果log還在客戶端做了grep、awk等處理那可能影響更重,這部分的buffer要留出來;

其次我們的進程的CPU消耗并不是線性不變的,仔細看看監控會發現忽高忽低,這部分buffer要留出來;

我們很難保證下游不發生抖動,如果下游抖動發生了一定程度的抖動,但還未超時,一定概率會導致主調server發生排隊,進而可能發生雪崩效應,放大問題,這時候產生的劣化波動也是要能承受的,同樣的也需要一定的buffer。

同樣的,我們也很難保證主調方不會發生合理程度內的激增現象,驟然的CPU波動也是合理的,這部分buffer要留出來。

干掉這些buffer,通常經驗值是60%上下浮動,如果單請求消耗資源極多并且可用性要求極高那可能多留點buffer,否則就少點,然后對應的平穩時對應CPU負載所能承載的負載就是我們應該設定的日常負載,再由此推出限流值、動態擴縮標準等等。

5. 熱門問題 – 語言的差異到底有多大

語言是個有趣的問題,也是一個爭論已久的問題,這里進行簡單的討論哈,通常我們的變成語言會分為編譯型語言和解釋型語言,還有些語言既有解釋器,又有編譯器,不用糾結概念,就從解釋執行和編譯執行的角度看問題即可。

編譯型比如說C/C++,編譯過的代碼總體來說是高性能的,因為在CPU執行執行時不需要額外的一層映射,并且機器代碼總是原始代碼映射的很緊密,當然啦,很大程度取決于編譯器的編譯優化。

解釋型如說Java(純解釋執行的話,拋開JIT不談),需要額外的一層解釋工作,會增加不少的開銷,通常不會被期待有很強大的性能。

對于解釋器和編譯器共存的語言Java(帶上JIT了),性能好壞主要看JIT的優化力度,想觀測的話可以打開JIT日志,看下你的代碼到底哪些進行最徹底的優化(JIT 優化層級是4),然后看哪些操作會導致退優化的發生,JIT的決策器也非常的關鍵。但是這種體系下,性能分析會相對費勁一些,JIT的優化策略(決策、編譯)很難說對所有開發者都是白盒的,并且在真正執行的時候,和我們代碼的映射已經幾乎匹配不上了(還好Java的性能分析工具集夠全)。

架構視角的性能優化

除此之外,語言的垃圾回收機制雖然帶來的很多開發的便捷性,但是對于性能要求極高的場景,垃圾回收會帶來額外的影響,比如說程序的停頓,內存中對象的掃描、索引、復制工作。

五、性能優化的時機和判斷

1. 性能優化的時機

什么時機進行性能優化,常見的有“系統創建時”、“發生改變時”、“問題暴露時”、“擺爛-重新寫”,這個業界并沒有一個標準的答案,個人的處理思路是:在系統創建時滿足近1年半左右的性能訴求就足夠了,剩下的交給將來。

系統設計的初期肯定會進行系統的定位分析及對應的邊界劃分,此時系統應該具備哪些功能、應該被哪些場景所使用就已經確定了,我們要做的就是對這部分內容進行預判,這應該是屬于架構設計很核心的一部分,并且在設計的過程中,如果預判有50%以上場景會發生性能要求的突變,那么我們就應該給系統留足擴展性。

架構設計思路中有種思想叫做“演進式架構”,這里推崇的就是這個觀點。

但需要額外注意的是,我們可以不做過多的性能優化設計,但是要做足性能優化分析的設計,正如文章最開始所提到的,我們要保證系統是可觀測、可量化的。

上面講的是設計側的優化問題,在系統發生變更時,如果通過分析工具和監測工具發現有性能劣化的現象,是一定要進行優化動作的,放任性能問題不管一定會阻礙我們系統的進程,技術債早晚堆成山。

2. 減少局部最優解

局部最優解是一個數學求解的最優化問題,我們設計實現的摸索,也可以粗暴的看作是一個最優的求解問題,那么理論上也是會存在局部最優解問題的。

實踐過程也確實如此,相信大家都經歷過大規模的重構。因為性能優化方案選擇絕對不止一條路,再有拆分到子方案岔路就變的更多了,在選擇的過程中很容易發生局部最優解問題。

架構視角的性能優化

就整體方案選擇而言

在進行系統設計的時候,因為當前的系統設計整體方案不合理,在一個相對偏差的方向上進行了極致的優化,導致性能看起是得到了解決,并且段時間內沒有問題,但是在后期的發展過程中很容易導致性能優化停止不前。所以進行性能問題設計的時候,應該更多的比較各個方案的差異性,以及可發展性。多去探索未知的未知,才能讓我們的方案更加合理。

就方案拆分而言

架構設計應該是一個“一鍋出”的過程,一定要有一個上層指導,不要求每個子模塊要用何種方案,但是一定要有明確的上層指導目標,這樣在選擇時就避免方向上的偏差,不至于每個子模塊都是自己域內最合理的方案,但拼湊在一起確發現壓根解決不了問題。

3. 性能分析工具

首先基礎架構一定提供了足夠全的分析工具,所在的公司也肯定有人在搞定針對基礎架構的探測分析及業務系統分析的基礎支持,找到對應的人,然后問他們。

要是進行學習或者文檔檢索,提供一點關鍵詞,火焰圖、探針、優化分析、診斷工具、perf、profiling、Trace、netstat、iostat、vmstat、jstack、top、slabtop、ps。

這種資料應該一搜一大片……

六、寫在最后

整體算是一點自己對于性能問題分析及設計優化相關的方法論總結,文章里面并沒有涉及過多的細節方案,核心原因是想“以漁”。另外市面上大多的直接方案,導致小朋友們習慣性的三板斧,個人覺著這種導向性不算良好,應該更清楚為什么這么做,才能更好的解決問題吧。

除此之外,本文的內容實在是有點扯,建議通過一些系統實踐來反觀我所描述的這些方法和觀點,個人經歷過的覺著收益匪淺的系統:支付相關-比如paycore、交易收單、推薦相關、廣告相關-比如DSP、營銷活動系統。

作者:鄒志全

來源:https://mp.weixin.qq.com/s/513BSGZoTWzeVAp6KDTHsA

本文由 @一個數據人的自留地 授權發布于人人都是產品經理,未經作者許可,禁止轉載

題圖來自 Unsplash,基于 CC0 協議

該文觀點僅代表作者本人,人人都是產品經理平臺僅提供信息存儲空間服務。

更多精彩內容,請關注人人都是產品經理微信公眾號或下載App
評論
評論請登錄
  1. 目前還沒評論,等你發揮!