支付系统中,像资金下发这种业务,通常是在我们系统发给第三方支付通道后,第三方支付通道会进行资金业务处理。然后,付款完成后,会主动发起回调,即,调用我们系统API,将付款结果通知给我们系统。

假定我们的支付系统对三方通道回调通知的处理逻辑包括:① 修改本地付款单的付款状态;② 将付款结果调用商户系统API,通知给商户。见下方示意图。

【不靠谱程序员】接收到回调通知的异步处理_异步处理

 

 

用伪代码来表示,大概是下面这样子:

public void payNotify(Order order) {
    // ① 修改付款单状态
    orderRepository.updatePayResult(order.getId(), "PAY_SUCCESS");
    
    // ② 将付款结果调用商户系统API,通知商户
    Merchant merchant = merchantRepository.getById(order.getMerId());
    String reqMsg = encrypt("orderNo="+order.getOrderId()+"&state=PAY_SUCCESS");
    HttpClient.sendPost(merchant.getNotifyUrl(), reqMsg);
}

 

 

为了提高程序性能,开发人使用了异步线程,出现了下面的不靠谱代码:

public void payNotify(Order order) {
    // ① 修改付款单状态
    new Thread(()->{
        orderRepository.updatePayResult(order.getId(), "PAY_SUCCESS");
    }).start();
    
    // ② 将付款结果调用商户系统API,通知商户
    new Thread(()->{
        Merchant merchant = merchantRepository.getById(order.getMerId());
        String reqMsg = encrypt("orderNo="+order.getOrderId()+"&state=PAY_SUCCESS");
        HttpClient.sendPost(merchant.getNotifyUrl(), reqMsg);
    }).start();
}

 

 

结果呢,出现了一种情况,当前系统里订单的付款状态没能修改,可是通知商户却成功了。。

 

我们作为支付服务提供方,我们系统里的付款单不是终态,而商户系统里却是终态。在支付系统中,这样的程序是很要命的。尤其是当我们系统里的付款单是“付款中”而商户系统里是“付款失败”的情况。这时,商户系统可能会因为付款失败而重新发起付款。如果原单实际是付款成功的话,这就出现了资金风险,即,一笔订单付款了两次。就问你怕不怕?

 

所以,本案中,修改付款单状态的逻辑,必须在主线程执行,确保当前支付系统的付款单状态变更完成后,才可以做后续业务处理。通知商户则可以异步处理,即使通知失败也可以用相关方式来补偿。

 

这个案例告诉我们,代码中在使用JUC、消息队列、回调函数、消息中间件等提高程序性能的方式进行异步处理时,一定要分清主次,哪些逻辑必须在主线程执行,哪些逻辑可以异步处理。