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