性能壓測是當服務上線前,或者之後重要需求發佈流程中,需要做的必要測試;以模擬真實流量的方式,獲取當前系統的性能指標、是否存着高併發隱患、瓶頸等信息的手段。
性能壓測處於什麼位置?或者說什麼時候去做呢?如何做?
如何做好線上環境的性能壓測、全鏈路壓測,如何做到壓測結果準確無誤,不影響外部環境、不污染數據,需要深入思考。
一、測試分類
按照上線流程:
- 單元測試
- 集成測試
- 兼容性測試(前端、客戶端)
- 性能測試
運維階段:
- 安全測試
- 容災測試(故障恢復測試)
由上可以看出,性能測試是應用服務在上線過程中的一個流程,除非是內部系統,訪問量少,否則都應當做一下性能測試。
安全測試又叫滲透測試,是運維和研發需要關注的問題;尤其是對數據安全要求高的系統。當一個服務穩定運行後,模擬一些攻擊手段,防止SQL注入、網絡攻擊、腳本攻擊等。
容災測試是容災方式的驗證,容災方式包括:雙機房建設、異地多活、兩地三中心。
二、壓測目的
- 瞭解吞吐量
- 瓶頸值
- 系統隱患
可用:一次請求,到達服務器,有迴應;那麼針對這次請求,服務是可用的,無論返回成功或者失敗。
不可用是指請求沒有返回,長時間等待,沒有應答,客戶端不知道結果。
高可用級別:99%、99.9%、99.99%、99.999%
大家經常說的系統可用性達到三個9,就是指99.9%。
高可靠:是指服務可靠,數據可靠。請求返回是成功,數據寫入正確,分佈式環境下數據一致性等。
三、壓測指標
四、實現流程
1. 壓測工具選型
2.壓測環境準備
- 服務環境
- 壓測機
- 數據庫
- Redis
- MQ
3.壓測數據準備
- 日活數據:數據要真實、流量真實,結果才能越接近線上真實性能指標。
- 數據隔離:防止污染真實用戶數據
- 數據恢復
- 熱點數據
- 壓測預熱:儘量模擬線上真實環境情況,如緩存命中率
4.壓測開發設計
- 擋板
- Mock數據
- 流量識別
- 壓測場景
- 加壓策略
擋板:是用來攔截調用第三方的請求,防止壓測對第三方造成影響;提前設計好擋板邏輯,識別壓測流量,執行擋板邏輯,並且爲了真實,還可以合理延遲一定時間再返回Mock數據。
流量識別:nginx做負載均衡,upstream 轉發服務器ip 時,可以獲取http請求Header的參數,我們可以給壓測流量header加標記參數,nginx獲取參數判斷後,轉發壓測流量到壓測機器。
5.監控
監控很重要,可以說是壓測中最爲重要的地方,如果沒有監控,或者監控不準確,會嚴重影響壓測本身準確性。監控數據不詳細,也會影響性能調優。
一般我們需要監控的如下:
- CPU、內存
- IO:網絡、磁盤
- Http請求、接口方法
- JVM
- 中間件
- 數據庫負載
- 帶寬佔用
其中對接口和方法的監控需要有,而且要準確,是定位性能問題的有效手段,越細化越好。
以下是一些監控工具:
1. Grafana 監控 (CPU、內存、成功率、http請求的RT)
2. Sentinel 監控 (接口、方法級別)
3. Visualvm 監控JVM
通過上圖可以直觀的看出JVM堆內存的增長情況和GC回收頻率、回收後內存大小。如果回收後的內存在持續增高,且當前請求沒有增長,說明有內存泄露,具體什麼對象泄漏,需要通過jmap等命令,查看堆內存heap的詳情。
如以下命令:
對比GC前後的對象數量,分析是哪些沒有回收。
五、性能優化
注意:
- 慎重優化,謹記二八原則 和 Amdahl定律。
- 優化一定要伴隨着壓測,通過性能指標、數據對比來論證和調整,否則沒有意義。
優化目標
- 提升吞吐量
- 降低 RT
優化方向
- 硬件層面:CPU、內存擴容、換SSD固態硬盤
- 軟件層面:集羣擴容、代碼優化、存儲優化(緩存、數據庫)、減少IO
優化方式
- 代碼優化:請求合併、異步、Cache、池化
- JVM調優
- Linux調優
JVM調優
1. 調優關注點
- 減少JVM不可用時間:STW(Stop the word)
- GC 頻率
- 安全點SafePoint
安全點是需要注意的問題,很多初學者會忽略安全點線程等待時間,其實STW時間包括安全點等待時間。
2. 常用GC回收器
- ParNew + CMS (低延遲、低核、小內存場景)
- G1 (高吞吐量、高核、大內存)
G1只有在多核CPU,大內存情況下,才能體現出它的優勢,如果內存過小,表現反而不如ParNew+CMS。
那麼到底多大算大內存呢?
根據R大給出的建議是:以堆內存8g爲限,大於8g用G1,小於8g用CMS。
3. JVM參數分類
- -:標準參數,所有JVM都必須實現,且向後兼容。
- -X :非標準參數,默認JVM實現該參數,不保證向後兼容。
- -XX:非stable參數,不同JVM有所不同,將來可能會取消。
4. G1關鍵參數
-Xms16g -Xmx16g
-XX:+UseG1GC -XX:MaxGCPauseMillis=50 -XX:InitiatingHeapOccupancyPercent=60
-verbose:gc -Xloggc:logs/gc_%p.log -XX:+DisableExplicitGC
-XX:-UseLargePages -XX:+PrintGCApplicationStoppedTime -XX:-OmitStackTraceInFastThrow
-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1
需要注意的是,G1最好不要加新生代大小參數 -Xmn ,如果固定了新生代大小,G1就沒法根據 -XX:MaxGCPauseMillis 設定的最大期望GC時間,動態調整堆內存的新生代和老年代大小,相當於-XX:MaxGCPauseMillis 失效了。除非你很熟悉G1原理,加了新生代大小,配合其他參數,也可以調優出一個不錯的結果。否則還是交給G1本身的自動調整吧。
5. GC 日誌
2020-01-09T11:05:34.836+0800: [GC pause (G1 Evacuation Pause) (young) ……
[Eden: 8576.0M(8576.0M)->0.0B(8576.0M) Survivors: 16.0M->16.0M Heap: 8643.7M(14.0G)->68.2M(14.0G)]
[Times: user=0.05 sys=0.04, real=0.02 secs]
2020-01-09T11:05:34.863+0800: 2043140.818: Total time for which application threads were stopped:0.0286394 seconds, Stopping threads took: 0.0002674 seconds
2020-01-09T12:01:10.488+0800: 2046476.443: Application time: 3335.6250688 seconds
2020-01-09T12:01:10.490+0800: 2046476.444: [GC pause (G1 Evacuation Pause) (young) 2046476.444:
gc日誌中 Stopping threads took 就是安全點時,線程的等待時間,所以STW時間應該是:GC時間 + 安全點等待時間。
如果 Stopping threads took 較大,說明代碼程序是有問題的,需要深入查看;
具體什麼情況會導致安全點時間過長,請參考:
HBase實戰:記一次Safepoint導致長時間STW的踩坑之旅
Linux調優
我在對業務項目、RocketMQ、Redis、codis集羣、Elasticsearch、Hbase等進行優化過程中,經常遇到一些相同的問題,我總結了下,主要有以下:
- 慎用交換區swap
優化方式:禁止swapping 或者設置 swappiness = 1
swapping會導致gc過程從毫秒級變成分鐘級
- 善用 /dev/shm/
/dev/shm/ 是linux的文件內存系統,是共享內存,直接從物理內存中開闢,根據使用情況會動態擴容。
針對一些寫入文件,IO性能要求高的場景,可以使用/dev/shm/,例如gc日誌存放。
高併發場景下,如果打開安全點日誌,安全點日誌是在安全點內打印,是同步的,所以日誌寫入文件速度,會嚴重影響gc快慢;應當將gc日誌配置改爲:
-Xloggc:/dev/shm/gc_%p.log
其他linux調優,一般大點的公司,都會有自己的運維團隊,運維就會做基本的優化,例如tcp連接優化等,這裏只介紹運維可能忽略的,不知道的點,這時候就需要研發同學介入,找運維做好應用相關的Linux內核優化。