关于异步处理,请分清真正需要异步处理的逻辑

发布时间 2023-12-25 21:38:59作者: buguge

我们的税地系统其中一次需求迭代的开发内容:每次调用银行接口查询订单支付状态时,如果对方返回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

摘自微信公众号「靠谱的程序员」