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

5次阅读

共计 963 个字符,预计需要花费 3 分钟才能阅读完成。

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

数据库逻辑设计
系统内应该存在一个用户钱包表,其中指定两条记录为系统收入账户和系统拨出账户。这样可以将投注的时候,对系统账户余额增加操作,和发奖的时候,对系统账户余额的减去操作分离。
可以避免上一期游戏的结算,对下一期游戏的投注发生锁等待的问题。
业务加锁
考虑到高并发的情况下,推荐使用 mysql 自带的排他锁,不推荐乐观锁,因为乐观锁需要重试机制,而队列结算暂时不考虑。当一名用户发起投注的时候,检查顺序应该如下

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

这里之所以要冗余检查用户的额度,是否了避免开启事务的消耗,防止恶意攻击消耗系统资源,用来开启无意义事务。
奖池额度的 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.2 和 9.8,结果是只发放了 22,如果你按照四舍五入原则,才能发放到 22.1
那为什么还要坚持舍弃原则呢?因为,假设出一个极端情况,当你碰到 A 的值是 12.05,B 的值是 9.05,按照舍弃原则,总数的确还是 22.1。但是按照四舍五入原则,发放的总值就是 22.2 了。
结语
在计算机系统内,浮点数的计算本身就是不可靠的,在业务内应该用整形去避免,当设计到百分比操作的时候,请尽量使用舍弃原则,保证不多发。按照舍弃原则,给用户少发 0.05 这种精度外的值,对业务来说无关紧要。如果超发了,会导致系统内账目混乱,后果将不堪设想。

正文完
 0