共計 3245 個字符,預計需要花費 9 分鐘才能閱讀完成。
這篇文章給大家分享的是有關(guān)如何排查線上 CPU 飆高的問題的內(nèi)容。丸趣 TV 小編覺得挺實用的,因此分享給大家做個參考,一起跟隨丸趣 TV 小編過來看看吧。
前段時間我們新上了一個新的應用,因為流量一直不大,集群 QPS 大概只有 5 左右,寫接口的 rt 在 30ms 左右。
因為最近接入了新的業(yè)務,業(yè)務方給出的數(shù)據(jù)是日常 QPS 可以達到 2000,大促峰值 QPS 可能會達到 1 萬。
所以,為了評估水位,我們進行了一次壓測。壓測過程中發(fā)現(xiàn),當單機 QPS 達到 200 左右時,接口的 rt 沒有明顯變化,但是 CPU 利用率急劇升高,直到被打滿。
壓測停止后,CPU 利用率立刻降了下來。
于是開始排查是什么導致了 CPU 的飆高。
問題排查與解決
在壓測期間,登錄到機器,開始排查問題。
本案例的排查過程使用的阿里開源的 Arthas 工具進行的,不使用 Arthas,使用 JDK 自帶的命令也是可以。
在開始排查之前,可以先看一下 CPU 的使用情況,最簡單的就是使用 top 命令直接查看:
top - 10:32:38 up 11 days, 17:56, 0 users, load average: 0.84, 0.33, 0.18 Tasks: 23 total, 1 running, 21 sleeping, 0 stopped, 1 zombie %Cpu(s): 95.5 us, 2.2 sy, 0.0 ni, 76.3 id, 0.0 wa, 0.0 hi, 0.0 si, 6.1 st KiB Mem : 8388608 total, 4378768 free, 3605932 used, 403908 buff/cache KiB Swap: 0 total, 0 free, 0 used. 4378768 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3480 admin 20 0 7565624 2.9g 8976 S 241.2 35.8 649:07.23 java 1502 root 20 0 401768 40228 9084 S 1.0 0.5 39:21.65 ilogtail 181964 root 20 0 3756408 104392 8464 S 0.7 1.2 0:39.38 java 496 root 20 0 2344224 14108 4396 S 0.3 0.2 52:22.25 staragentd 1400 admin 20 0 2176952 229156 5940 S 0.3 2.7 31:13.13 java 235514 root 39 19 2204632 15704 6844 S 0.3 0.2 55:34.43 argusagent 236226 root 20 0 55836 9304 6888 S 0.3 0.1 12:01.91 systemd-journ
可以看到,進程 ID 為 3480 的 Java 進程占用的 CPU 比較高,基本可以斷定是應用代碼執(zhí)行過程中消耗了大量 CPU,接下來開始排查具體是哪個線程,哪段代碼比較耗 CPU。
首先,下載 Arthas 命令:
curl -L http://start.alibaba-inc.com/install.sh | sh
啟動
./as.sh
使用 Arthas 命令 thread -n 3 -i 1000 查看當前 最忙(耗 CPU)的三個線程:
通過上面的堆棧信息,可以看出,占用 CPU 資源的線程主要是卡在 JDBC 底層的 TCP 套接字讀取上。連續(xù)執(zhí)行了很多次,發(fā)現(xiàn)很多線程都是卡在這個地方。
通過分析調(diào)用鏈,發(fā)現(xiàn)這個地方是我代碼中有數(shù)據(jù)庫的 insert,并且使用 TDDL(阿里內(nèi)部的分布式數(shù)據(jù)庫中間件)來創(chuàng)建 sequence,在 sequence 的創(chuàng)建過程中需要和數(shù)據(jù)庫有交互。
但是,基于對 TDDL 的了解,TDDL 每次從數(shù)據(jù)庫中查詢 sequence 序列的時候,默認會取出 1000 條,緩存在本地,只有用完之后才會再從數(shù)據(jù)庫獲取下一個 1000 條序列。
按理說我們的壓測 QPS 只有 300 左右,不應該這么頻繁的何數(shù)據(jù)庫交互才對。但是,經(jīng)過多次使用 Arthas 的查看,發(fā)現(xiàn)大部分 CPU 都耗盡在這里。
于是開始排查代碼問題。最終發(fā)現(xiàn)了一個很傻的問題,那就是我們的 sequence 創(chuàng)建和使用有問題:
public Long insert(T dataObject) { if (dataObject.getId() == null) { Long id = next(); dataObject.setId(id); } if (sqlSession.insert(getNamespace() + .insert , dataObject) 0) { return dataObject.getId(); } else { return null; } } public Sequence sequence() { return SequenceBuilder.create() .name(getTableName()) .sequenceDao(sequenceDao) .build(); } /** * 獲取下一個主鍵 ID * * @return */ protected Long next() { try { return sequence().nextValue(); } catch (SequenceException e) { throw new RuntimeException(e); } }
是因為,我們每次 insert 語句都重新 build 了一個新的 sequence,這就導致本地緩存就被丟掉了,所以每次都會去數(shù)據(jù)庫中重新拉取 1000 條,但是只是用了一條,下一次就又重新取了 1000 條,周而復始。
于是,調(diào)整了代碼,把 Sequence 實例的生成改為在應用啟動時初始化一次。這樣后面在獲取 sequence 的時候,不會每次都和數(shù)據(jù)庫交互,而是先查本地緩存,本地緩存的耗盡了才會再和數(shù)據(jù)庫交互,獲取新的 sequence。
public abstract class BaseMybatisDAO implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { sequence = SequenceBuilder.create().name(getTableName()).sequenceDao(sequenceDao).build(); } }
通過實現(xiàn) InitializingBean,并且重寫 afterPropertiesSet() 方法,在這個方法中進行 Sequence 的初始化。
改完以上代碼,提交進行驗證。通過監(jiān)控數(shù)據(jù)可以看出優(yōu)化后,數(shù)據(jù)庫的讀 RT 有明顯下降:
sequence 的寫操作 QPS 也有明顯下降:
于是我們開始了新的一輪壓測,但是發(fā)現(xiàn),CPU 的使用率還是很高,壓測的 QPS 還是上不去,于是重新使用 Arthas 查看線程的情況。
發(fā)現(xiàn)了一個新的比較耗費 CPU 的線程的堆棧,這里面主要是因為我們用到了一個聯(lián)調(diào)工具,該工具預發(fā)布默認開啟了 TDDL 的采集(官方文檔中描述為預發(fā)布默認不開啟 TDDL 采集,但是實際上會采集)。
這個工具在打印日志過程中會進行脫敏,脫敏框架會調(diào)用 Google 的 re2j 進行正則表達式的匹配。
因為我的操作中 TDDL 操作比較多,默認采集大量 TDDL 日志并且進行脫敏處理,確實比較耗費 CPU。
所以,通過在預發(fā)布中關(guān)閉 DP 對 TDDL 的采集,即可解決該問題。
感謝各位的閱讀!關(guān)于“如何排查線上 CPU 飆高的問題”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!