背景
工作中遇到这样一个需求:我方需为客户开发一个清算支付应用,底层支付和提现功能使用依赖其他部门提供的产品。
该产品提供了交易账户创建,交易账户创建结果查询,支付,提现等API。其中:交易账户创建API接口内部采用异步实现方式,即:接收到我方传入参数后,立即返回参数接收响应,交易账户的创建结果需调用其他接口获取,创建交易账户API响应参数结构如下
1)requestStatus:FIN,BNK
2)returnCode:SUC0000,其他错误码
3)referenceNumber:业务号,用于结果查询(我方构建唯一参数)
FIN表示处理完成,此时returnCode一般是错误信息
BNK表示正处理中,可通过referenceNumber查询另一个接口获取交易账户号
问题分析
对于该API的处理方式,想到两种方案获取交易账户号
我方提供两个接口
- 接口1:账户创建接口,返回referenceNumber
- 接口2:交易账户创建结果查询接口,传入接口1的返回结果,将返回的账户号更新到交易账户表
异步处理
我方调用交易账户创建API后,发送一个异步消息,由消息接收者调用交易账户创建结果查询API获取交易账户号,并更新到交易账户表,有如下三种异步处理方案
- Spring事件监听
- MQ
- 多线程
鉴于我方业务设计没有提供两个接口,故而采用异步的方式处理,而且功能比较简单,事前分析过,不太可能出现较大并发,因而从资源和实现复杂性等角度考虑,最终采用Spring事件监听实现该功能。
方案设计
Spring事件监听
A)业务逻辑
- 前端调用后端提供的接口,并传入待创建的账户信息
- Service层调用其他部门API,若返回结果是错误的,直接报错提示,若返回结果处理中,开启事务,保存账户信息,并发布账户创建事件
- 监听器监听到对应的事件后,查询结果。若结果成功,更新账户编号;若结果失败,继续轮询;轮询超过3次,发送告警短信
B)异常处理
- 调用创建交易账户API失败:该问题可能是网络,服务宕机等原因导致,由于对方产品已上线运行较长时间,让对方配合我方调整成分布式事务,可能性不大,针对这种情况,我方先保存账户信息,标注为无效。继续保存和创建结果查询事件发布。
- 保存账户信息失败:回滚
- 发布账户创建事件失败:回滚
- 对于我方失败的场景,需要提供一个手动任务,任务里查询交易创建结果,若结果失败,报错提示,若结果成功,保存账户信息,若结果处理中,保存账户,发布账户创建查询事件。
C)Spring事件监听问题
创建监听器的流程:
- 往容器中注册一个Bean,name=applicationEventMulticaster,类型=ApplicationEventMulticaster
- 默认为SimpleApplicationEventMulticaster
- 该实例默认为同步方式发布消息
SimpleApplicationEventMulticaster同步发布
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
// 查询执行器
// Spring启动时,会创建SimpleApplicationEventMulticaster,但该对象实例的Executor为空,故而发布事件走同步方式
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
SimpleApplicationEventMulticaster异步发布
/**
* 异步发布
* 1)往BeanFactory中注入ApplicationEventMulticaster,beanName=applicationEventMulticaster
* 2)创建线程池
* 3)赋值给属性TaskExecutor
*/
@Bean
public ApplicationEventMulticaster eventMulticaster() {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
ThreadPoolExecutor executor = new ThreadPoolExecutor(6, 10, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
multicaster.setTaskExecutor(executor);
return multicaster;
}
/**
* Set a custom executor (typically a {@link org.springframework.core.task.TaskExecutor})
* to invoke each listener with.
* <p>Default is equivalent to {@link org.springframework.core.task.SyncTaskExecutor},
* executing all listeners synchronously in the calling thread.
* <p>Consider specifying an asynchronous task executor here to not block the
* caller until all listeners have been executed. However, note that asynchronous
* execution will not participate in the caller's thread context (class loader,
* transaction association) unless the TaskExecutor explicitly supports this.
* @see org.springframework.core.task.SyncTaskExecutor
* @see org.springframework.core.task.SimpleAsyncTaskExecutor
*/
public void setTaskExecutor(@Nullable Executor taskExecutor) {
// executor即属性taskExecutor,若我们不想阻塞主线程,我们可以注入一个name=applicationEventMulticaster的bean,并调用setTaskExceutor方法,为了减少麻烦,我们可以注入SimpleApplicationEventMulticaster。
this.taskExecutor = taskExecutor;
}
代码实现
总结
总体来说,功能比较简单,使用Spring事件监听机制即可,业务设计方面考虑了多种可能出现的异常场景。