線上環境運行一段時間便出現cpu爆滿,致使服務不可用,沒法提供正常服務。shell
發現,進程號爲5525的進程cpu利用率很高。segmentfault
top -H -p 5525
複製代碼
發現前四個線程cpu佔用率很高,並且持有cpu時間很長,因此查看該線程是誰在使用,由於該線程號顯示爲十進制,因此首先將其轉化爲十六進制
printf "%x\n" 5530
複製代碼
接着使用 jstack 命令查看該線程是誰持有
jstack 5525 | grep 159a
複製代碼
發現居然是GC佔用,cpu利用率很高,並且持有cpu很長時間。繼續-發現是GC的問題,接下來查看一下GC的狀態信息
jstat -gcutil 5525 2000 10
複製代碼
能夠看出內存的年輕代和年老帶的利用率都達到了驚人的100%。FGC的次數也特別多,而且在不斷飆升。能夠推斷出 程序確定是在哪裏的實現有問題,須要重點查看大對象或者異常多的對象信息。此時能夠生成headdump文件拿到本地來分析bash
jmap -dump:format=b,file=dump.bin 5525
複製代碼
jstack -l 5525 >> jstack.out
複製代碼
使用eclipse的mat工具分析dump文件,結果以下服務器
點開查看 發現問題出如今阿里雲的oss中 sdk 的 com.aliyun.oss.common.comm.IdleConnectionReaper 類中 根據官方說明:使用方法及mat概念:www.lightskystreet.com/2015/09/01/…eclipse
www.alibabacloud.com/help/zh/doc… 而後分析代碼,哪裏建立的client,建立client的動做在哪裏使用。工具
private OSSClient getOSSClient(){
OSSClient client = null;
client = new OSSClient(endpoint, accessKeyId, accessKeySecret);
return client;
}
複製代碼
結果發現,每次都會建立一個新的對象,建立一個新的oss的連接 而後發現使用的地方oop
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調用的地方改爲了單例。修改完線上跑了一段日子,後來也沒有出現過這樣的問題。ui
由此得出結論:優化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壓測驗證
samples:請求數
average:平均耗時,單位:毫秒
error:錯誤率
複製代碼
// 此處是每秒啓動20個線程,每一個線程發送10個請求
Number of threads(users) : 用戶數,即線程數
Ramp-up period(in seconds) : 線程多長時間所有啓動起來
Loop Count : 每一個線程發送的請求數
複製代碼
兩千個請求,平均每一個請求2.3秒,請求很快,錯誤率很低。 原先寫法的話,當請求到達必定次數,服務器服務已經沒法響應,致使錯誤率上升!並且cpu一直爆滿,其內部維護的client滿了的話,會不斷的觸發GC,因此就致使頻繁GC。