一个简单的唯一令牌实现,彻底防止恶意并发多领奖励的行为。

7 篇文章 0 订阅

事情的起因

mysql的四种事务的隔离级别,开发时候mysql默认可重复读(repeatable-read)
所以使用事务行锁,锁定一行记录,可以实现互斥,并发操作这一行记录的请求最终按顺序排队,避免常见的恶意并发多领奖励。
但是合作方的机房DBA,为了可能存现的小概率事件,假如行锁释放出问题,会导致数据库卡死。所以把事务隔离级别调整到,读未提交(read-uncommitted)。
这样确实可以避免互斥,大幅度减少数据库卡死的可能性,但是问题也跟着来了。恶意并发多领奖励。
场景:支付一个订单,发起10个并发领取奖励,10个php进程同时去数据库验证订单是否可以领取,结果全部都是可以领取(虽然启用了事务,但查询操作事务不互斥)。
结果就是很悲剧给恶意玩家发了10份奖励。

机房DBA主动预警了这个问题,但是拒绝调整事务隔离级别。
所以只能开发端改逻辑。

我曾经有一篇文章
对恶意的并发访问进行强制排队按顺序执行
https://blog.csdn.net/hangbobo/article/details/108646587

和合作方 DBA 沟通,对方对逻辑锁,队列,非常敏感,任务可能会导致更多的问题
反复沟通大致知道了这个DBA的担忧原因。
以前实现逻辑锁 都是用linux的文件锁,磁盘io是系统短板,所以很多老程序员都对逻辑锁畏惧,现在逻辑锁都是基于redis,根本没有io瓶颈问题。效率非常高。
可惜根本改变不了这个DBA的态度,只能另想办法。

最终使用唯一令牌方法,实现方法简单清晰就几行代码,开发修改代码少,不改变原有的业务逻辑。最关键的是合作的DBA能理解,也认可了方案。

具体代码
 

 

第一步:订单生成处签发订单唯一令牌
一个令牌就是redis中的一个key


假设订单id      789456
$redis->set('d:789456',1,['nx']);

技术点说明:   一个订单只能签发一个令牌,参数 'nx' 确保只能签发成功一个

第二步:领取订单奖励要获得令牌才能继续


if($redis->getset('d:79458',0)==1){    
      说明获取令牌成功了继续逻辑
}else{
     说明获取令牌失败了 结束请求
}

技术点说明  getset先获取原来的值,再设置一个新数值。整个过程是原子操作不会被其他进程打断。
10个领取奖励的并发进程最终只有一个进程能成功拿到唯一令牌。其余的进程都会被丢弃。

 

第三步:重置令牌
$this->redis->set('d:79458',1);
如果检验订单支付未成功要重置令牌

第四步:删除令牌
$this->redis->del('d:79458')
领取成功也及时删除令牌

订单恶意领奖励的就防住了。

还有关卡战斗结束结算领奖励,也可能恶意并发多领奖励。
只要战斗开始,用uid和关卡id组合,签发一个唯一令牌.
战斗结束后结算奖励前先获取这个令牌,就可以保证一次战斗只发一份奖励。

问题最终完满解决,在此做个记号。

 







 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hangbobo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值