關(guān)于微信紅包的架構(gòu)思考
微信紅包的架構(gòu)實現(xiàn)是前段時間技術(shù)圈里很熱門的一個話題,這是一個非常典型的大訪問高并發(fā)場景狼荞。至于如何實現(xiàn)胁镐,答案并不是唯一的,對這個問題的思考其實反映了工程師的架構(gòu)設(shè)計功底华望。所以我也談一談我的想法。
首先任何架構(gòu)設(shè)計離不開對業(yè)務(wù)的理解和認(rèn)識仅乓,架構(gòu)設(shè)計并不是憑空存在的赖舟,一定是基于業(yè)務(wù)場景并且服務(wù)業(yè)務(wù)場景的。所以我先分析一下微信紅包的業(yè)務(wù)場景夸楣。
*** 需要特別說一點宾抓,本文中的架構(gòu)設(shè)計不一定是官方的實現(xiàn)方式,只是一種分析豫喧,是基于我目前的技術(shù)能力的一種思考 ***
業(yè)務(wù)場景分析
根據(jù)微信紅包的操作石洗,可以把紅包的業(yè)務(wù)場景分為發(fā)放紅包,搶紅包和打開紅包三個步驟紧显。
PS:我覺得微信紅包的架構(gòu)討論之所以非辰采溃火爆,一個重要原因就是微信紅包的業(yè)務(wù)場景大家都很熟悉
發(fā)放紅包:
- 發(fā)放者設(shè)置紅包金額孵班,紅包數(shù)量等屬性
- 從發(fā)放者賬戶扣除紅包金額
- 生成紅包涉兽,并且發(fā)送搶紅包的鏈接
搶紅包:
- 用戶點擊紅包鏈接
打開紅包:
- 顯示用戶搶到的紅包金額
- 剩余紅包數(shù)量-1,紅包剩余金額改變
- 搶到紅包的用戶賬戶金額增加
注意篙程,以上均為原子操作
根據(jù)業(yè)務(wù)場景枷畏,可以知道紅包的主要屬性有:
- 紅包金額:始終不變
- 紅包總數(shù)量:始終不變
- 紅包剩余金額:每次有人領(lǐng)取紅包,該屬性變化
- 紅包剩余數(shù)量:每次有人領(lǐng)取紅包虱饿,該屬性-1
- 搶到紅包的用戶以及搶到的金額:用于顯示手氣排行拥诡,并且防止重復(fù)搶紅包
- 過期時間:如果到過期紅包仍未搶完触趴,返回發(fā)放者賬戶
架構(gòu)分析
單純實現(xiàn)微信紅包的功能并不復(fù)雜,其難點在于如何處理高訪問和并發(fā)渴肉,當(dāng)發(fā)出一個紅包之后冗懦,只有少數(shù)人能夠搶到,而大部分的請求都屬于無效請求宾娜,如果讓這部分請求落到數(shù)據(jù)庫或者在服務(wù)端經(jīng)過復(fù)雜處理批狐,那么以微信的用戶體量,后果是災(zāi)難性的前塔。
從這個角度來說嚣艇,微信紅包和電商的秒殺業(yè)務(wù)有幾分相似
處理類似業(yè)務(wù)最常見的手段就是請求過濾和添加緩存(cache),當(dāng)然华弓,至于服務(wù)器集群食零,負(fù)載均衡,讀寫分離這些基礎(chǔ)架構(gòu)寂屏,由于已經(jīng)成了大型網(wǎng)站的標(biāo)配贰谣,默認(rèn)已經(jīng)存在。
其中迁霎,請求過濾是只允許少數(shù)符合條件的請求走到最后吱抚,把大量不符合條件的請求擋在外面,這個非常關(guān)鍵考廉。而采用緩存技術(shù)秘豹,原因有兩個,一是緩存的訪問速度快昌粤,使用緩存可以有效提高吞吐速度既绕,二是紅包發(fā)放相對來說是一個臨時性的東西,故而可以放在緩存里面涮坐。
業(yè)務(wù)上需要注意的地方
- 防止用戶重復(fù)搶紅包
- 應(yīng)對redis宕機的風(fēng)險凄贩,目前一般是采用sentinel機制
架構(gòu)設(shè)計
緩存我們默認(rèn)使用redis,數(shù)據(jù)庫默認(rèn)使用mysql
新建紅包的時候袱讹,生成唯一紅包id疲扎,設(shè)置紅包屬性,在mysql中添加記錄捷雕,表結(jié)構(gòu)大致如下
|紅包id|發(fā)放者uid|紅包總金額|紅包總數(shù)量|紅包剩余金額|紅包剩余數(shù)量|搶到紅包的用戶列表json|過期時間|創(chuàng)建時間|
|---|:----|:---|---|----|---|---|----|---|--|--|:---|:----|:---|---|----|---|---|----|---|--
同時椒丧,在redis里添加:
- 搶紅包請求隊列(隊列)
- 打開紅包請求隊列(隊列)
- 紅包信息,包含剩余紅包數(shù)量和剩余金額(鍵值對)
- 已經(jīng)搶到紅包的用戶信息(有序集合)
當(dāng)搶紅包的時候非区,
做以下判斷:
- 剩余紅包數(shù)量是否大于0
- 該用戶是否在已經(jīng)搶到紅包的用戶集合里面
通過判斷的請求添加進(jìn)請求隊列瓜挽,否則返回已經(jīng)搶完的標(biāo)示盹廷,這樣下一步用戶打開紅包的時候征绸,甚至可以不發(fā)送請求直接顯示紅包已經(jīng)搶完,過濾無效請求
于此同時,另外有進(jìn)程從請求隊列里取出搶紅包請求管怠,生成token標(biāo)示淆衷,返回給搶紅包者
當(dāng)用戶打開紅包的時候,傳入該標(biāo)示渤弛,放進(jìn)打開紅包請求隊列祝拯,后端消費者進(jìn)程從隊列中取出打開紅包請求,判斷紅包數(shù)量是否大于0她肯,以及用戶傳來的打開紅包token與紅包id是否合法佳头,參數(shù)檢驗之后,生成紅包金額晴氨,調(diào)用余額接口處理康嘉,并且更新redis中紅包剩余數(shù)量,紅包剩余金額
最后異步方式更新數(shù)據(jù)庫
紅包算法
關(guān)于紅包算法籽前,網(wǎng)上說的很多了亭珍,這里就不啰嗦了,金額是拆的時候?qū)崟r算出來枝哄,不是預(yù)先分配的肄梨,采用的是純內(nèi)存計算,不需要預(yù)算空間存儲挠锥,具體的算法是在在0.01和剩余平均值的2倍之間隨機生成一個金額
效果
比如現(xiàn)在發(fā)了一個紅包众羡,分為5份。有100個人搶瘪贱。
可能有10個人成功進(jìn)入請求隊列(搶到紅包和打開紅包之間有一定間隔纱控,所以先搶到的不一定先打開),拿到了打開紅包token菜秦,其余90個人拿到的是紅包已經(jīng)搶完的token甜害,那么當(dāng)打開紅包的時候,這90個人甚至都不需要發(fā)送請求球昨,直接顯示紅包已搶完
這10個人打開紅包的時候尔店,其中5個人搶到,剩下的5個也顯示紅包已經(jīng)搶完
本來5個紅包100個人搶主慰,會請求201次(生成紅包1次+搶紅包100次+打開紅包100次)
那么這種情況下嚣州,只需要111次(生成紅包1次+搶紅包100次+打開紅包10次),并且共螺,其中90次請求很快就返回该肴,大大減輕服務(wù)端壓力
需要注意的是
- 如何保證原子性操作
- 如何提高可用性,主要是防止redis宕機
總結(jié)
衡量一個架構(gòu)設(shè)計的標(biāo)準(zhǔn)主要是性能藐不,擴展性匀哄,伸縮性秦效,可用性,安全性等指標(biāo)涎嚼,本例中阱州,由于紅包業(yè)務(wù)是一個很具體的業(yè)務(wù),并非通用服務(wù)法梯,所以不討論擴展性苔货。
- 性能角度,由于采用了緩存以及請求過濾等手段立哑,性能肯定比常規(guī)實現(xiàn)大大提高
- 伸縮性角度夜惭,應(yīng)該采用服務(wù)器集群,redis集群铛绰,mysql集群的方式部署滥嘴,注意redis集群中的一致性hash的實現(xiàn)
- 可用性角度,本例中決定可用性的關(guān)鍵還是在于redis集群是否能夠保證高可用至耻,所以sentinel機制必不可少若皱,甚至還應(yīng)該加上其他的各種檢測節(jié)點,替換節(jié)點方案尘颓,以保證高可用走触。而且需要考慮極端情況,例如緩存被擊穿導(dǎo)致mysql壓力過大的情況下疤苹,如何確保服務(wù)不宕機互广。
- 安全性角度,紅包涉及資金來往卧土,所以需要格外注意安全性惫皱。紅包業(yè)務(wù)中,是通過調(diào)用接口來實現(xiàn)資金往來尤莺,故賬目平衡的風(fēng)險不是由紅包系統(tǒng)來承擔(dān)而是由紅包所調(diào)用的接口保證的旅敷,但紅包系統(tǒng)也可能存在以下幾種安全隱患,單一用戶重復(fù)搶紅包颤霎,所有用戶搶的的紅包金額總數(shù)大于發(fā)放者設(shè)置的紅包金額等媳谁。所以其關(guān)鍵在于確保搶紅包,打開紅包均為一次原子性操作友酱。如有必要晴音,甚至可以通過加鎖等方式實現(xiàn),但需要考慮加鎖對于性能上的影響