1. 程式人生 > >快被系統性能逼瘋了?你需要這份效能優化策略

快被系統性能逼瘋了?你需要這份效能優化策略

劉迪偉,就職於世界五百強銀行。負責公司網銀業務系統的設計和交付,擅長並持續關注Java效能優化、DevOps等領域。

XX銀行網銀系統是一套全新的對公業務渠道類系統,經過兩年的建設,將逐步對外提供服務。

該系統融合了原來多個對公渠道系統,併發量是以前多個系統之和,吞吐量要求將大幅上升。為了使廣大對公客戶使用系統時獲得更快的響應時間體驗,專案組對系統進行了持續的效能測試和優化。這一過程中,形成了一套針對新建系統進行效能測試和優化的方法論。

該方法論包括測試環境準備、測試功能優先順序、效能優化原則、常用效能指標及工具、工具使用方法、常見效能問題原因和優化方法,以及典型案例和進一步優化方法的討論。

由於系統已經開發完成,根據效能優化修改範圍儘可能小且不引入更多問題的原則,本文將只討論對系統進行區域性優化的方法,而系統初始設計時為了提高效能而進行的設計不在討論範圍之內。

我們使用Linux作業系統,壓測工具為loadrunner,中介軟體版本包括IHS8.5.5.9、WAS8.5、IBMJDK1.7(IBM J9VM)、DB2V10、Redis3.2.3。

一、應用系統性能評價指標

  • 響應時間:儘快的給使用者返回響應,體現系統處理請求的速度;

  • 吞吐量TPS:每秒完成的事務數,體現系統處理能力;

  • 併發性:業務請求高併發時,系統能否穩定執行;

  • 擴充套件性:單機處理能力不足時,系統能否橫向擴充套件。

TPS = 併發使用者數 / 響應時間

二、常見效能監控指標及工具

1、作業系統監控指標及工具

主要監控指標:CPU、系統CPU、記憶體、磁碟IO、網路IO、請求耗時。

常用命令:

  • top –H –p pid:cpu負載監控,實時檢視佔用cpu高的執行緒;

  • vmstat:系統負載監控;

  • pidstat:cpu讓步式上下文切換監控,監控鎖競爭;

  • iostat:磁碟利用率監控;

  • nmon:監控cpu、記憶體、io使用率 ./nmon -f -t -s 2 -c 100 每2秒採集一次,共100次;

  • netstat -anp|grep埠或IP|grep ESTABLISHED|wc -l:伺服器連線數監控。

2、JVM監控指標及工具

  • Jconsole,監控cpu、記憶體垃圾回收。

JVM啟動引數中增加:

-Dcom.sun.management.jmxremote.port=1088

-Dcom.sun.management.jmxremote.authenticate=false

-Dcom.sun.management.jmxremote.ssl=false

快被系統性能逼瘋了?你需要這份效能優化策略

  • jvisualvm,cpu取樣、java方法耗時分析、jvm執行緒棧快照、監控cpu、記憶體垃圾回收。

CPU抽樣:

快被系統性能逼瘋了?你需要這份效能優化策略

執行緒快照:

快被系統性能逼瘋了?你需要這份效能優化策略

  • jca457.jar,ibm javacore執行緒快照分析:

快被系統性能逼瘋了?你需要這份效能優化策略

  • ga456.jar,ibmjvm垃圾回收gc分析:

快被系統性能逼瘋了?你需要這份效能優化策略

  • ha456.jar,ibmjvm記憶體,heapdump分析,記憶體溢位時使用;

  • TProfiler,cpu取樣、java方法cpu耗時分析,cpu高,響應慢時使用;

  • JProfiler,cpu取樣、java方法cpu耗時分析,cpu高,響應慢時使用;

  • OracleDeveloperStudio12.6-linux-x86,cpu取樣、java方法cpu耗時分析,cpu高,響應慢時使用。

三、效能測試前置條件

1、資料庫表資料量準確

要和生產資料量保持一致,至少一個數量級。資料分佈儘量均勻。

2、測試環境和生產一致

測試環境機器配置、引數、程式碼儘可能和生產保持一致(資料庫伺服器硬體除外)。

3、合理確定併發使用者量

系統併發使用者量有多種演算法可以估算:

  • 平均併發使用者數:

    C=nL/T(n是考察時間內使用者登入數,L是使用者平均線上時間長度,T是考察值時間長度)

    併發使用者數峰值:C’=C + 3*根號C

  • 使用者總量/統計時間*影響因子:

    網銀使用者量100萬,根據2/8原則, 80%使用者在上午9點到11點,下午2點到4點之間登入系統,每次登入耗時1到1.5秒。則併發使用者為:

    1000000*0.8/4/3600*1.5=82.5,

    1000000*0.8/4/3600*1=55

  • 根據系統使用者數計算:

    併發使用者數=系統最大線上使用者數的8%到12%

  • 根據TPS估計:

    C=(Think Time + 1)* TPS,

    網銀使用者思考時間10s,

    C=(10+1)* 3=33。

4、預估各功能交易量,確定壓測功能優先順序

根據交易量從大到小排名,排名靠前的優先壓測。

5、設定效能問題認定標準

比如響應時間超過3s、TPS低於10、伺服器cpu佔用率超過70%、jvm堆記憶體使用100%、垃圾回收頻繁、網路IO或磁碟IO達到瓶頸等……都可能是效能問題。

四、效能優化一般思路

1、找到效能瓶頸

效能瓶頸定義:導致系統TPS低、響應時間長、資源(CPU、記憶體、網路)佔用高等問題的關鍵程式模組。提升該程式模組的效能,可以大幅度改善效能。

常見的效能瓶頸原因包括:資料庫慢查詢SQL、日誌列印、xml大報文解析和格式轉換、複雜業務邏輯、鎖競爭等。

2、如何找到效能瓶頸

  • 使用LoadRunner給每個介面的增加事務,記錄其響應時間和TPS,最慢的那個介面往往是瓶頸;

  • 分析慢交易的日誌,檢視是哪個操作耗時最長;

  • 分析資料庫快照,看是否有執行較慢或者全表掃描的SQL;

  • 通過Javacore檢視執行緒正在執行的程式碼,是大部分阻塞在IO上,還是大部分在進行計算。針對不同的問題,使用不同的分析工具。詳細內容可以看下一章節。

3、針對性能瓶頸進行合理優化

效能優化原則:

  • 先優化瓶頸問題;

  • 方案簡單,儘量不引入更多複雜性,儘量不降低業務體驗;

  • 滿足系統性能要求即可,不引入新的bug。

五、常見問題及優化方法

1、SQL執行時間長

問題現象:系統響應時間長、資料庫cpu高。

問題原因:全表掃描、索引低效、排序溢位。

解決方法:

  • 通過DB2資料庫快照檢視執行時間長的SQL,檢視該SQL執行計劃,在cost比較高的SQL上增加合適索引。要求所有大表SQL執行計劃的cost低於100,超過100的SQL要評審。對於排序溢位、可參考資料中心規範設定排序堆大小,規範中排序堆設定為AUTOMATIC。

  • 制定資料庫表清理策略。根據資料的生命週期要求,對流水類的資料定期進行清理備份,不再長期保留。定期對全庫的表結構進行reorg、runstats操作,以提高索引效率。

排查方法:

  • 獲得資料庫快照:db2 get snapshot for all on corpdb >gswyzfzzshpl1207.log

  • 從快照中提取慢SQL,Toad檢視SQL執行計劃

  • Db2命令方式檢視SQL執行計劃:db2expln -d corpdb -t -g -q "SQL語句"

  • 執行計劃檢視方法:自上而下檢視cost最大的分支,找到未走索引或索引使用不當的表

2、資料庫出現死鎖

問題現象:資料庫快照檢測到存在資料庫死鎖,或通過db2evmon -db corpdb -evm DB2DETAILDEADLOCK>dlock.txt生成死鎖監控檔案,快照或監控檔案中存在deadlock的情況。

問題原因:全表掃描、大事務、更新相同表記錄的SQL執行順序交叉等。

解決方法:縮短事務路徑長度,避免全表掃描。如果必須存在大事務,則更新相同表的SQL執行順序一致,並且堅決避免全表掃描。網銀系統指令傳送功能全表掃描+全域性大事務,導致資料庫死鎖。

排查方法:根據死鎖監控檔案dlock.txt找到導致死鎖的SQL,以及該SQL持有的鎖,分析該SQL可能存在的問題。

3、執行緒阻塞在日誌記錄上

問題現象:系統響應時間長、通過javacore檢視很多執行緒阻塞在列印日誌上。

問題原因:log4j1.x版本較低,效能較差;大報文日誌多次輸出。

解決方法:

  • 減少無效日誌、刪除無用日誌,減少大日誌輸出。

  • 升級log4j元件到log4j2,參考log4j2官方文件,配置合理的日誌緩衝區,採用高效的Appenders,比如RollingRandomAccessFile。但log4j2仍然採用同步日誌,不採用非同步日誌。因為網銀系統日誌量較大,非同步日誌佇列很快就滿了,如果單條日誌存在大報文,還有可能導致記憶體溢位,因此不適合採用非同步日誌。如果日誌量少(壓測產生日誌的速度,低於日誌寫入檔案的速度),則可以使用非同步日誌,大幅提高效能。如果日誌量較大,則不建議使用非同步日誌。

排查方法:

  • JVM啟動引數中增加-XX:+HeapDumpOnCtrlBreak,壓測進行時,kill -3 pid 殺幾個javacore,使用jca457.jar工具開啟並分析。推薦使用該工具,因為該工具可以對所有執行緒狀態進行統計,並生成餅狀圖,方便檢視。

  • 壓測進行時,使用jvisualvm獲取jvm快照,分析執行緒堆疊。

4、多執行緒併發問題

問題現象:採用合理的併發數壓測,系統出現邏輯錯誤、交易失敗或異常報錯。經查是由於物件中變數被異常修改導致。

問題原因:系統中全域性物件中的類變數或全域性物件,被多個執行緒修改。

解決方法:排查系統中所有持有全域性物件或類變數的程式碼,檢查其全域性變數是否可能被多個執行緒並行修改。

修改方法:

  • 將全域性變數轉成方法內的區域性變數;

  • 對全域性變數進行同步控制比如syncronized程式碼塊,或者java.util.concurrent鎖。

排查方法:併發問題很可能是由全域性變數或者物件導致,準確識別全域性變數,通過閱讀程式碼找問題。建議應用梳理所有可能存放全域性物件的程式碼,統一管控,或者把所有全域性物件放到一個類中,方便管理。

5、打開了太多檔案

問題現象:採用合理的併發數壓測,交易失敗,或後臺日誌報錯:To many open files。

問題原因:

  • 讀取配置檔案或者業務資料檔案後,未關閉檔案流;

  • /etc/security/limits.conf中最大開啟檔案數配置過小。

解決方法:

  • 使用lsof –p pid 命令檢視程序開啟的檔案,如果大部分檔案都是同一型別的檔案,說明可能未關閉檔案流。找到開啟檔案的程式碼,關閉檔案流即可。

  • 如果不存在未關閉檔案流的問題,且業務本身就需要處理大量檔案,則修改/etc/security/limits.conf檔案如下內容:

* hard nproc 10240

* soft nproc 10240

6、記憶體洩漏

問題現象:JVM記憶體耗盡,後臺日誌丟擲OutOfMemeryError異常 ;

問題原因:記憶體溢位問題可能的原因比較多,可能是全域性的List、Map等物件不斷被擴大,也可能是程式不慎將大量資料讀到記憶體裡;可能是迴圈操作導致,也可能後臺執行緒定時觸發載入資料導致。

解決方法:對於ibmjdk純java應用,在jvm啟動時設定-XX:+HeapDumpOnOutOfMemory Error引數,會在記憶體溢位時生成heapdump檔案。使用ha456.jar工具開啟heapdump檔案,分析大物件是如何產生的。

當然,在heapdump中物件型別可能只是List這種結構,看不出具體哪個業務程式碼建立的物件。此時要分析所有的全域性物件,列出可疑的List或Map物件,排查其溢位原因。

全域性物件、引用的初始化、修改要慎重。建議應用梳理所有可能存放全域性物件的程式碼,統一管控。

7、JVM垃圾回收頻繁

問題現象:top –H –p pid命令檢視,GC Slave執行緒CPU佔用排名始終為前三名,同時Jconsole檢視jvm記憶體佔用較高,垃圾回收頻繁。使用ga456.jar分析gc日誌,檢視gc頻率、時長。

列印gc日誌的方法:在jvm啟動引數裡增加-verbose:gc -Xverbosegclog:/usr/ebank/ logs/cobp/gcdetail.log

問題原因:高併發下,記憶體物件較多,jvm堆記憶體不夠用 。

解決方法:擴大堆記憶體大小–Xmx2048m –Xms2048m。

8、CPU高

問題現象:50併發壓測,監控工具顯示bp、前置CPU佔用90%以上。

問題原因:業務處理中存在大量CPU計算操作。

解決方法:採用更高效的演算法、資料結構替換原來消耗CPU的程式碼,或者採用新的設計繞過瓶頸程式碼,比如查詢資料的邏輯,可以把List改為Map,以空間換時間;比如用Json報文替換XML報文,提高傳輸、解析和列印日誌的效率。

導致Cpu計算資源高消耗的程式碼:報文格式轉換、加解密、正則表示式、低效的迴圈、低效的正則表示式。

排查方法:

  • 壓測進行時,使用jvisualvm工具遠端連線應用,點抽樣器àCPU,點快照生成執行緒快照。取樣一段時間後,抽樣器會顯示各個方法佔用cpu時間,可以針對CPU時間佔用高的方法進行優化。

  • 使用tprofiler,jprofiler,OracleDeveloperStudio12.6-linux-x86工具分別分析消耗CPU時間長的方法,以上工具分析結果可能有些差別。針對CPU計算耗時最長的方法進行優化。

9、批處理時間長、資料庫逐筆插入緩慢

問題現象:大批量資料(10萬條以上)更新或插入資料庫,耗時較長。

問題原因:批量資料處理時,如果逐條更新資料庫,則會存在大量網路io、磁碟io,耗時較長,而且對資料庫資源消耗較大。

解決方法:

  • 採用java提供的batchUpdate方法批量更新資料庫,每1000條commit一次,可大幅提高資料更新效率。

  • 單執行緒改成執行緒池,並行處理,充分利用多核CPU,通過資料庫或者其他同步鎖控制並行性;增加緩衝池,降低資料庫或磁碟IO訪問頻次。

10、資料庫CPU高

問題現象:後臺指令傳送滿負荷工作時,資料庫CPU高。

問題原因:後臺指令傳送執行緒每次對全量查詢結果排序,結果集很大,然後取一條記錄;索引區分度不高,滿負荷執行時;查詢頻率很高;壓測顯示,並行傳送指令的後臺執行緒越多,資料庫CPU越高,效率越低。

解決方法:

  • 去掉ORDER BY,增加索引後,效果不明顯。因為結果集大和查詢頻繁兩個問題沒有解決,因此考慮使用設計新的方案。

  • 新方案:設計指令傳送執行緒池,生產者執行緒每臺任務伺服器只有一個執行緒,負責查詢待發送指令,每次查詢50條指令。每條指令包裝成一個Runnable物件,放進ThreadPoolExecutor執行緒池,執行緒池大小引數設定為100或200。每當執行緒池滿時,生產者停止生產指令,休息15秒後繼續。消費者執行緒即執行緒池裡的執行緒,引數設定為4,8或12(和不同指令型別的指令資料量成正比)。

改進後的方案,資料庫CPU降到10%一下,傳送效率單機提升6倍,且可線性擴充套件任務伺服器。

11、壓測TPS曲線劇烈下降或抖動

問題現象:50併發壓測,TPS曲線正常應該是平緩的,波動不大,如果突然出現劇烈下降,並且短時間內無法恢復,則可能存在問題。

問題原因:一般是由於前置或bp的jvm進行垃圾回收,或者日誌記錄磁碟滿導致的。

解決方法:如果不是特別劇烈的波動或者TPS曲線下降後長時間不反彈,則可以忽略該問題。否則,需要分析曲線下降的時刻,系統當時正在發生的事情。可以通過top命令監控當時CPU佔用比價高的執行緒,也可以kill -3 pid殺javacore來檢視執行緒堆疊。

六、優化案例

1、網銀系統基本情況

1) 壓測環境系統架構

壓測環境不包括F5,只有1臺WEB、1臺前置(應用伺服器)、1臺BP(業務處理伺服器)、1臺DB、1臺Redis伺服器。

2)客戶請求鏈路:

客戶端壓力機-〉Web-〉PRE-〉BP-〉DB2 ;

客戶端壓力機-〉Web-〉PRE-〉BP-〉擋板伺服器(模擬後端服務方)。

3)系統特點:

  • 使用者、賬號許可權校驗較多。

    對公業務典型場景:經辦、稽核、查詢、下載、批量。使用者許可權通過業務鏈控制。

  • 介面呼叫較多,且由前端組合呼叫介面。

    前後端分離,前端通過呼叫一個個介面來完成業務。介面粒度較細。登陸、支付轉賬經辦需要15~19個介面完成一次交易。

  • 強一致性、高可用性。

    資金交易系統,一致性需要通過資料庫來保證。

4)網銀登入壓測曲線

快被系統性能逼瘋了?你需要這份效能優化策略

2、資料庫訊息佇列案例(指令傳送佇列)

需求是:對於需要非同步處理的交易指令,需要設計一個基於資料庫的訊息佇列,我們稱之為指令傳送佇列。該佇列既要滿足伺服器的效能約束,又要滿足每日處理交易量的要求。

1)優化前處理過程

後臺任務每次從資料庫指令表中排序並取最早的一條指令,獲取該指令的詳細交易資訊,組裝成報文並呼叫介面傳送後臺核心系統。

在壓測環境下,該方案如果配置一個後臺任務,無法達到系統設計的指令處理速度;如果配置多個後臺任務,則會導致資料庫CPU佔用較高,影響其他聯機業務的開展。

2)優化後處理過程

每臺後臺任務伺服器配置一個生產者任務,20個消費者任務。

生產者任務一次取100個(可配置)指令,依次分配給20個消費者任務中空閒的任務,消費者任務獲取指令詳細資訊,並組裝報文後傳送後臺核心系統;生產者任務如果發現無空閒消費者任務,則等待15s後重新判斷。

此方案顯著降低了資料庫CPU負載,合理利用了應用伺服器的併發能力,處理效率大為提高,以上執行緒數和每次獲取的指令數可以配置。

指令傳送執行緒池模型圖:

快被系統性能逼瘋了?你需要這份效能優化策略

3、資料庫死鎖案例

1)死鎖問題現象

當多臺任務伺服器同時執行大額指令傳送後臺執行緒,即多個生產者執行緒並行更新資料庫指令表時,資料庫快照檢測到存在資料庫死鎖,或通過db2evmon -db corpdb -evm DB2DETAILDEADLOCK>dlock.txt 生成死鎖監控檔案,快照或監控檔案中存在deadlock的情況。

DB2資料庫有自動解除死鎖功能,死鎖超時時間預設為10s,資料庫會隨機選擇一個死鎖事務kill掉。本案例由於是後臺任務,所以使用者感覺不到死鎖;如果是聯機交易,一個使用者會發現交易失敗,另一個使用者交易成功,但是會感覺交易變慢。指令傳送後臺任務模型詳見下一章節內容。

2)資料庫死鎖發生原理

兩個不同的資料庫事務使用排它鎖鎖住了同一張表的不同行記錄,並且互相等待讀取對方鎖住的行記錄。

快被系統性能逼瘋了?你需要這份效能優化策略

3)導致死鎖的可能原因

全表掃描、大事務、事務之間對死鎖訪問順序交叉等。

4)死鎖問題排查過程

Step1:分析資料庫快照和死鎖監控日誌,檢視導致死鎖的SQL,定位問題SQL。

Step2:問題SQL不存在事務之間對死鎖訪問順序交叉的情況,當時尚不清楚程式中的全表掃描、大事務可能會導致死鎖,因此做了以下實驗:

  • 模擬問題SQL在兩個不同的資料庫客戶端執行SQL,檢視資料是否更新成功。

  • 完全按照源程式的SQL邏輯執行驗證:

UPDATE ( SELECT BP_SRVR_IP FROM ${tableName} WHERE TSK_STAT='TODO' AND ( BP_SRVR_IP IS OR BP_SRVR_IP='') AND PRTY =? AND eff_tm <= CURRENT TIMESTAMP FETCH FIRST 50 ROWS ONLY WITH RS) t SET BP_SRVR_IP=?

  • 不加索引,A事務在R上加了X鎖,B事務無法在任何記錄上加X鎖,B事務會等待A事務提交後再加鎖。

  • 增加索引,A事務在R記錄加了X鎖,B事務在S記錄加X鎖,互不衝突。

Step3:實驗發現查詢SQL增加with RS隔離級別,查詢效率會更高。當A事務已對記錄R加X鎖,B事務掃描到R記錄時,如果是CS隔離級別,B事務會自動退出,返回空結果集;如果是RS隔離級別,B事務會等待A事務完成後,跳過R記錄,對符合條件的R+1記錄加X鎖。

Step4:PRTY欄位增加索引,沒有出現死鎖問題;或者不加索引,維持全表掃描不變,大事務改成小事務後,也沒有出現死鎖問題。

5)兩點經驗

  • 需要提前熟悉DB2鎖的種類和作用,隔離級別的種類和作用,表鎖和行鎖發生的條件。比如,全表掃描時,DB2會在整個表上加表級鎖;如果是增刪改操作,會加表級排他鎖。

  • 在不清楚死鎖原因時,或者不瞭解鎖的機制和隔離級別機制時,不同隔離級別下SQL的影響範圍,可以在資料庫客戶端工具上進行手工小實驗,驗證資料庫機制以及猜想。

七、後續提升網銀系統性能備選方法

1、增加快取的使用

  • 對於讀多寫少的資料,可以載入到分散式快取,降低資料庫壓力;

  • 目前已經將部分引數和錯誤碼資料放到分散式快取,後續謹慎提高快取使用率,降低資料庫壓力。

2、精簡BP日誌。刪除交易訪問記錄日誌表的操作。

3、合併、精簡介面數量,前端快取資料。