CAS更新账户余额,并且记录流水变动,大致逻辑如下:
原有余额
= select 余额
from 账户表
where uid = 1;
更新数量
= update 账户表
set 余额
=余额
- 变化金额
where uid = 1 and 余额
= 原有余额
insert into 流水表
values(uid, 原有余额
, 变化金额
, 现有余额
)
当第二步的更新数量为0时,表示扣费失败,会抛异常并回滚
现在问题是,当同一个用户并发更新余额失败时,怎么加重试机制?
业务里同一个用户可以有多个附属账号同时操作扣费这个用户余额,所以是有这种场景的
非webman的话,可能是整个流程加个重试次数,每次失败sleep一下然后重试
现在用webman,这种问题有什么好的解决办法么?
如果是webman,可以引入一下消息队列,让步骤2这里抛出异常时,丢到消息队列中,消费失败,也可以再重试消费
业务是请求api接口扣费,扣费不管成不成功都要响应给用户端,应该不能放到队列里处理
那你写的时候,就在里面写一下重调函数逻辑(类似递归,加个次数限制),或者返回一个参数让客户端重试。
你要么 uid for update 。
要不你就队列异步实现充值,推荐这种方式。(当然消息通知就不是及时的)
好处就是不会阻塞http worker 进程处理请求,
当然该lock还是要lock,不要用sleep()这个是秒级别,你可以选择用usleep() 这种控制微妙或者毫秒(重试)
webman redis 队列配置文件有个每个任务失败次数,你上锁失败就重新放回队列(说明出现并发情况)
如果达到webman 任务失败次数上限,大概率这个任务就是失败了。(需要人工干预)
业务是请求api接口扣费,扣费不管成不成功都要响应给用户端,应该不能放到队列里处理
其余框架的做法,可能就是简单加个usleep然后重试几次,尽量保证用户不出现扣费失败这种情况。但是webman不是不能用sleep函数么
当然能用
usleep() 时间短一点,worker 数量稍微大一点
更新余额,我们是加业务锁的,同一时间 只能一个先完成
同一时间肯定是要保证只有一个能扣费成功的。
因为我这边业务场景,一个账号是允许多个附属账号同时登录操作扣费的,都是扣同一个账户的余额。所以想尽量保证这种情况下,用户都能操作成功,避免出现扣费失败让用户以为软件有问题
下面回复了一个,搞了个伪代码, 你看符合需求不
如果不能出现负余额只能加锁吧
方案有几种。 看你们附属账号多不多,还有扣费逻辑执行时间久不久。 简单来处理,就用缓存锁。 比如 laravel cache 的lock,当然也可以自己实现。
第一种:
第二种:第二种,就用 for update 锁住行记录,其他事物得等待释放才能执行, 但是这种需要等待锁超时或者死锁问题。还是需要重试机制,设置次数。
建议用第一种,简单,灵活,也可以减少对数据库的直接负载