一个游戏拨账系统的数据库结算设计

假设现存在一个简单的猜大小游戏,由用户下注大或者小,扣除手续费3%后的钱全部放入奖池中,赢的一方按投注比例平分整个奖池。使用mysql作为数据库,系统精度精确到1位小数。
本文将会讲解其中会出现的业务结算导致的数据问题,以及解决方法。

数据库逻辑设计

系统内应该存在一个用户钱包表,其中指定两条记录为系统收入账户和系统拨出账户。这样可以将投注的时候,对系统账户余额增加操作,和发奖的时候,对系统账户余额的减去操作分离。

可以避免上一期游戏的结算,对下一期游戏的投注发生锁等待的问题。

业务加锁

考虑到高并发的情况下,推荐使用mysql自带的排他锁,不推荐乐观锁,因为乐观锁需要重试机制,而队列结算暂时不考虑。
当一名用户发起投注的时候,检查顺序应该如下

  1. 检查系统游戏开关
  2. (冗余) 查询一次用户余额是否大于这次下注金额
  3. 开启事务
  4. 对系统收入账户加排他锁
  5. 对用户收入账户加排他锁
  6. 检查用户余额是否足够
  7. 对用户进行扣款
  8. 对系统进行收款
  9. 为奖池加入97%的投注额度
  10. 事务提交

这里之所以要冗余检查用户的额度,是否了避免开启事务的消耗,防止恶意攻击消耗系统资源,用来开启无意义事务。

奖池额度的97%这里计算需要保持一位精度,如果用户投注是98,按照计算得到的值应该是95.06,我们应该取95.0而不是95.1,否则你最后存到奖池里面的数就会大于97%,这样系统抽取就不会达到3%,用户少分点没关系,要保证系统一定能分到3%。

简单一句话就是:精度位后都舍弃

发奖过程设计

假设按照投注比例,瓜分出的奖金总数是22.1,A用户的份额是55.5%,A用户拿到12.2655,B用户的份额是45%,B用户拿到9.8345

这种情况下,你会发现,按照舍弃,原则,分别是12.29.8,结果是只发放了22,如果你按照四舍五入原则,才能发放到22.1

那为什么还要坚持舍弃原则呢?因为,假设出一个极端情况,当你碰到A的值是12.05,B的值是9.05,按照舍弃原则,总数的确还是22.1。但是按照四舍五入原则,发放的总值就是22.2了。

结语

在计算机系统内,浮点数的计算本身就是不可靠的,在业务内应该用整形去避免,当设计到百分比操作的时候,请尽量使用舍弃原则,保证不多发。按照舍弃原则,给用户少发0.05这种精度外的值,对业务来说无关紧要。如果超发了,会导致系统内账目混乱,后果将不堪设想。

    原文作者:boywus
    原文地址: https://segmentfault.com/a/1190000018042913
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞