OLAP 分析系統,分邏輯服務器(元數據)與分析服務器(MDX 查詢執行)。原使用 Mondrian 引擎,存在效能與擴展性瓶頸:緩存進程內部、有狀態、易 OOM。任務是將緩存移至第三方緩存系統。
上線後用戶反饋報表打開極慢。回滾版本後問題依然可在測試環境復現:點擊「清除緩存」操作(異步)後,後續操作出現卡頓,最終導致 Tomcat 線程耗盡。
使用 jstack 抓取線程堆疊,發現明確死鎖:
Found one Java-level deadlock:
"mondrian.rolap.RolapResultShepherd$executor_160": waiting for monitor 0x... (MemberCacheHelper), held by "executor_152"
"mondrian.rolap.RolapResultShepherd$executor_152": waiting for monitor 0x... (MemberCacheHelper), held by "http-8182-11"
"http-8182-11": waiting for monitor 0x... (MemberCacheHelper), held by "executor_152"
三線程循環等待,大量其他線程阻塞於這兩把鎖,導致線程池耗盡。
堆疊指向 RolapCubeHierarchy.flushCache → MemberCacheHelper.flushCache → flushCacheSimple。核心問題在 SmartMemberReader 類:
// 子類 flushCache
public void flushCache() {
super.flushCache(); // 鎖父類 cacheHelper
rolapCubeCacheHelper.flushCache(); // 鎖子類 cacheHelper
}
// 父類 flushCache
public void flushCache() {
synchronized(cacheHelper) { cacheHelper.flushCache(); }
}
// MemberCacheHelper.flushCache
public synchronized void flushCache() {
// 清理 maps...
if (rolapHierarchy instanceof RolapCubeHierarchy) {
((RolapCubeHierarchy)rolapHierarchy).flushCacheSimple(); // 又回來了!
}
}
// 子類 flushCacheSimple
public void flushCacheSimple() {
super.flushCacheSimple(); // 鎖父類 cacheHelper
rolapCubeCacheHelper.flushCacheSimple(); // 鎖子類 cacheHelper
}
MemberCacheHelper(cacheHelper 與 rolapCubeCacheHelper)。flushCache 呼叫順序:父類鎖 → 子類鎖 → 觸發 flushCacheSimple → 子類鎖 → 父類鎖。flushCacheSimple 呼叫順序:子類鎖 → 父類鎖。flushCache 持有父鎖等子鎖;線程 B 執行 flushCacheSimple 持有子鎖等父鎖 → 經典死鎖。經分析,flushCache 與 flushCacheSimple 邏輯高度重複(均清理相同 maps)。決定移除所有對 flushCacheSimple 的呼叫,打破巢狀鎖循環。熱修復上線後問題解決。
jstack/jmap/MAT 等工具定位問題。深入研究 Mondrian:MDX 解析、動態 SQL 生成、緩存實現、聚合表優化。需補強編譯原理知識。