这个问题非常好,能够在平常都能注意到每个方法会不会有并发问题。有了这样的意识,是做好分布式系统的关键。在考虑并发的时候,我们要考虑并发操作同一个数据的问题。那么这个“同一个数据”肯定是根据一定条件来得到这一个数据的。例如一个人同时买多个商品,那么用户的余额就是要操作的同一条数据。
但是,在这节的这里实例当中,这个方法是由消息触发的,也就是:
@Transactional
@JmsListener(destination = "order:pay", containerFactory = "msgFactory")
public void handle(OrderDTO msg) {
LOG.info("Get new order to pay:{}", msg);
// 先检查payInfo判断重复消息。
PayInfo pay = payInfoRepository.findOneByOrderId(msg.getId());
if (pay != null) {
LOG.warn("Order already paid, duplicated message.");
return;
}
。。。
所以,对于同一个订单,这个方法不会被并行触发多次,只有失败了一次以后,再被消息重试触发。而这个支付信息,是由这个订单生产的支付信息,就不会出现对同一个订单,保存多个payInfo的情况。如果是对于不同订单,这边即使并发执行到这里,也不会有影响。
在这个方法里,在视频中我强调了针对扣费的操作,没有使用:
customer.setDeposit(customer.getDeposit() - msg.getAmount());
customerRepository.save(customer);
就是怕同一个用户,几乎同时下了多个订单,那么先获取用户信息,再更新余额,再保存的方式就会出现问题,后面保存的方法会把前面的覆盖。然后,下面的同学也提到数据库的隔离级别,但是对于
customerRepository.save(customer)
这个操作,数据库的隔离级别保证读到的是另一个请求还未提交的,也就是未修改的余额值。update的时候,就是update这条记录,这时候即使另一个请求已经提交,也会按原先的余额扣除后保存。