最近在使用 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 对象中, 不要和其他代码混用.
来源: https://segmentfault.com/a/1190000040403690