异步,该用的时候你再用

61 阅读1分钟

来,用时15分钟,手敲一段代码(含伪代码)。这是我们系统其中一次迭代的开发内容:每次调用银行接口查询订单支付状态时,如果对方返回404-订单不存在,并且如果订单是在5min前创建的,那么,就触发重新下发,要求每笔订单只可重发一次。

你先读一下。

/**
 * 银行查单服务类
 */
@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 {

    /**
     * 注入订单下发服务类
     */
    @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.repeatPay(order);
                break;
            default:
                // 
                ...
                break;
        }
    }
}



// import org.springframework.scheduling.annotation.Async;

/**
 * 订单重发服务类
 */
@Slf4j
@Service
public class OrderRepeatPayService {

    /**
     * 注入订单下发服务类
     */
    @Autowired
    private BankOrderPayService bankOrderPayService;

    /**
     * 订单重发(只重发一次)
     */
    public void repeatPay(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;
    }
}