問題初始
線上環境運作一段時間便出現cpu爆滿,導緻服務不可用,無法提供正常服務。
排查過程
- 通過top指令檢視程序使用狀況
發現,程序号為5525的程序cpu使用率很高。
- 随即檢視程序号為5525的程序中的線程情況,使用指令:
top -H -p 5525
複制代碼
發現前四個線程cpu占用率很高,而且持有cpu時間很長,是以檢視該線程是誰在使用,因為該線程号顯示為十進制,是以首先将其轉化為十六進制
printf "%x\n" 5530
複制代碼
接着使用 jstack 指令檢視該線程是誰持有
jstack 5525 | grep 159a
複制代碼
發現竟然是GC占用,cpu使用率很高,而且持有cpu很長時間。繼續-發現是GC的問題,接下來檢視一下GC的狀态資訊
- 通過 jstat檢視 gc 狀态(沒10秒gc狀态)
jstat -gcutil 5525 2000 10
複制代碼
可以看出記憶體的年輕代和年老帶的使用率都達到了驚人的100%。FGC的次數也特别多,并且在不斷飙升。可以推斷出 程式肯定是在哪裡的實作有問題,需要重點檢視大對象或者異常多的對象資訊。此時可以生成headdump檔案拿到本地來分析
- 通過 jmap 指令生成dump檔案,down下來進行分析
jmap -dump:format=b,file=dump.bin 5525
複制代碼
- 通過 jstack 指令生成堆棧資訊
jstack -l 5525 >> jstack.out
複制代碼
使用eclipse的mat工具分析dump檔案,結果如下
使用方法及mat概念:www.lightskystreet.com/2015/09/01/…
點開檢視 發現問題出現在阿裡雲的oss中 sdk 的 com.aliyun.oss.common.comm.IdleConnectionReaper 類中 根據官方說明:
www.alibabacloud.com/help/zh/doc… 然後分析代碼,哪裡建立的client,建立client的動作在哪裡使用。
private OSSClient getOSSClient(){
OSSClient client = null;
client = new OSSClient(endpoint, accessKeyId, accessKeySecret);
return client;
}
複制代碼
結果發現,每次都會建立一個新的對象,建立一個新的oss的連結 然後發現使用的地方
public boolean objectExist(String path) {
OSSClient ossClient = getOSSClient();
return ossClient.doesObjectExist(bucketName, path);
}
複制代碼
業務中涉及到檔案存儲的一律都是oss存儲,而且由于曆史原因,有些路徑不是oss路徑,而是本地伺服器路徑,是以在傳回檔案連結時,會進行判斷,
public static String getResourceUrl(String url) {
if (url != null && !url.trim().isEmpty()) {
if (url.trim().toLowerCase().startsWith("http")) {
return url.trim();
}
// 如果oss路徑下能查到,則從oss查
if (url.startsWith("/")) {
url = url.substring(1);
}
// 注釋掉去oss驗證的步驟,不會有性能損耗
boolean objectExist = ossHelper.objectExist(url);
if (objectExist) {
return XTools.getUrl(getOssServer(), url);
}
// 否則從本地查
return XTools.getUrl(getResServer(), url);
}
return "";
}
複制代碼
分析一下,在判斷oss路徑是否存在的時候,也就是調用boolean objectExist = ossHelper.objectExist(url);方法時,每次都會建立一個oss的連結,而我們的檔案是很多的,是以每個檔案傳回通路連結的時候都會調用這個方法,是以建立的oss的連結是巨多的!!! 查詢資料發現,如下網友也中招:
是以大概原因找到了,就是ossClient的連結太多了,扛不住了,是以一直在進行FGC,導緻服務不可用了,最後找到相關的代碼,發現有個小方法裡面在每次上傳或者下載下傳的時候,都會去建立一個ossClient。修改了代碼将ossClient調用的地方改成了單例。修改完線上跑了一段日子,後來也沒有出現過這樣的問題。
segmentfault.com/a/119000001…
代碼優化
由此得出結論:優化ossHelper類中建立client的方法。并且在初始化 ossHelper 的 bean的時候,初始化建立client的連接配接,交給Spring管理。
private static OSSClient ossClient;
private OSSClient getOSSClient(){
if(ossClient == null){
synchronized (this){
ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
}
}
return ossClient;
}
複制代碼
xml:
<bean id="ossHelper" class="com.ybl.crm.common.oss.OssHelper" init-method="getOSSClient">
<constructor-arg index="0" value="${oss.endpoint}"/>
<constructor-arg index="1" value="${oss.access.key.id}"/>
<constructor-arg index="2" value="${oss.access.key.secret}"/>
<constructor-arg index="3" value="${oss.bucket.name}"/>
<constructor-arg index="4" value="${oss.server}"/>
</bean>
複制代碼
注解方式:@Component聲明元件,@PostConstruct聲明需要随元件初始化的方法.
jmeter壓測驗證
經jmeter壓測驗證
samples:請求數
average:平均耗時,機關:毫秒
error:錯誤率
複制代碼
// 此處是每秒啟動20個線程,每個線程發送10個請求
Number of threads(users) : 使用者數,即線程數
Ramp-up period(in seconds) : 線程多長時間全部啟動起來
Loop Count : 每個線程發送的請求數
複制代碼
兩千個請求,平均每個請求2.3秒,請求很快,錯誤率很低。 原先寫法的話,當請求到達一定次數,伺服器服務已經無法響應,導緻錯誤率上升!而且cpu一直爆滿,其内部維護的client滿了的話,會不斷的觸發GC,是以就導緻頻繁GC。
轉載于:https://juejin.im/post/5caed4d36fb9a068985fa527