一、背景
以下內(nèi)容基于QCon某高可用架構(gòu)群討論總結(jié)
群里某同學(xué)問起微信紅包架構(gòu)筷黔,騰訊財付通同學(xué)作出解答佛舱,以下實現(xiàn)原理根據(jù)對話內(nèi)容推導(dǎo)得出,不代表官方實現(xiàn)订歪。
實現(xiàn)方式千百種肆捕,不追求方法復(fù)制,只追求推導(dǎo)過程的思考總結(jié)眼虱。最后轉(zhuǎn)了新浪微博Tim總的另一種實現(xiàn)方式席纽。
二胆筒、微信紅包實現(xiàn)原理
關(guān)鍵設(shè)計
- 通過cache抵擋大部分請求(是否能拆紅包等)
- DB使用CAS操作更新紅包計數(shù)記錄
- DB、cache使用sharding抒和,可水平擴展
1. 新建紅包
- 在DB彤蔽、cache各新增一條記錄
2. 搶紅包
- 請求訪問cache,剩余紅包個數(shù)大于0則可拆開紅包
3. 拆紅包
- 請求訪問cache镊辕,剩余紅包個數(shù)大于0則繼續(xù),同時獲取可搶紅包數(shù)與金額
- 計算金額(從1分到剩余平均值2倍之間隨機數(shù)石咬,如果不是最后一個紅包卖哎,剩余金額預(yù)留最少1分給cas更新失敗亏娜,最后一位拿紅包的人)
- cas更新數(shù)據(jù)庫(更新紅包計數(shù)表記錄【剩余紅包個數(shù)、剩余紅包金額】它掂、插入領(lǐng)取記錄)
- 更新失敗溯泣,重復(fù)以上操作,直到更新成功或已領(lǐng)取完
- 更新成功熟妓,更新緩存
DB實現(xiàn)CAS操作偽代碼(說明作用):
while (hadHongBao()) {
//剩余紅包個數(shù)
def remainCount = getRemainCount()
//實時計算獲取紅包金額
def getAmount = calculateAmount()
def result = sql.excute("update '紅包計算表' set balance=${total-getAmount}, remainCount=${remainCount-1} where remainCount=${remainCount} and id=${id}")
// 更新失敗既繼續(xù)執(zhí)行循環(huán),直到更新成功或已領(lǐng)取完只恨,達(dá)到CAS效果
if (result > 0) {
// 更新成功官觅,執(zhí)行更新緩存等后續(xù)操作
// ......
break
}
}
DB、cache主要數(shù)據(jù)結(jié)構(gòu)
- DB:紅包計數(shù)表【主要字段:紅包總金額咱圆、紅包總個數(shù)功氨、剩余紅包個數(shù)、剩余紅包金額】
- cache:紅包計數(shù)記錄【主要字段:剩余紅包個數(shù)忱详、剩余紅包金額】
其他補充
注:DB匈睁、cache使用sharding,訪問量增大胀蛮,DB糯钙、cache、服務(wù)端都可水平擴展鸳玩。
三演闭、FAQ
上述內(nèi)容講解了搶紅包關(guān)鍵實現(xiàn)原理,更多其他細(xì)節(jié)請看下述群記錄摘要(以下內(nèi)容轉(zhuǎn)載自:https://www.zybuluo.com/yulin718/note/93148)
微信的金額什么時候算窝革?
答:微信金額是拆的時候?qū)崟r算出來吕座,不是預(yù)先分配的吴趴,采用的是純內(nèi)存計算,不需要預(yù)算空間存儲厢拭。撇叁。
采取實時計算金額的考慮:預(yù)算需要占存儲,實時效率很高楞捂,預(yù)算才效率低趋厉。實時性:為什么明明搶到紅包觅廓,點開后發(fā)現(xiàn)沒有?
答:2014年的紅包一點開就知道金額帖蔓,分兩次操作矮瘟,先搶到金額,然后再轉(zhuǎn)賬塑娇。
2015年的紅包的拆和搶是分離的澈侠,需要點兩次,因此會出現(xiàn)搶到紅包了埋酬,但點開后告知紅包已經(jīng)被領(lǐng)完的狀況哨啃。進入到第一個頁面不代表搶到,只表示當(dāng)時紅包還有写妥。分配:紅包里的金額怎么算拳球?為什么出現(xiàn)各個紅包金額相差很大?
答:隨機珍特,額度在0.01和剩余平均值2之間祝峻。
例如:發(fā)100塊錢扎筒,總共10個紅包莱找,那么平均值是10塊錢一個,那么發(fā)出來的紅包的額度在0.01元~20元之間波動嗜桌。
當(dāng)前面3個紅包總共被領(lǐng)了40塊錢時奥溺,剩下60塊錢,總共7個紅包骨宠,那么這7個紅包的額度在:0.01~(60/72)=17.14之間浮定。
注意:這里的算法是每被搶一個后,剩下的會再次執(zhí)行上面的這樣的算法(Tim老師也覺得上述算法太復(fù)雜诱篷,不知基于什么樣的考慮)壶唤。
這樣算下去雳灵,會超過最開始的全部金額棕所,因此到了最后面如果不夠這么算,那么會采取如下算法:保證剩余用戶能拿到最低1分錢即可悯辙。
如果前面的人手氣不好琳省,那么后面的余額越多,紅包額度也就越多躲撰,因此實際概率一樣的针贬。紅包的設(shè)計
答:微信從財付通拉取金額數(shù)據(jù)郭萊,生成個數(shù)/紅包類型/金額放到redis集群里拢蛋,app端將紅包ID的請求放入請求隊列中桦他,如果發(fā)現(xiàn)超過紅包的個數(shù),直接返回谆棱。根據(jù)紅包的裸祭處理成功得到令牌請求快压,則由財付通進行一致性調(diào)用圆仔,通過像比特幣一樣,兩邊保存交易記錄蔫劣,交易后交給第三方服務(wù)審計坪郭,如果交易過程中出現(xiàn)不一致就強制回歸。高并發(fā)處理:紅包如何計算被搶完脉幢?
答:cache會抵抗無效請求歪沃,將無效的請求過濾掉,實際進入到后臺的量不大嫌松。cache記錄紅包個數(shù)沪曙,原子操作進行個數(shù)遞減,到0表示被搶光萎羔。財付通按照20萬筆每秒入賬準(zhǔn)備珊蟀,但實際還不到8萬每秒。通如何保持8w每秒的寫入外驱?
答:多主sharding育灸,水平擴展機器。據(jù)容量多少昵宇?
答:一個紅包只占一條記錄磅崭,有效期只有幾天,因此不需要太多空間瓦哎。詢紅包分配砸喻,壓力大不?
答:搶到紅包的人數(shù)和紅包都在一條cache記錄上蒋譬,沒有太大的查詢壓力割岛。一個紅包一個隊列?
答:沒有隊列犯助,一個紅包一條數(shù)據(jù)癣漆,數(shù)據(jù)上有一個計數(shù)器字段。有沒有從數(shù)據(jù)上證明每個紅包的概率是不是均等剂买?
答:不是絕對均等惠爽,就是一個簡單的拍腦袋算法。拍腦袋算法瞬哼,會不會出現(xiàn)兩個最佳婚肆?
答:會出現(xiàn)金額一樣的,但是手氣最佳只有一個坐慰,先搶到的那個最佳较性。每領(lǐng)一個紅包就更新數(shù)據(jù)么?
答:每搶到一個紅包,就cas更新剩余金額和紅包個數(shù)赞咙。紅包如何入庫入賬永毅?
數(shù)據(jù)庫會累加已經(jīng)領(lǐng)取的個數(shù)與金額,插入一條領(lǐng)取記錄人弓。入賬則是后臺異步操作沼死。入帳出錯怎么辦?比如紅包個數(shù)沒了崔赌,但余額還有意蛀?
答:最后會有一個take all操作。另外還有一個對賬來保障健芭。
四县钥、另一種實現(xiàn)
群里討論實現(xiàn)原理時,多人提出預(yù)分配金額的實現(xiàn)方式(新建紅包時已分配好每一位搶到紅包的金額)慈迈,減少實時計算及CAS操作的性能損耗若贮,財付通同學(xué)說到預(yù)分配金額還需要額外存儲空間(更重要的可能是現(xiàn)在的實現(xiàn)方式已滿足需求),后來新浪微博Tim總提出預(yù)分配方式不占用額外存儲空間的方式痒留,詳細(xì)請看:
<a >微信紅包金額分配的算法</a>
實現(xiàn)原理
- 通過保存random seed達(dá)到預(yù)分配金額效果谴麦,既無需記錄剩余紅包金額,只需記錄紅包剩余數(shù)
- 搶紅包時伸头,針對紅包剩余數(shù)進行DESC原子操作匾效,當(dāng)紅包剩余數(shù)為0既搶完