并发扣款,如何保证一致性?

有朋友问我:

沈老师,我们有个业务,同一个用户在并发“查询,逻辑计算,扣款”的情况下,余额可能出现不一致,请问有什么优化方法么?


今天和大家聊一聊这个问题。

画外音:文章较长,建议提前收藏。

问题一:用户扣款的业务场景是怎样的?

用户购买商品的过程中,要对余额进行查询与修改,大致的业务流程如下:

第一步,从数据库查询用户现有余额:
SELECT money FROM t_yue WHERE uid=$uid;
不妨设查询出来的$old_money=100元。

第二步,业务层实施业务逻辑计算,比如:
(1)先查询购买商品的价格,例如是80元;
(2)再查询产品是否有活动,以及活动折扣,例如是9折;
(3)比对余额是否足够,足够时才往下走;

if($old_money> 80*0.9){
    $new_money=$old_money-80*0.9=28
} else {
    return "Not enough minerals";
}

第三步,将数据库中的余额进行修改。
UPDATE t_yue SET money=$new_money 

WHERE uid=$uid;

在并发量低的情况下,这个流程没有任何问题,原有金额100元,购买了80元的九折商品(72元),剩余28元。

问题二:同一个用户,并发扣款可能出现什么问题?

在分布式环境中,如果并发量很大,这种“查询+修改”的业务有一定概率出现数据不一致。

极限情况下,可能出现这样的异常流程:

步骤一,业务1和业务2并发查询余额,是100元。

9dd0ec0994bf1c12a9eee8e737423960.png

画外音:这些并发查询,是在不同的站点实例/服务实例上完成的,进程内互斥锁肯定解决不了。


步骤二,业务1和业务2并发进行逻辑计算,算出各自业务的余额,假设业务1算出的余额是28元,业务2算出的余额是38元。

67eb7e7af12661d4d04fad27e61c467c.png

步骤三,业务1对数据库中的余额先进行修改,设置成28元。

业务2对数据库中的余额后进行修改,设置成38元。

f2aba3f49fde7e972af5bf0d138cd0dc.png

此时异常出现了,原有金额100元,业务1扣除了72元,业务2扣除了62元,最后剩余38元。
画外音:假设业务1先写回余额,业务2再写回余额。

问题三:有什么常见的解决方案?

对于此案例,同一个用户,并发扣款时,有小概率会出现异常,可以对每一个用户进行分布式锁互斥,例如:在redis/zk里抢到一个key才能继续操作,否则禁止操作。

这种悲观锁方案确实可行,但要引入额外的组件(redis/zk),并且会降低吞吐量。

对于小概率的不一致,有没有乐观锁的方案呢?

对并发扣款进行进一步的分析发现:

(1)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值