我们的税地系统其中一次需求迭代的开发内容:每次调用银行接口查询订单支付状态时,如果对方返回404-订单不存在,并且如果订单是在5min前创建的,那么,就触发重新下发,要求每笔订单只可重发一次。
展示代码前,下面用类时序图来直观介绍一下这3个类以及实现脉络。
下面贴程序实现代码(含伪代码)。
/** * 银行查单服务类 */ @Service public class BankOrderQueryService { @Autowired private OrderRepeatPayService orderRepeatPayService; public void queryBank(BankOrder order) { // 调用银行接口查询支付结果 String responseMsg = ... BankQueryResult queryResult = JSON.parseObject(responseMsg, BankQueryResult.class); switch ( queryResult.state ) { case 成功: // 支付成功的处理 ... break; case 失败: // 支付失败的处理 ... break; case 404: // 支付单不存在, 触发重发逻辑 ... orderRepeatPayService.repeatPay(order); break; default: // ... break; } } } import org.springframework.scheduling.annotation.Async; /** * 订单重发服务类 */ @Slf4j @Service public class OrderRepeatPayService { /** * 注入订单下发服务bean */ @Autowired private BankOrderPayService bankOrderPayService; /** * 订单重发(只重发一次) */ @Async public void repeatPay(BankOrder order) { if (!canRepeatPay(order)){ log.info("订单不满足重发条件,中止"); return; } //组装参数,调用下发服务,实现再次下发 bankOrderPayService.pay(order); } private static boolean canRepeatPay(BankOrder order){ if(order.createTime <= (当前时间-5min) && redisUtil.setnx("orderRepeatPay:"+order.orderId, "Y", HOUR.toMillis(24))){ return true; } return false; } }
我在review上面代码时,其中,注意到了@Async注解。那么,上面代码有什么不足呢?
主线程方法 BankOrderQueryService#queryBank 每当满足条件state=404时,都会调用标记了@Async的异步方法 OrderRepeatPayService#repeatPay。OrderRepeatPayService#repeatPay里的订单重发的逻辑,并不总是会触发。上面文章开头描述了,每笔订单只可重发一次。所以,不足就显现出来了————JVM会不停地创建线程然后很快释放。如果交易量大,可能会导致线程无法创建(jvm:unable to create new native thread)。
所以,我明确叮嘱小组成员:该用异步的时候再用异步。——异步线程里只处理需要异步处理的代码,主线程里明确判断需要走异步时才走。而不是直接调用异步线程,然后异步线程里判断是否需要执行需要异步处理的代码。——像不像下图的绕口令?
改进后的代码如下:
/** * 银行查单服务类 */ @Service public class BankOrderQueryService { @Autowired private OrderRepeatPayService orderRepeatPayService; public void queryBank(BankOrder order) { // 调用银行接口查询支付结果 String responseMsg = ... BankQueryResult queryResult = JSON.parseObject(responseMsg, BankQueryResult.class); switch ( queryResult.state ) { case 成功: // 支付成功的处理 ... break; case 失败: // 支付失败的处理 ... break; case 404: // 支付单不存在, 触发重发逻辑 ... orderRepeatPayService.repeatPayIfNeeded(order); break; default: // ... break; } } } // import org.springframework.scheduling.annotation.Async; /** * 订单重发服务类 */ @Slf4j @Service public class OrderRepeatPayService { /** * 注入订单下发服务bean */ @Autowired private BankOrderPayService bankOrderPayService; /** * 订单重发(只重发一次) */ public void repeatPayIfNeeded(BankOrder order) { if (!canRepeatPay(order)){ log.info("订单不满足重发条件,中止"); return; } //组装参数,调用下发服务,实现再次下发(异步处理) ThreadPoolUtil.getThreadPoolExecutor().execute(()-> bankOrderPayService.pay(order)); } private static boolean canRepeatPay(BankOrder order){ if(order.createTime <= (当前时间-5min) && redisUtil.setnx("orderRepeatPay:"+order.orderId, "Y", HOUR.toMillis(24))){ return true; } return false; } }
设计图物料:https://www.processon.com/view/link/6579736c3fb4b0188b2d5c09
摘自微信公众号「靠谱的程序员」