发现有网友对网络延迟导致供货商结算多次支付这篇文章提出问题:多个用户同时支付多个采购订单的话,是不是会阻塞在那里?
答案是一定会的,并且这个事务涉及到微信支付网络调用,事务时间比较长。下面讲解一种优化方案,讲解之前先说明《阿里巴巴Java开发手册_v1.4.0》对加锁的开发原则。
6. 【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁; 能锁区块,就不要锁整个方法体; 能用对象锁,就不要用类锁。
说明: 尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法
——参考《阿里巴巴Java开发手册_v1.4.0》
上一篇在整个方法块上加锁,而且在锁代码块中调用 RPC 方法,违背了开发设计的原则。该如何优化呢?
我们重新分析下后端支付的简化逻辑:
如果在第一步汇集采购记录加锁,满足设计原则,但无法解决问题。原因是虽然线程异步获取采购记录的数据,但会生成不同的支付记录再去调用微信支付,之后在修改采购记录状态时发生数据库操作异常。
上述整个过程中,并不是所有步骤都需要满足ACID,只有第一步汇集采购记录和最后一步修改采购记录状态时才需要,因而我们调换下顺序,汇集采购记录和修改采购记录的状态放到一起,并且对这个代码块加锁,满足《阿里巴巴Java开发手册》,能很好的提高并发性。
@Override
public Message payment(List<Purchase> list, Admin admin) {
PurchaseOrder purchaseOrder;
synchronized (this) {
purchaseOrder = create(list, admin);
// 标记采购记录结算成功
for (Purchase purchase : list) {
purchase.setStatus(PurchaseOrderStatus.completed);
}
}
if (null == purchaseOrder) {
return new Message(Message.Type.error, "请选择同一个供货商,且状态为等待主管审核或会计审核通过!");
}
...
简单分析下上述方法的过程:
1、两个线程同时执行到汇集采购数据(只有等待支付状态的数据),由于这个地方有锁,因而只有一个线程进入,并且修改了采购数据的状态,继续执行其他方法;
2、第二个线程进入代码块,无法得到汇集的采购数据直接返回。
知识点
1、《阿里巴巴Java开发手册_v1.4.0》
2、事务的ACID原则与CAP原理