一個遊戲撥賬系統的資料庫結算設計
假設現存在一個簡單的猜大小遊戲,由使用者下注大或者小,扣除手續費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這種精度外的值,對業務來說無關緊要。如果超發了,會導致系統內賬目混亂,後果將不堪設想。