線上某核心鏈路服務的一個節點瘋狂GC,監控圖以下:
正則表達式
平均1分鐘觸發CMS GC 36次,已沒法正常處理線上請求。sql
發現該節點有問題後,找運維將該節點從服務註冊中心上摘掉
,由於咱們須要去jmap dump服務的堆棧信息,而dump內存會STW
,必須先摘流。緩存
dump命令以下:app
jmap -dump:format=b,file=heap.bin [pid]
dump好之後gzip壓縮,便於文件傳輸到本地,從2G壓縮到300+M左右。運維
將dump的文件導入到MAT中,MAT內存分佈圖以下:
ide
1.2G的內存都被AccountChangeTask中的ConcurrentHashMap對象佔用了,那思路就很清晰了,去檢查代碼中什麼地方使用了 這個AccountChangeTask對象。性能
AccountChangeTask的總體結構以下:優化
@Service public class AccountChangeTask { // 緩存SQL和tableName映射關係 private static final Map<String, String> sqlMap = new ConcurrentHashMap<>(); @Async public void processTask(String sql) { // 對sqlMap對象的get put操做,key是SQL,value是表名 // 緣由是邏輯中有一些對SQL作正則解析的操做,可能比較耗時和耗CPU,因此想經過緩存優化 ... 其餘業務邏輯 } }
咱們再去查找使用了AccountChangeTask.processTask()方法的地方,代碼以下:ui
@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }), @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) }) @Component public class MybatisInterceptor implements Interceptor { @Autowired private AccountChangeTask accountChangeTask; @Override public Object intercept(Invocation invocation) throws Throwable { // 省略... String sql = showSql(config,boundSql) // 填充爲真實的SQL,將?填充爲真實的SQL參數 // 業務邏輯判斷,若是true走下面邏輯 accountChangeTask.processTask(sql); } }
分析代碼,緣由就是緩存在ConcurrentHashMap中的SQL是被參數填充過的SQL,而線上環境的sql參數變幻無窮,有不一樣uid和時間等等,請求量一上來就把ConcurrentHashMap撐爆了。spa
解決思路其實也很簡單:在對性能沒有極致要求的狀況下,移除代碼中對SQL的緩存;而直接走正則邏輯 而且 提早對正則表達式作好編譯,多是更合理的選擇。
在沒有極致性能要求的狀況下,簡化咱們的設計,服務可能會更具健壯性。