後端介面效能最佳化分析

王鹏鑫發表於2024-06-10

原文連結:https://blog.csdn.net/qq_40851232/article/details/134401234

定位問題

1.慢查詢日誌

通常情況下,為了定位sql的效能瓶頸,我們需要開啟mysql的慢查詢日誌。把超過指定時間的sql語句,單獨記錄下來,方面以後分析和定位問題。

開啟慢查詢日誌需要重點關注三個引數:

slow_query_log 慢查詢開關
slow_query_log_file 慢查詢日誌存放的路徑
long_query_time 超過多少秒才會記錄日誌
透過mysql的set命令可以設定:

set global slow_query_log='ON'; 
set global slow_query_log_file='/usr/local/mysql/data/slow.log';
set global long_query_time=2;

設定完之後,如果某條sql的執行時間超過了2秒,會被自動記錄到slow.log檔案中。

當然也可以直接修改配置檔案my.cnf

[mysqld]
slow_query_log = ON
slow_query_log_file = /usr/local/mysql/data/slow.log
long_query_time = 2

但這種方式需要重啟mysql服務。

2.監控

可觀測性幫助企業在複雜的分散式系統中更加快速的排查、定位問題,已經是分散式系統中必不可少的運維工具。

可觀測性從傳統監控場景不斷延伸,逐漸覆蓋 Metrics、Traces、Logs 三個維度並將之相互融合。在效能壓測領域中,可觀測性更為重要,除了有助於定位效能問題,其中 Metrics 效能指標更直接決定了壓測是否透過,系統最終是否可以上線,具體如下:

Metrics - 監控指標
系統效能指標,包括請求成功率、系統吞吐量、響應時長

資源效能指標,衡量系統軟硬體資源使用情況,配合系統效能指標,觀察系統資源水位

Logs - 日誌
施壓引擎日誌,觀察施壓引擎是否健康,壓測指令碼執行是否有報錯

取樣日誌,取樣記錄API的請求和響應詳情,輔助排查壓測過程中的一些出錯請求的引數是否正常,並透過響應詳情,檢視完整的錯誤資訊

Traces - 分散式鏈路追蹤
用於效能問題診斷階段,透過追蹤請求在系統中的呼叫鏈路,定位報錯 API 的報錯系統和報錯堆疊,快速定位效能問題點。

壓測監控核心指標
壓測過程中,對系統硬體、中介軟體、資料庫資源的監控也很重要,包括系統效能指標、資源指標、中介軟體指標、資料庫指標、前端指標、穩定性指標、批次處理指標、可擴充套件性指標、可靠性指標等。

系統效能指標
交易響應時間
定義 :響應時間指使用者從客戶端發起一個請求開始,到客戶端接收到從伺服器端返回的響應結束,整個過程所耗費的時間。在效能檢測中一般以壓力發起端至被壓測伺服器返回處理結果的時間為計量,單位一般為秒或毫秒。平均響應時間指系統穩定執行時間段內,同一交易的平均響應時間。一般而言,交易響應時間均指平均響應時間。

簡稱 :Response Time: RT

參考標準不同行業不同業務可接受的響應時間是不同的

網際網路企業:500 毫秒以下,例如淘寶業務 10 毫秒左右

金融企業:1 秒以下為佳,部分複雜業務 3 秒以下

保險企業:3 秒以下為佳

製造業:5 秒以下為佳

時間視窗:即整個壓測過程的時間,不同資料量則時間不一樣,例如雙 11 和 99 大促,資料量級不一樣則時間視窗不同。大資料量的情況下,2 小時內可完成壓測

系統處理能力
定義 :系統處理能力是指系統在利用系統硬體平臺和軟體平臺進行資訊處理的能力。系統處理能力透過系統每秒鐘能夠處理的交易數量來評價,是技術測試活動中重要指標

簡稱一般情況下,用以下指標來度量:

HPS(Hits Per Second) :每秒點選次數,單位是次/秒

TPS(Transaction per Second):系統每秒處理交易數,單位是筆/秒。

QPS(Query per Second):系統每秒處理查詢次數,單位是次/秒。對於網際網路業務中,如果某些業務有且僅有一個請求連線,那麼 TPS=QPS=HPS,一般情況下用 TPS 來衡量整個業務流程,用 QPS 來衡量介面查詢次數,用 HPS 來表示對伺服器單擊請求

標準無論 TPS、QPS、HPS,此指標是衡量系統處理能力非常重要的指標,越大越好,根據經驗,一般情況下:

金融行業:1000 TPS~50000 TPS,不包括網際網路化的活動

保險行業:100 TPS~100000 TPS,不包括網際網路化的活動

製造行業:10 TPS~5000 TPS

網際網路電子商務:10000 TPS~1000000 TPS

網際網路中型網站:1000 TPS~50000 TPS

網際網路小型網站:500 TPS~10000 TPS

錯誤率
定義 :錯誤率指系統在負載情況下,失敗交易的機率。錯誤率=(失敗交易數/交易總數)×100%。穩定性較好的系統,其錯誤率應該由超時引起,即為超時率。

標準不同系統對錯誤率的要求不同,但一般不超出千分之六,即成功率不低於 99.4%

資源指標
CPU
定義 :中央處理器是一塊超大規模的積體電路,是一臺計算機的運算核心(Core)和控制核心( Control Unit)。它的功能主要是解釋計算機指令以及處理計算機軟體中的資料。

Memory
定義 記憶體是計算機中重要的部件之一,它是與 CPU 進行溝通的橋樑。計算機中所有程式的執行都是在記憶體中進行的,因此記憶體的效能對計算機的影響非常大

磁碟吞吐量
定義 磁碟吞吐量是指在無磁碟故障的情況下單位時間內透過磁碟的資料量

網路吞吐量
定義 網路吞吐量是指在無網路故障的情況下單位時間內透過的網路的資料數量。單位為 Byte/s。網路吞吐量指標用於衡量系統對於網路裝置或鏈路傳輸能力的需求。當網路吞吐量指標接近網路裝置或鏈路最大傳輸能力時,則需要考慮升級網路裝置

中介軟體指標

資料庫指標

資料庫監控中的命中率通常指的是快取命中率,它表示在資料庫訪問中,請求能夠從快取中獲取所需資料的比例。換句話說,命中率越高,就意味著資料庫查詢所需的資料越多地可以從快取中獲取,而不需要去訪問磁碟或進行其他昂貴的操作。這通常被認為是一個效能指標,因為高命中率通常意味著更快的響應時間和更好的系統效能。

穩定性指標
定義 :最短穩定時間:系統按照最大容量的 80% 或標準壓力(系統的預期日常壓力)情況下執行,能夠穩定執行的最短時間。一般來說,對於正常工作日(8小時)執行的系統,至少應該能保證系統穩定執行8小時以上。對於 7×24 執行的系統,至少應該能夠保證系統穩定執行 24 小時以上。如果系統不能穩定的執行,上線後,隨著業務量的增長和長時間執行,將會出現效能下降甚至崩潰的風險

標準

TPS 曲線穩定,沒有大幅度的波動
各項資源指標沒有洩露或異常情況
可擴充套件性指標
定義 :指應用軟體或作業系統以叢集方式部署,增加的硬體資源與增加的處理能力之間的關係。計算公式為:(增加效能/原始效能)/(增加資源/原始資源)×100%。擴充套件能力應透過多輪測試獲得擴充套件指標的變化趨勢。一般擴充套件能力非常好的應用系統,擴充套件指標應是線性或接近線性的,現在很多大規模的分散式系統的擴充套件能力非常好。

標準

理想的擴充套件能力是資源增加幾倍,效能就提升幾倍。
擴充套件能力至少在70%以上。
目前業界使用比較多的開源監控系統是:Prometheus

它提供了 監控 和 預警 的功能。

我們可以用它監控如下資訊:

介面響應時間
呼叫第三方服務耗時
慢查詢sql耗時
cpu使用情況
記憶體使用情況
磁碟使用情況
資料庫使用情況
它的介面大概長這樣子:

可以看到mysql當前qps,活躍執行緒數,連線數,快取池的大小等資訊。

如果發現資料量連線池佔用太多,對介面的效能肯定會有影響。

這時可能是程式碼中開啟了連線忘了關,或者併發量太大了導致的,需要做進一步排查和系統最佳化。

3.鏈路追蹤

有時候某個介面涉及的邏輯很多,比如:查資料庫、查redis、遠端呼叫介面,發mq訊息,執行業務程式碼等等。

該介面一次請求的鏈路很長,如果逐一排查,需要花費大量的時間,這時候,我們已經沒法用傳統的辦法定位問題了。

有沒有辦法解決這問題呢?

用分散式鏈路跟蹤系統:skywalking。

透過skywalking定位效能問題:

在skywalking中可以透過traceId(全域性唯一的id),串聯一個介面請求的完整鏈路。可以看到整個介面的耗時,呼叫的遠端服務的耗時,訪問資料庫或者redis的耗時等等,功能非常強大。

之前沒有這個功能的時候,為了定位線上介面效能問題,我們還需要在程式碼中加日誌,手動列印出鏈路中各個環節的耗時情況,然後再逐一排查。

問題排查

1.個別介面響應慢

鏈路追蹤

使用SkyWalking,展示出每一個與網路有關的耗時。比如:讀寫資料庫、讀寫Redis、SpringCloud呼叫、Dubbo呼叫等。這樣就能立馬定位是哪次操作耗時了。

同時,SkyWalking可以記錄每一個SQL語句,可以幫助定位。

例如:(如箭頭所指處,最上邊一個是總耗時,下邊的線段是單個操作的耗時)

在鏈路上列印日誌

在相應的鏈路上列印日誌,然後檢視日誌,看是哪個地方耗時。

死鎖問題

在這裡重點講一下出現死鎖問題的排查經驗吧

有這樣的情況:一個執行緒需要同時獲取多把鎖,這時就容易發生死鎖

t1 執行緒 獲得 A物件 鎖,接下來想獲取 B物件 的鎖 t2 執行緒 獲得 B物件 鎖,接下來想獲取 A物件 的鎖

Object A = new Object();
    Object B = new Object();
    Thread t1 = new Thread(() -> {
        synchronized (A) {
            log.debug("lock A");
            sleep(1);
            synchronized (B) {
                log.debug("lock B");
                log.debug("操作...");
            }
        }
    }, "t1");
    Thread t2 = new Thread(() -> {
        synchronized (B) {
            log.debug("lock B");
            sleep(0.5);
            synchronized (A) {
                log.debug("lock A");
                log.debug("操作...");
            }
        }
    }, "t2");
    t1.start();
    t2.start();

定位死鎖

檢測死鎖可以使用 jconsole工具,或者使用 jps 定位程序 id,再用 jstack 定位死鎖:

jps

cmd > jps
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
12320 Jps
22816 KotlinCompileDaemon
33200 TestDeadLock // JVM 程序
11508 Main
28468 Launcher
cmd > jstack 33200
        Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
        2018-12-29 05:51:40
        Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.91-b14 mixed mode):
        "DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000003525000 nid=0x2f60 waiting on condition
        [0x0000000000000000]
        java.lang.Thread.State: RUNNABLE
        "Thread-1" #12 prio=5 os_prio=0 tid=0x000000001eb69000 nid=0xd40 waiting for monitor entry
        [0x000000001f54f000]
        java.lang.Thread.State: BLOCKED (on object monitor)
        at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28)
        - waiting to lock <0x000000076b5bf1c0> (a java.lang.Object)
        - locked <0x000000076b5bf1d0> (a java.lang.Object)
        at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)
        "Thread-0" #11 prio=5 os_prio=0 tid=0x000000001eb68800 nid=0x1b28 waiting for monitor entry
        [0x000000001f44f000]
        java.lang.Thread.State: BLOCKED (on object monitor)
        at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15)
        - waiting to lock <0x000000076b5bf1d0> (a java.lang.Object)
        - locked <0x000000076b5bf1c0> (a java.lang.Object)
        at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

// 略去部分輸出
        Found one Java-level deadlock:
        =============================
        "Thread-1":
        waiting to lock monitor 0x000000000361d378 (object 0x000000076b5bf1c0, a java.lang.Object),
        which is held by "Thread-0"
        "Thread-0":
        waiting to lock monitor 0x000000000361e768 (object 0x000000076b5bf1d0, a java.lang.Object),
        which is held by "Thread-1"
        Java stack information for the threads listed above:
        ===================================================
        "Thread-1":
        at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28)
        - waiting to lock <0x000000076b5bf1c0> (a java.lang.Object)
        - locked <0x000000076b5bf1d0> (a java.lang.Object)
        at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)
        "Thread-0":
        at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15)
        - waiting to lock <0x000000076b5bf1d0> (a java.lang.Object)
        - locked <0x000000076b5bf1c0> (a java.lang.Object)
        at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)
        Found 1 deadlock

這裡是兩個執行緒 “Thread-0” 和 “Thread-1” 在互相等待對方釋放鎖,從而導致了死鎖。

執行緒 “Thread-1” 試圖獲取物件0x000000076b5bf1c0的監視器鎖(即synchronized關鍵字保護的那個物件),但是這個鎖已經被執行緒 “Thread-0” 持有。因此,“Thread-1” 進入了等待狀態,等待 “Thread-0” 釋放這個鎖。

同時,執行緒 “Thread-0” 試圖獲取物件0x000000076b5bf1d0的監視器鎖,但是這個鎖已經被執行緒 “Thread-1” 持有。因此,“Thread-0” 進入了等待狀態,等待 “Thread-1” 釋放這個鎖。

jconsole

2.所有介面響應慢

如果線上出了問題,首先判斷是業務問題還是整個系統的問題。如果是業務問題,就去看應用的日誌等進行排查。如果出現瞭如下問題,就可能是整個系統的問題

  1. 大量介面都很慢
  2. 頁面打不開

如何得知系統出問題了?

系統出問題時,我們需要進行詳細排查,一般情況下,有以下場景我們可以得知線上出問題了:

  1. 使用者反饋功能不能正常使用
  2. 監控系統的郵件或者簡訊提醒

系統問題排查步驟

以下按順序進行

  1. 是否CPU佔用過高
  2. 是否記憶體佔用過高
  3. 是否磁碟佔用過高
  4. 是否網路故障
  5. 檢視後臺日誌
  6. 是否是資料庫問題(比如:索引失效、死鎖)
  7. 是否是垃圾回收導致

CPU佔用過高

什麼場景需要排查CPU佔用?

  1. 訪問介面的響應速度很慢。
  2. 系統崩潰無響應
  3. 壓測時要檢視CPU、記憶體、load、rt、qps等指標

步驟簡述

  1. 定位程序 (命令:top)
  2. 定位執行緒 (命令:top -Hp 程序號)
  3. 定位程式碼位置 (命令:jstack)

排查方法詳述

  1. 找到佔CPU最高的程序。
    1. top命令,記下程序號(PID)。假設最高的是:1893
  2. 透過程序找到對應的執行緒。
    1. top -Hp 1893。假設最高的是:4519
    2. (因為Java是單程序多執行緒的)
  3. 透過執行緒定位到程式碼大概的位置資訊。
    1. printf %x 4519。結果為:11a7
  4. jstack 1893 | grep 11a7 -A 30 –color,結果大概是這樣的:

可能導致CPU使用率飆升的操作

  • 無限迴圈的while
  • 經常使用Young GC
  • 頻繁的GC

如果訪問量很高,可能會導致頻繁的GC甚至Full GC。當呼叫量很大時,記憶體分配將如此之快以至於GC執行緒將連續執行,這將導致CPU飆升。

  • 序列化和反序列化

序列化和反序列化本身不會導致CPU使用率飆升。序列化是將物件轉換為位元組流的過程,而反序列化則是將位元組流轉換回物件的過程。這兩個過程通常在記憶體中完成,並不直接導致CPU使用率的飆升。

然而,在某些情況下,序列化和反序列化可能會導致CPU使用率增加。例如,當需要大量的資料進行序列化或反序列化時,可能會消耗較多的CPU資源。此外,如果序列化和反序列化的操作頻繁且耗時較長,也可能對CPU使用率產生一定影響。

  • 正規表示式

正規表示式本身不會導致CPU飆升,但是使用不當或者複雜的正規表示式可能會導致CPU負載增加。

正規表示式在處理大量文字時,特別是複雜的表示式或者需要進行大量回溯的情況下,可能會消耗大量的 CPU 資源。這是因為正規表示式引擎需要在文字中進行搜尋和匹配,並且有些複雜的規則可能需要大量的計算才能找到匹配的結果。

  • 執行緒上下文切換

記憶體佔用過高

步驟簡述

定位Java程式記憶體使用過高或者記憶體洩漏的問題跟CPU也類似,可分為以下步驟:

  1. 定位程序 (命令:top)
  2. 定位執行緒 (命令:top -Hp 程序號)
  3. 定位程式碼位置 (命令:jmap生成快照,MAT / jprofiler / VisualVM / jhat 檢視快照資料)

3.定位程式碼位置

如果是線上環境,注意dump之前必須先將流量切走,否則大記憶體dump是直接卡死服務。

dump當前快照:jmap -dump:format=b,file=dump.hprof 14279

查詢例項數比較多的業務相關的例項,然後找到相應程式碼檢視。(使用工具檢視dump.hprof。比如:MAT、VisualVM、jhat)

磁碟問題

步驟簡介

  1. 是否磁碟快用完了(命令:df、du)
  2. TPS是否正常(命令:iostat、vmstat、lsof)

這個命令是用來檢查系統的磁碟效能是否正常。具體來說:

  • iostat 命令用於報告 CPU 使用情況和磁碟I/O統計資訊。
  • vmstat 命令用於報告虛擬記憶體統計資訊。

lsof 命令用於列出開啟檔案。

透過使用這些命令,可以獲得關於磁碟 I/O 活動、CPU 使用率、記憶體使用等方面的統計資料。其中,磁碟 I/O 統計資訊中的 TPS(每秒傳輸的 I/O 運算元)可以用來評估磁碟的效能是否正常。如果 TPS 過高或者過低,可能意味著磁碟存在效能問題,需要進一步分析和解決。

網路問題

垃圾回收

什麼情況下,GC會對程式產生影響?

不管Minor GC還是FGC,都會造成一定程度的程式卡頓(即Stop The World:GC執行緒開始工作,其他工作執行緒被掛起),即使採用ParNew、CMS或者G1這些更先進的垃圾回收演算法,也只是在減少卡頓時間,而並不能完全消除卡頓。

那到底什麼情況下,GC會對程式產生影響呢?根據嚴重程度從高到底,包括以下4種情況:

  1. FGC過於頻繁
    1. FGC通常比較慢,少則幾百毫秒,多則幾秒,正常情況FGC每隔幾個小時甚至幾天才執行一次,對系統的影響還能接受。
    2. 一旦出現FGC頻繁(比如幾十分鐘執行一次),是存在問題的,會導致工作執行緒頻繁被停止,讓系統看起來一直有卡頓現象,也會使得程式的整體效能變差。
  2. YGC耗時過長
    1. 一般來說,YGC的總耗時在幾十或者上百毫秒是比較正常的,雖然會引起系統卡頓幾毫秒或者幾十毫秒,這種情況幾乎對使用者無感知,對程式的影響可以忽略不計。
    2. 如果YGC耗時達到了1秒甚至幾秒(都快趕上FGC的耗時了),那卡頓時間就會增大,加上YGC本身比較頻繁,就會導致比較多的服務超時問題。
  3. FGC耗時過長
    1. FGC耗時增加,卡頓時間也會隨之增加,尤其對於高併發服務,可能導致FGC期間比較多的超時問題,可用性降低,這種也需要關注。
  4. YGC過於頻繁
    1. 即使YGC不會引起服務超時,但是YGC過於頻繁也會降低服務的整體效能,對於高併發服務也是需要關注的。

如何排查

  1. 公司的監控系統:大部分公司都會有,可全方位監控JVM的各項指標。
  2. JDK自帶工具:jmap、jstat
    1. 檢視堆記憶體各區域的使用率以及GC情況:jstat -gcutil pid 1000 (重點關注結果中的YGC、YGCT、FGC、FGCT、GCT)
    2. 檢視堆記憶體中的存活物件,並按空間排序:jmap -histo pid | head -n20
    3. dump堆記憶體檔案:jmap -dump:format=b,file=heap pid

後端介面效能最佳化分析-多執行緒最佳化-CSDN部落格

後端介面效能最佳化分析-程式結構最佳化-CSDN部落格

後端介面效能最佳化分析-資料庫最佳化-CSDN部落格

相關文章