1、現象
2月11日閘道器在短時間內出現20+的訪問出錯,檢視kibina如下:
根據trace得到具體的堆疊異常,發現都是負載均衡同一個pod均顯示連線異常:
去grafana檢視該pod執行情況,可以發現記憶體突然下降的情況,同時該pod已經達到記憶體上限(8G),當資源申請不到的情況下,該pod可能存在重啟的情況:
檢視ad服務的日誌驗證了當時確實發生了重啟:
此時可以基本定位到因為記憶體問題使得服務重啟,從而閘道器無法負載均衡到該服務例項導致閘道器報錯,接下來需要明確是什麼原因讓該pod的記憶體佔用如此之高。
二、記憶體問題定位
- 根據grafana的圖表,可以看出堆記憶體比較正常,而非堆記憶體出現了異常(使用率超過100%):
非堆就是JVM留給自己用的,方法區、JVM內部處理或優化所需的記憶體(如JIT編譯後的程式碼快取)、每個類結構(如執行時常數池、欄位和方法資料)以及方法和構造方法 的程式碼都在非堆記憶體中。
-
用JProfiler開啟該檔案(也可以用jVisualVM),找到Biggest Objects,然後發現在存活物件中存在大量的AdDto這個類的例項,大量這些物件整整佔用了1G多:
三、程式碼檢查
回到程式碼中,檢查AdDto的生成方式,發現用瞭如下的操作:
modelMapper每一次呼叫addMapping都將建立一份該類的結構(通過位元組碼然後由類載入器載入),查閱官網相關文件。如果轉換型別確定,應該將ModelMapper設定成單例(modelmapper.org/user-manual…
四、線下重現
在普通介面中用這段程式碼驗證猜想,可以很明顯看到非堆記憶體一路猛漲,並且載入類的數量也在一路上升,基本證實了問題:
五、修復並驗證
將程式碼改成如下形式並重新壓測:
private static ModelMapper modelMapper;
static {
modelMapper = new ModelMapper();
TypeMap<Person, PersonDTO> typeMap = modelMapper.createTypeMap(Person.class, PersonDTO.class);
typeMap.addMappings(mapper -> {
mapper.skip(PersonDTO::setAge);
});
}
@GetMapping("api/benchmark/qa")
public PersonDTO qa() {
Person p = new Person();
p.setName("wangao");
p.setAge(30);
return modelMapper.map(p, PersonDTO.class);
}
複製程式碼
發現非堆記憶體穩定,類載入數量穩定: