采纳答案成功!
向帮助你的同学说点啥吧!感谢那些助人为乐的人
老师您好,有个问题请教一下,有没有可能T1和T2。两个请求同时执行到52 这一行,如果可以的话,感觉幂等性校验没起作用,重复支付没有识别出来,如果payInfo表有唯一索引会如何呢?
这个问题非常好,能够在平常都能注意到每个方法会不会有并发问题。有了这样的意识,是做好分布式系统的关键。在考虑并发的时候,我们要考虑并发操作同一个数据的问题。那么这个“同一个数据”肯定是根据一定条件来得到这一个数据的。例如一个人同时买多个商品,那么用户的余额就是要操作的同一条数据。
但是,在这节的这里实例当中,这个方法是由消息触发的,也就是:
@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这条记录,这时候即使另一个请求已经提交,也会按原先的余额扣除后保存。
非常感谢老师,我忽略了这个是消息系统触发这个问题了,如果是回调或者外部直接调用这个操作过程的话,是不是就需要考虑分布式锁来锁住整个请求或者在事务里手动锁表来解决这个并发问题了?感谢老师!
首先,还是不要一上来就考虑分布式锁。首先要看可能出问题的请求,如果只是一条记录,最容易的是用数据库的serialize的锁,这样就能够锁住读操作。还有就是用这个例子中 更新用户余额的方法,用一条update的语句更新记录,而不是先读再写。再或者,我们也经常用redis做缓存,先通过redis来挡住大部分并发请求,例如秒杀系统里,用redis的一个计数器来记录商品余量,这样也能避免大量的锁操作。只有在一些复杂的微服务的分布式系统中,有复杂的相互调用,多数据源操作,才根据情况考虑分布式锁。但是,我们不也是为了解决这种相互调用造成的过于耦合,和数据一致性问题,才使用消息驱动、消息溯源模式,将服务之间通过消息来驱动业务吗。
好的,感谢老师!
根据mysql的默认隔离级别,应该不会锁住读请求吧
是,默认的隔离级别是`REPEATABLE READ`,不会锁住读请求,所以读到的时候,可能在别的请求里面正在修改,可能在一个请求里面刚读完,就被别的请求的事务修改完提交了。那么这时候,这个数据就变成脏数据了。
那么是不是说,在判断余额的时候,可能看到的用户余额不是最新的,那么判断就失效了啊
对,你读出来的数据可能已经被另一个并发的请求修改了。
登录后可查看更多问答,登录/注册
掌握分布式事务实现技术,是架构师必备技能。
1.3k 13
1.2k 13
1.7k 12
1.7k 8
1.7k 7
购课补贴联系客服咨询优惠详情
慕课网APP您的移动学习伙伴
扫描二维码关注慕课网微信公众号