系統效能優化總結

香吧香發表於2022-05-14

本文為博主原創,未經允許不得轉載:

效能測試的主要指標與方法

               

  一般來說,衡量系統的效能,主要有以下幾個指標:

  響應時間

    可以從端到端的響應時間細分下去:比如資料庫的響應時間,IO的響應時間,HTTPClient的響應時間。當我們優化系統的時候,通過收集這些響應時間可以精確定位效能問題出現在哪。

  併發數

    併發數是指系統能夠同時處理請求的數量,這個數字也反映了系統的負載承受能力。

  吞吐量

    吞吐量是指單位時間內系統處理的請求數量,體現的是系統的處理能力。在 Web 系統中,常常用 TPS ( 每秒事務處理量 ) 或者 QPS ( 每秒查詢量 ) 來衡量系統的吞吐量。在不考慮網路卡等網路裝置限制的情況下,可以使用下面的公式來大致估算系統的吞吐量:

    吞吐量 = (1000/響應時間 ms) x 併發數

 

1.MySQL調優

1.1.SQL優化

  這裡以MySQL為例,最常見的方式是,由自帶的慢查詢日誌或者開源的慢查詢系統定位到具體的出問題的SQL,然後使用explain、profile等工具來逐步調優,最後經過測試達到效果後上線。

  這裡舉幾個優化的例子:

  1查詢優化

    對查詢進行優化,要儘量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。

  2.避免null判斷

    應儘量避免在 where 子句中對欄位進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:

select id from t where num is null

  

  3.合理使用索引

    索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及 update 的效率,因為 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。

    一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有 必要。

  4.多使用數字型欄位

    儘量使用數字型欄位,若只含數值資訊的欄位儘量不要設計為字元型,這會降低查詢和連線的效能,並會增加儲存開銷。

    這是因為引擎在處理查詢和連 接時會逐個比較字串中每一個字元,而對於數字型而言只需要比較一次就夠了。

  5.避免大數量

    儘量避免向客戶端返回大資料量,若資料量過大,應該考慮相應需求是否合理。

1.2.慢sql或索引失效分析

  常見慢sql 或 索引失效的場景:

  • 無索引或新增索引的欄位區分性很差

  • 複合索引時不滿足最左字首規則

  • 應儘量避免在 where 子句中使用 != 或 <> 操作符,否則引擎將放棄使用索引而進行全表掃描。

  • 應儘量避免在 where 子句中使用 or 來連線條件,如果一個欄位有索引,一個欄位沒有索引,將導致引擎放棄使用索引而進行全表掃描。

  • in和 not in 也要慎用,否則會導致全表掃描,如:

select id from t where num in(1,2,3)

  對於連續的數值,能用 between就不要用 in 了:

select id from t where num between 1 and 3
  • 優化 SELECT * ; 查詢具體需要的列:如 select a,b from table where id=1,假設存在a和b的聯合索引,那麼 select * 會導致回表

1.4 SQL分析優化

  通過 EXPLAIN 分析 SQL 執行計劃

  

  圖中每個欄位說明

  • 「id」:每個執行計劃都有一個 id,如果是一個聯合查詢,這裡還將有多個 id。id 越大優先順序越高

  • 「select_type」:表示 SELECT 查詢型別,常見的有 SIMPLE(普通查詢,即沒有聯合查詢、子查詢)、PRIMARY(主查詢, 子查詢中最外層查詢 )、UNION(UNION 中後面的查詢)、SUBQUERY(子查詢)等。

  • 「table」:當前執行計劃查詢的表,如果給表起別名了,則顯示別名資訊。

  • 「partitions」:訪問的分割槽表資訊。

  • 「type」:表示從表中查詢到行所執行的方式,查詢方式是 SQL 優化中一個很重要的指標,結果值從好到差依次是:system > const > eq_ref > ref > range > index > ALL。

    • 「system/const」:表中只有一行資料匹配,根據索引查詢一次就能找到對應的資料。

    • 「eq_ref」:使用唯一索引掃描,常見於多表連線中使用主鍵和唯一索引作為關聯條件。

    • 「ref」:非唯一索引掃描,還可見於唯一索引最左原則匹配掃描。

    • 「range」:索引範圍掃描,比如,<,>,between 等操作。

    • 「index」:索引全表掃描,此時遍歷整個索引樹。

    • 「ALL」:表示全表掃描,需要遍歷全表來找到對應的行。

  • 「possible_keys」:可能使用到的索引。

  • 「key」:實際使用到的索引。

  • 「key_len」:當前使用的索引的長度。

  • 「ref」:關聯 id 等資訊。

  • 「rows」:查詢到記錄所掃描的行數。

  • 「filtered」:查詢到所需記錄佔總掃描記錄數的比例。

  • 「Extra」:額外的資訊。

    • Using where:不用讀取表中所有資訊,僅通過索引就可以獲取所需資料,這發生在對錶的全部的請求列都是同一個索引的部分的時候,表示mysql伺服器將在儲存引擎檢索行後再進行過濾

    • Using temporary:表示MySQL需要使用臨時表來儲存結果集,常見於排序和分組查詢,常見 group by ; order by

    • Using filesort:當Query中包含 order by 操作,而且無法利用索引完成的排序操作稱為“檔案排序”

    • Using index:表示相應的select操作中使用了覆蓋索引(covering index),避免訪問了表的資料行,效率不錯!

1.5.連線池調優

    我們的應用為了實現資料庫連線的高效獲取、對資料庫連線的限流等目的,通常會採用連線池類的方案,即每一個應用節點都管理了一個到各個資料庫的連線池。

    隨著業務訪問量或者資料量的增長,原有的連線池引數可能不能很好地滿足需求,這個時候就需要結合當前使用連線池的原理、具體的連線池監控資料和當前的業務量作一個綜合的判斷,通過反覆的幾次除錯得到最終的調優引數。

1.6.架構層面

    這一類調優包括讀寫分離、多從庫負載均衡、水平和垂直分庫分表等方面,一般需要的改動較大,但是頻率沒有SQL調優高。

 

2.分散式快取

  快取可以稱的上是效能優化的利器,快取主要用來存放那些讀寫比很高、很少變化的資料。

  什麼情況適合用快取?考慮以下兩種場景:

  • 短時間內相同資料重複查詢多次且資料更新不頻繁,這個時候可以選擇先從快取查詢,查詢不到再從資料庫載入並回設到快取的方式。此種場景較適合用單機快取。

  • 高併發查詢熱點資料,後端資料庫不堪重負,可以用快取來扛。

  使用快取需要注意的問題:

2.1.避免快取失效

  把頻繁修改的資料放入快取,容易出現資料寫入快取後,應用還來不及讀取快取,資料就已經失效的情形,徒增系統負擔。

2.2.快取熱點資料

  快取使用的記憶體資源非常寶貴,只能將最新訪問的資料快取起來,而把歷史資料清理出快取,即快取資源應該留給20%的熱點資料。

2.3.資料不一致性

  一般會對快取設定失效時間,超過失效時間,就要從資料庫重新載入。

  因此應用要忍受一定時間的資料不一致,另一種策略是資料更新時立即更新快取,不過這也會帶來更多的系統開銷和事務一致性的問題。

2.4.快取可用性

  業務發展到一定階段時,快取會承擔大部分資料訪問的壓力,資料庫已經習慣了有快取的日子,所以當快取伺服器崩潰時,資料庫會因為完全不能承受如此大的壓力而當機,進而導致整個網站不可用,這種情況被稱作快取雪崩,發生這種故障,甚至不能簡單地重啟快取伺服器和資料庫伺服器來恢復網站訪問。

2.5. 快取預熱

  快取中存放的是熱點資料,熱點資料是快取系統用LRU對不斷訪問的資料篩選出來的,這個過程需要較長的時間。

  新啟動的快取系統沒有任何資料,此時系統的效能和資料庫負載都不太好。因此可以選擇在啟動快取是就把熱點資料預載入好。

2.6.快取穿透

  因為不恰當的業務或惡意攻擊,持續高併發地訪問某一個不存在的資料,如果快取不儲存該資料,就會有大量的請求壓力落在資料庫上。

  簡單的解決方式是把請求的不存在的資料也放進快取,其value是null。

3.非同步化(訊息中介軟體)

  針對某些客戶端的請求,在服務端可能需要針對這些請求做一些附屬的事情,這些事情其實使用者並不關心或者使用者不需要立即拿到這些事情的處理結果,這種情況就比較適合用非同步的方式處理這些事情。

  非同步化的作用:

  • 縮短介面響應時間,使使用者的請求快速返回,使用者體驗更好。

  • 避免執行緒長時間處於執行狀態,這樣會引起服務執行緒池的可用執行緒長時間不夠用,進而引起執行緒池任務佇列長度增大,從而阻塞更多請求任務,使得更多請求得不到技術處理。

  • 執行緒長時間處於執行狀態,可能還會引起系統Load、CPU使用率、機器整體效能下降等一系列問題,甚至引發雪崩。非同步的思路可以在不增加機器數和CPU數的情況下,有效解決這個問題。

  比如:使用訊息佇列(MQ)中介軟體服務,MQ天生就是非同步的。

  複雜查詢以及一些聚合計算不適合在資料庫中做,可以利用搜尋引擎來實現,另外搜尋引擎還可以幫我們很好的解決跨庫、跨資料來源檢索的場景。

4.Web前段

  Web前端指網站業務邏輯之前的部分,包括:

  • 瀏覽器載入

  • 網站檢視模型

  • 圖片服務

  • CDN服務等

4.1.瀏覽器訪問優化

  1減少http請求

  HTTP協議是無狀態的應用層協議,意味著每次HTTP請求都需要簡歷通訊鏈路,進行資料傳輸,而在伺服器端,每個HTTP都需要啟動獨立的執行緒去處理,這些通訊和服務的開銷都很昂貴,減少HTTP請求的數目可有效提高訪問效能。

  減少HTTP請求的主要手段是:

  • 合併CSS,以及壓縮CSS大小

  • 合併JavaScript,以及壓縮JS大小

  • 合併圖片

4.2.CDN加速

  CDN(Content Distribute Network,記憶體分發網路)的本質上仍然是一個快取,而且將資料快取在離使用者最近的地方,是使用者以最快速度獲取資料,即所謂網路訪問第一跳。

  CDN一般快取的是靜態資源,如圖片,檔案,CSS,Script指令碼,靜態網頁等,但是這些檔案訪問頻率很高,將其快取在CDN可極大改善網頁的開啟速度。

5.服務化架構拆分

  做服務化最基礎的是按業務做服務拆分,避免跨業務間的互相影響,資料和服務同時拆分。同一個業務內部我們還按計算密集型/IO密集型的服務拆分、C端/B端服務拆分、核心/非核心服務拆分、高頻服務單獨部署等原則做拆分。

6. JVM 優化

6.1 GC 效能衡量指標

  「吞吐量」:這裡的吞吐量是指應用程式所花費的時間和系統總執行時間的比值。我們可以按照這個公式來計算 GC 的吞吐量:系統總執行時間 = 應用程式耗時 +GC 耗時。如果系統執行了 100 分鐘,GC 耗時 1 分鐘,則系統吞吐量為 99%。GC 的吞吐量一般不能低於 95%。

  「停頓時間」:指垃圾收集器正在執行時,應用程式的暫停時間。對於序列回收器而言,停頓時間可能會比較長;而使用併發回收器,由於垃圾收集器和應用程式交替執行,程式的停頓時間就會變短,但其效率很可能不如獨佔垃圾收集器,系統的吞吐量也很可能會降低。

  「垃圾回收頻率」:多久發生一次垃圾回收呢?通常垃圾回收的頻率越低越好,增大堆記憶體空間可以有效降低垃圾回收發生的頻率,但同時也意味著堆積的回收物件越多,最終也會增加回收時的停頓時間。所以我們只要適當地增大堆記憶體空間,保證正常的垃圾回收頻率即可。

-XX:+PrintGC               輸出 GC 日誌
-XX:+PrintGCDetails        輸出 GC 的詳細日誌
-XX:+PrintGCTimeStamps     輸出 GC 的時間戳(以基準時間的形式)
-XX:+PrintGCDateStamps     輸出 GC 的時間戳(以日期的形式,如 2013-05-04T21:53:59.234+0800-XX:+PrintHeapAtGC         在進行 GC 的前後列印出堆的資訊
-Xloggc:../logs/gc.log     日誌檔案的輸出路徑

6.2 GC 優化策略

  • 「降低 Young GC 頻率」

    • 由於新生代空間較小,Eden 區很快被填滿,會導致頻繁 Young GC,可以通過增大新生代空間來降低 Young GC 的頻率。

    • 通常在虛擬機器中,複製物件的成本要遠高於掃描成本。

    • 如果在堆中存在較多的長期存活的物件,此時增加年輕代空間,反而會增加 Young GC 的時間。

    • 如果堆中的短期物件很多,那麼擴容新生代,單次 Young GC 時間不會顯著增加。因此,單次 Young GC 時間更多「取決於 GC 後存活物件的數量」,而非 Eden 區的大小。

  • 「降低 Full GC 的頻率」(頻繁的 Full GC 會帶來上下文切換,增加系統的效能開銷)

    • 減少建立大物件

    • 增大堆記憶體空間

  • 「結合業務場景選擇合適的 GC 回收器」

    • 對響應時間有要求的場景,可以選擇響應速度較快的 GC 回收器,CMS 或 G1

    • 對系統吞吐量有要求時,可以選擇 Parallel Scavenge

6.3 FullGC 優化

  頻繁FullGC,會導致服務不可用,應為jvm 垃圾回收器在進行FullGC時,會存在 stop the world 的現象,暫停jvm 中的服務程式進行垃圾回收,會出現服務短暫的不可用,所以需要對fullGC 或 JVM 的引數進行優化,以減小fullGC 的次數。fullGC 優化的方法如下

  1.使用top 命令檢視 伺服器的cpu使用情況

top

  2.獲取 top 中cpu 佔用率最高的程式的pid ,通過 top -H -P pid 獲取該程式對應所有執行緒的使用情況

top -H -p pid

  3.通過上面命令得到使用cpu 最高的執行緒號 threadId ,將執行緒號通過命令轉換為十六進位制

printf "%x\n" threadId

  4.通過以上命令獲取到jvm中對應的 nid , 通過 jstack 檢視該 threadId 執行緒的堆疊資訊

jstack -l pid| grep -10 nid

  通過以上命令判斷該執行緒 執行任務的內容,從而推斷導致cpu飆升的原因。

  5.通過 jstat 命令檢視 FGC 的頻率
  
jstat  -gc   pid  3000 

  需要定位 jvm 記憶體中的堆疊內容與執行緒。通過 Visualm 遠端監控服務的jvm 效能,jvisualm 使用可參考這篇文章https://www.cnblogs.com/zjdxr-up/p/14916455.html),通過 jvisualm 檢視服務當前存在的執行緒和堆內容。

6.4 CMS 垃圾回收器引數配置

-Xmn3072m -Xms8192m -Xmx8192m -XX:NewSize=3072M -XX:MaxNewSize=3072M -XX:-UseAdaptiveSizePlicy 
-XX:ParallelGCThreads=16 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:MaxTenuringThreshold=15  

6.5 G1垃圾回收器引數配置

java -server -Xms2048 -Xmx4096m -Xss512k -XX:MetaspaceSize=128m -XX:MaxNewSize=512m -XX:MaxMetaspaceSize=512m -XX:+PrintGCDataStamps
-Xloggc:/opt/app/gc/logs/gc.log -XX:UseG1GC -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -XX:+HeapDumpOnOutOfMemoryError
-XX:+DisableExplicitGC -XX:+PrintGCDetails -javaagent:/opt/app/common/sky-walking/sky-walking-javaagent.jar

 

7.硬體升級

  硬體問題對效能的影響不容忽視。

  舉一個例子:一個DB叢集經常有慢SQL報警,業務排查下來發現SQL都很簡單,該做的索引優化也都做了,後來DBA同學幫忙定位到問題是硬體過舊導致,將機械硬碟升級成固態硬碟之後報警立馬消失了,效果立竿見影!

 

8.兜底策略

  效能優化做得再好,系統總會存在極限,因此,兜底的策略也是效能優化的一部分,常見的兜底策略有限流、降級和熔斷。很多中介軟體都有這樣的功能,我們應當合理使用。還有我們可以通過減少湧入伺服器的流量來避免高流量對我們伺服器的衝擊,比如接入CDN,利用CDN的節點優化和快取能力能很好的優化我們的效能,當然能使用更高階的邊緣計算技術那麼在某些場景下會有質的飛躍。

 
 
 
 
 

相關文章