專案正式上線之後,後期主要是不斷地進行版本迭代,開發新的功能。自己參與
開發的專案正式開始使用後,人數還不少,早上高峰期的時候一個介面一個小時的請求
數達到約3萬。而且這只是部分使用者在進行使用,還沒有大規模地放開,伺服器已經
開始告警,某一個介面的查詢超過四五秒。收到這個資訊後,負責人立馬讓我們檢視
日誌資訊,排查問題。透過命令 grep “介面請求的URL” 日誌檔名,檢視多臺服務
器上面列印的日誌資訊,發現確實有多臺伺服器上列印的介面耗時都超過5S以上。
檢視日誌的方式是自己在網上搜尋的,可是介面耗時卻是系統中寫的,如何衡量一個
介面的好壞?其中一個指標就是處理請求的能力,可以透過jmeter來做介面的效能測試。
這個在測試階段,專業的測試人員都已經測試過,肯定是符合要求我們才上生產。可是
現在已經是服務在生產上面跑,在生產階段,不能在使用那種方式來處理。對於後端
開發人員來說,可以在需要的介面請求中,列印每一次介面的耗時。簡單處理方式如下:
首先需要定義一個攔截器:
@Slf4j
public class InterfaceInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
request.setAttribute("REQUEST_START_TIME", String.valueOf(System.currentTimeMillis()));
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String requestStartTime = (String)request.getAttribute("REQUEST_START_TIME");
log.info("介面 {} 請求耗時: {} ms", requestURI, System.currentTimeMillis() - Long.parseLong(requestStartTime));
}
}
重寫兩個方法,一個方法是在處理請求前呼叫的preHandle方法,處理邏輯很簡單,設定一個固定的變數值,
具體的值設定為當前的時間戳,將其放在請求物件中。介面請求處理完成之後,呼叫afterCompletion方法,
將之前存入的值取出來,轉換為Long型別,然後用當前時間時間戳減去最開始請求的時間戳,就可以計算出單個介面處理的耗時。
接下來寫一個簡單的測試方法,如下
@Slf4j
@RestController
@RequestMapping("/happy/yilang")
public class TestControlelr {
@GetMapping("/interface")
@LcloudThreadLimiter(maxThread="max_thread", waitTime="wait_time")
public String interfaceTest(){
return "介面耗時統計";
}
}
最後將攔截器新增到攔截器配置類中,即是註冊攔截器,攔截的指定的路徑為/happy/**,可以
按需要進行靈活的配置,如下
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
/**
* Function: addInterceptors
* Author : kaye0110,
* Version : 1.0
* Description : 註冊攔截器
* Param and Description :
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(InterfaceInterceptor()).addPathPatterns("/happy/**");
super.addInterceptors(registry);
}
@Bean
public InterfaceInterceptor InterfaceInterceptor(){
return new InterfaceInterceptor();
}
}
啟動專案,測試結果如下
拿到每一次介面請求的耗時資料,對於效能要求是一個重要的參考指標。每個公司的要求都不一樣,有的可能要求1s內返回,
有的可能要求500ms內返回,有的可能要求20ms內返回。根據公司自己的要求,然後來對比介面的耗時,就可以判斷出這個
介面的效能如何,是否需要最佳化。
自己在排查日誌的過程中發現,這個介面就是一個簡單的介面,沒有做比較複雜的計算操作,就是根據主鍵ID查詢一條資料
資訊,為什麼會導致這麼慢呢?怎麼進行最佳化呢?之後經過仔細分析,發現在請求高峰期的時候,資料庫的CPU達到90%多,
因為資料庫的效能急劇下降。負責人經過檢視資料庫伺服器的相關資訊,普通開發人員沒許可權看,還發現MQ在快速的大量的
向資料庫中寫資料, 還有定時任務也在頻繁的向資料庫寫資料,所以導致資料庫的效能解決下降。由於起了多臺伺服器,定時
任務的處理就更加地頻繁,如果每間隔兩分鐘處理一次定時任務,5臺伺服器兩分鐘就處理10次。因此就找到對應的解決方案:
.1.擴容資料庫;.2.這個介面實時查詢資料庫修改為從快取中取資料;.3.增加定時任務的處理時間間隔;4.降低MQ訊息消費的流量。
定時將需要查詢的所有資料,預先載入到快取中,然後查詢的時候就直接從快取當中取,不再去查詢資料庫。透過這幾步的最佳化,
最終很好地解決了這個介面的效能問題。
如果有其他更好建議的小夥伴,歡迎留言討論。