最近在使用COLA框架自帶的異步任務時,發現每次執行異步都執行了兩次,如果一些沒有做冪等的接口,這樣是會有問題的,比如入庫操作之類的,就會造成數據重複入庫,造成嚴重bug。
帶着疑惑,開始了 bug 之旅。
1 問題發現
1、首先排查執行入口,是不是有兩個,發現只有一個;
2、調用入口的問題?直接通過controller調用handler,還是調用了兩次。
3、簡化代碼,把handler內的內容都刪掉,只有一個logger打印語句?結果還是打印了兩次。
但是這次,發現logger的線程名不一樣,是兩個線程。
2021-07-26 14:11:19.429 INFO 47294 --- [pool-4-thread-2] c.e.colademo.event.handler.TestHandler : >>>>>>>>>>>>> 0
2021-07-26 14:11:19.430 INFO 47294 --- [pool-4-thread-1] c.e.colademo.event.handler.TestHandler : >>>>>>>>>>>>> 0
2 問題排查
爲什麼會有兩個線程同時執行呢?查看COLA源碼。
public void asyncFire(EventI event) {
this.eventHub.getEventHandler(event.getClass()).parallelStream().map((p) -> {
Response response = null;
try {
if (null != p.getExecutor()) {
p.getExecutor().submit(() -> {
return p.execute(event);
});
} else {
this.defaultExecutor.submit(() -> {
return p.execute(event);
});
}
} catch (Exception var5) {
response = this.handleException(p, response, var5);
}
return response;
}).collect(Collectors.toList());
}
提交異步任務,最終都走到上面的代碼,將任務提交到線程池執行,如果沒有自定義線程池,那麼會提交到defaultExecutor
這個默認線程池中。
發現提交了兩遍,查看this對象中的內容,==發現Event對象和Handler對象都有兩個==。
3 問題原因
是什麼原因會造成重複對象呢?
對比之前的handler對象,這個對象唯一的不同就是使用@RefreshScope
,查看註解源碼,發現使用了==這個註解的對象,都會使用代碼創建一個新的對象,並緩存起來==,debug源碼,查看緩存的對象。
發現的確有TestHandler對象,對象爲@12349。
對比圖1中的handler對象,裏面也有一個TestHandler對象,對象也是@12349.
原來如此,因爲使用了註解@RefreshScope
,這個註解會創建一個對象,這樣就會有兩個相同的對象,造成重複執行。
結論:使用註解@RefreshScope
需要注意,最好把獲取配置的內容放在單獨的property對象中,不要和其他代碼混用。