概況:2014年微信紅包使用數據庫硬抗整個流量,2015年使用cache抗流量。redis
微信金額是拆的時候實時算出來,不是預先分配的,採用的是純內存計算,不須要預算空間存儲。採起實時計算金額的考慮:預算須要佔存儲,實時效率很高,預算才效率低。算法
算法:數據庫
算法很簡單,由於微信不會將紅包的算法開源,因此,這是基於部分樣本提取出的特徵以及網上的資源猜想出的算法。微信
很簡單:數據結構
基於截尾正態分佈,數額隨機,額度在0.01和剩餘平均值*2之間。
實現上述算法的邏輯主要是:併發
public static BigDecimal getRandomMoney(RedPackage redPackage) { // remainSize 剩餘的紅包數量 // remainMoney 剩餘的錢 int remainSize = redPackage.getRemainSize(); BigDecimal remainMoney = redPackage.getRemainMoney(); if (remainSize == 1) { remainSize--; redPackage.setRemainSize(remainSize); redPackage.setRemainMoney(BigDecimal.ZERO); return remainMoney; } Random r = new Random(); BigDecimal min = new BigDecimal(0.01); BigDecimal max = remainMoney.divide(new BigDecimal(remainSize * 2), 2, RoundingMode.HALF_UP); BigDecimal money = max.multiply(new BigDecimal(r.nextInt())); if (money.compareTo(new BigDecimal(0.01)) < 0) { money = new BigDecimal(0.01); } remainSize--; remainMoney = remainMoney.subtract(money); redPackage.setRemainSize(remainSize); redPackage.setRemainMoney(remainMoney); return money; }
RedPackage數據結構以下:app
public class RedPackage { private int remainSize; private BigDecimal remainMoney; public int getRemainSize() { return remainSize; } public void setRemainSize(int remainSize) { this.remainSize = remainSize; } public BigDecimal getRemainMoney() { return remainMoney; } public void setRemainMoney(BigDecimal remainMoney) { this.remainMoney = remainMoney; } }
測試時初始化相關數據是:dom
remainSize = 30;
remainMoney = 500;
以上面的初始化數據(30人搶500塊),執行了兩次,結果以下:異步
// 第一次 15.69 21.18 24.11 30.85 0.74 20.85 2.96 13.43 11.12 24.87 1.86 19.62 5.97 29.33 3.05 26.94 18.69 34.47 9.4 29.83 5.17 24.67 17.09 29.96 6.77 5.79 0.34 23.89 40.44 0.92 // 第二次 10.44 18.01 17.01 21.07 11.87 4.78 30.14 32.05 16.68 20.34 12.94 27.98 9.31 17.97 12.93 28.75 12.1 12.77 7.54 10.87 4.16 25.36 26.89 5.73 11.59 23.91 17.77 15.85 23.42 9.77
對應圖表以下:ide
第一次:
第二次:
200次:
2000次:
爲何不採用徹底隨機的辦法?
這種產生機理很差的地方在於:大多數人獲得的錢很是少,而極少數人獲得的錢卻很是多,而這可能會對抽取人的積極性產生影響。截尾正態分佈可以更好地避免這樣的問題,由於更多人的紅包大小會彙集在平均值附近,並且因爲尾部更快的衰減,所以得到特別大的紅包的機率也會相應減少,有助於增長公平性與參與的積極性。儘管具體截尾的範圍可能須要獲取更多的數據纔有可能有一個準確的預測。
微信的金額何時算?
微信金額是拆的時候實時算出來,不是預先分配的,採用的是純內存計算,不須要預算空間存儲。
採起實時計算金額的考慮:預算須要佔存儲,實時效率很高,預算才效率低。
2014年的紅包一點開就知道金額,分兩次操做,先搶到金額,而後再轉帳。
2015年的紅包的拆和搶是分離的,須要點兩次,所以會出現搶到紅包了,但點開後告知紅包已經被領完的情況。進入到第一個頁面不表明搶到,只表示當時紅包還有。
隨機,額度在0.01和(剩餘平均值*2)之間。
例如:發100塊錢,總共10個紅包,那麼平均值是10塊錢一個,那麼發出來的紅包的額度在0.01元~20元之間波動。
當前面3個紅包總共被領了40塊錢時,剩下60塊錢,總共7個紅包,那麼這7個紅包的額度在:0.01~(60/7*2)=17.14之間。
注意:這裏的算法是每被搶一個後,剩下的會再次執行上面的這樣的算法
這樣算下去,會超過最開始的所有金額,所以到了最後面若是不夠這麼算,那麼會採起以下算法:保證剩餘用戶能拿到最低1分錢便可。
若是前面的人手氣很差,那麼後面的餘額越多,紅包額度也就越多,所以實際機率同樣的。
微信從財付通拉取金額數據郭萊,生成個數/紅包類型/金額放到redis集羣裏,app端將紅包ID的請求放入請求隊列中,若是發現超過紅包的個數,直接返回。根據紅包的裸祭(邏輯)處理成功獲得令牌請求,則由財付通進行一致性調用,經過像比特幣同樣,兩邊保存交易記錄,交易後交給第三方服務審計,若是交易過程當中出現不一致就強制迴歸。
cache會抵抗無效請求,將無效的請求過濾掉,實際進入到後臺的量不大。cache記錄紅包個數,原子操做進行個數遞減,到0表示被搶光。財付通按照20萬筆每秒入帳準備,但實際還不到8萬每秒。
多主sharding,水平擴展機器。
一個紅包只佔一條記錄,有效期只有幾天,所以不須要太多空間。
搶到紅包的人數和紅包都在一條cache記錄上,沒有太大的查詢壓力。
沒有隊列,一個紅包一條數據,數據上有一個計數器字段。
不是絕對均等,就是一個簡單的拍腦殼算法。
會出現金額同樣的,可是手氣最佳只有一個,先搶到的那個最佳。
每搶到一個紅包,就cas更新剩餘金額和紅包個數。
數據庫會累加已經領取的個數與金額,插入一條領取記錄。入帳則是後臺異步操做。