请教下前端APP异步并发提交订单,如何保证账户余额一致性

Doogeli

问题描述

最近遇到的问题,之前没有怎么注意,前端APP用异步提交订单,订单需要扣减余额,类似于快速下单秒杀的这种,用户可能在1秒内下单5次左右。一般的逻辑写法是:
APP用户提交订单金额,逻辑处理后,查询余额,余额>订单金额,提交成功并扣除账户余额,记录流水。

同步的时候这种情况是没错的,但是在异步的时候,用户一秒内下单5,6次,用户余额10元,每次下单3元,
1秒5次的情况下,会导致余额变成负数,第1次订单余额是10元,这个时候订单可能还没有扣除余额,第2次就提交上来了,又是查到10元,依次的情况下,负数就出现了。。

请教下,如何保证前端APP异步提交的情况下,余额等账户情况保持一致性,不会出现这种金额负数的情况呢?
没有这方面的经验,但接手项目,头疼~~

745 6 2
6个回答

软饭工程师

使用业务锁,上一订单未完成,阻塞当前订单扣款

banro512
  1. 数据表余额字段设为 无符号int,禁止为负
  2. 修改订单代码,使用 事务处理,先 beginTransaction ,然后for update 查询订单信息锁定。使用 Throwable 捕获异常回滚
  • 小W 2023-08-28

    update TABLE set yu_e = yu_e - 3 where userId= 1 and yu_e > 0;

  • Doogeli 2023-08-29

    感谢大佬指点~我学习一下。好像说可以用乐观锁这个概念。

MarkGo

兩方面來的吧;
後端:如果不支持餘額為負數的情況,下單->扣款->返回結果 應當是一個事務,執行後餘額不等於0或庫存充足的情況下才提交事務;
前端:如果禁止重複下單,下單後按鈕disable,有結果後才enable。或者通過節流防抖那套方案。

  • gddd 2023-08-28

    正解,如果要防止数据库压力过大,可以执行 数据库事务之前,加上redis锁,就最简单的那种就行了,事务提交成功了删除key,不然再下单,就直接返回失败

  • Doogeli 2023-08-29

    感谢大佬指点~主要是秒杀类,按钮如果disable用户体验就差了。项目意思不加这个。感谢大佬们~

  • Doogeli 2023-08-29

    可以执行 数据库事务之前,加上redis锁,用户的钱包余额,没地进redis的,因为需要实时更新这块,我直接都是从数据库里面实时取出来的。如果redis如何保证用户的余额与redis里面的是一致的?

  • MarkGo 2023-08-30

    “按钮如果disable用户体验就差了”;其實看你怎樣設計,如提交時異步的,disable按鈕後害怕體驗差,那就加上搶購中的動畫效果;但始終需要分開,前端的disable是為了防止重複提交對後端產生額外的性能開銷和流量開銷,並不是為了防止重單;重單的應該後端事務控制;而如果你是搶購類的,其實還不如預熱的時候把數據丟入redis,redis進行庫存控制,數據延遲入庫,這樣就能避免數據庫壓力

  • Doogeli 2023-08-31

    感谢大佬的回答,学习了~确实是应该丢入redis,处理起来会更好一点

adobe

数据库事务里加锁查询 或使用redis锁实现

  • Doogeli 2023-08-29

    谢谢大佬。~

  • Doogeli 2023-08-29

    那就是要把用户的余额这些都放在redis里面,然后每次请求同步redis数据吗?先请求redis,业务锁,然后去数据库查询余额,判断此时余额是否达到订单创建的标准,就放行,同步redis数据。

  • adobe 2023-08-30

    余额是实时的,不建议存在redis里,余额存在数据库里,只是并发可以使用redis锁来解决,谁拿到锁,谁先锁定余额

  • Doogeli 2023-08-31

    等我测试下效果先。感谢大佬,学习了

qq7467466

这个插件非常适合你,支持文件锁,redis锁等各种

https://www.workerman.net/plugin/56

  • 小W 2023-08-29

    直接使用setnx就行吧,关键是数据库的压力

  • Doogeli 2023-08-29

    感稿大佬~

army

我用cache锁,收到请求前先判断缓存标记是否存在->没有就添加缓存标记/有标记就阻断业务->再处理业务->处理完再删除缓存标记

  • Doogeli 2023-08-31

    因为有些需要实时的效果,缓存不太适合现在的场景,感谢大佬的回答~学 习了

🔝