2022 春节抖音视频红包系统设计与实现
動手點關注?干貨不迷路?👆
我們做了什么
業務背景
在春節活動期間,抖音將視頻和春節紅包相結合,用戶可以通過拍視頻發紅包的方式來給粉絲和好友送祝福。
業務玩法
整個活動玩法分為 B2C 和 C2C 兩種玩法,下面對這兩個玩法的流程簡單介紹下
B2C 紅包
在 B2C 紅包玩法中,用戶需要先來抖音或者抖 Lite 參加春節紅包雨活動,有一定概率在春節紅包雨活動中獲得紅包補貼。用戶可以在獲得補貼后直接跳轉到相機頁面,或者在之后拍攝視頻跳轉到相機頁面,在相機頁面用戶拍攝完視頻后會看到一個紅包掛件,在掛件中可以看到已發放補貼。用戶選擇補貼后點擊下一步完成投稿后即可完成視頻紅包的發放。
圖 1 春節紅包雨活動?? 圖 2 紅包補貼? ?圖 3 紅包掛件? ?圖 4b2c 紅包發送 tab 頁
C2C 紅包
在 C2C 紅包玩法中,用戶拍攝視頻點擊掛件,填寫紅包的金額和個數信息,選擇紅包的領取范圍后,點擊發送紅包會拉起收銀臺,用戶支付完成后點擊下一步發布視頻,即可完成 C2C 紅包的發放。
圖 1C2C 紅包發送 Tab 頁? ?圖 2 支付界面? ?圖 3 紅包支付后掛件展示
紅包領取
B2C 和 C2C 紅包的領取流程都是一樣的。用戶在抖音刷視頻遇到有視頻紅包的視頻時,視頻下方有個領取紅包按鈕,用戶點擊紅包領取,會彈出到紅包封面,用戶點擊紅包封面的開紅包后即可領取紅包,領取成功后會顯示領取結果彈窗,在領取結果中用戶可以看領取金額,以及跳轉到領取詳情頁,在領取詳情頁中可以看到這個紅包其他用戶的領取手氣。
圖 1 紅包視頻? ?圖 2 紅包封面? ?圖 3 紅包領取結果? ?圖 4 紅包領取詳情
衛視春晚演示視頻
C2C 視頻紅包在衛視春晚上也有推廣,這里貼一個衛視春晚的推廣視頻,方便大家了解下整體流程。
我們碰到的一些問題
通用紅包系統的設計
在上文中有提到,本次春節活動需要同時支持 B2C 和 C2C 兩種類型的紅包,這兩個類型的紅包既有一些相似的業務,也有很多不同的業務。在相同點上他們都包括紅包的發放和領取這兩個操作。在不同點上,比如 B2C 的紅包發放需要通過使用補貼來發送,而 C2C 的紅包發放需要用戶去完成支付。B2C 的紅包用戶領取后需要去提現,而 C2C 的紅包用戶領取后直接到零錢。因此需要設計一個通用的紅包系統來支持多種紅包類型。
另外對于紅包系統本身而言,除了發領紅包外,還涉及到一些紅包信息的查詢,以及各種狀態機的推進,這些功能模塊之間如何劃分也是需要考慮的一個點。
大流量補貼的發放處理
前面提到過,B2C 紅包玩法會先進行補貼的發放。在春節活動期間,每場紅包雨都會有大量的用戶進入參與,如果將這些流量直接打到數據庫,將需要大量的數據庫資源,而春節期間數據庫的資源是非常稀缺的,如何減少這部分的資源消耗也是一個需要考慮的問題。
紅包領取方案的選型
在紅包業務中,領取是一個高頻的操作,在領取方式的設計中,需要業務場景考慮一個紅包是否會被多個用戶同時領取。多個用戶同時去領取同一個紅包可能會導致熱點賬戶問題,成為系統性能的瓶頸。解決熱點賬戶問題也有多個方案,我們需要結合視頻紅包的業務場景特點來選取合適的方案。
穩定性容災
在本次春節活動中,包括 B2C 和 C2C 兩種業務流程,其中每個業務流量鏈路都依賴很多的下游服務和基礎服務。在這種大型活動中,如果出現黑天鵝事件時,如何快速止損,減少對系統的整體影響,是一個必須要考慮的問題。
資金安全保證
在春節活動期間,B2C 會發放大量的紅包補貼,如果補貼發生超發,或者補貼的核銷出現問題,一個補貼被多次核銷,將會造成大量的資損。另外 C2C 也涉及到用戶的資金流入流出,如果用戶領取紅包后如果發現錢變少了,也可能會造成大量的客訴和資損。因此資金安全這塊需要做好充足的準備。
紅包系統的壓測
在傳統的壓測方式中,我們一般會對某個大流量接口進行壓測從而得到系統的瓶頸。然而在紅包系統中,用戶的發領和查都是同時進行的,并且這幾個接口之間也是相互依賴的,比如需要先發紅包,有了紅包后才能觸發多個人的領取,領取完成后才可以查看領取詳情。如果使用傳統的單接口的壓測方式,首先 mock 數據會非常困難,和支付相應的壓測數據因為涉及實名還需要特殊生成,而且單個接口單個接口的壓測很難得到系統的真實瓶頸,因此如何對系統進行全鏈路的壓測從而得到系統準確的瓶頸也是我們需要解決的一個問題。
我們怎么做的
通用紅包系統的設計
對于紅包系統,核心操作包括紅包發送,領取,以及未領取的退款這三個操作。另外我們還會需要去查一些紅包的信息和領取的信息等。同時對于發送,領取和退款這三個核心操作,我們需要對它們的狀態進行一個維護。同時在我們的業務場景中,還存在 B2C 特有的補貼的發放,我們也需要維護補貼的狀態。
在上面初步介紹紅包系統后,可以看到紅包的幾個功能模塊,有發放,領取,退款,補貼發放以及各種信息查詢,另外還有狀態機的維護等,對紅包的功能進行梳理后,我們開始對紅包的模塊進行劃分。
劃分原則
功能內聚,每個系統只處理一個任務,方便之后系統的開發和迭代,以及問題的排查
API 網關層只進行簡單的 proxy 處理
異步任務拆解
讀寫分離,將紅包的核心操作和紅包的查詢分成兩個服務
劃分模塊
紅包網關服務
HTTP API 網關,對外對接客戶端和 h5,對內封裝各個系統 rpc 接口,限流,權限控制、降級等功能
紅包核心服務
主要承載紅包核心功能,包括紅包的發放、領取、退款,以及紅包補貼的發放,維護紅包狀態機,紅包的狀態推進
紅包查詢服務
主要承載紅包查詢功能,包括紅包詳情、紅包發送狀態、紅包領取狀態、紅包領取詳情、紅包補貼信息
紅包異步服務
主要承載紅包異步任務,保證狀態機的流轉,包括紅包的轉賬,紅包的退款,以及紅包補貼的狀態推進
紅包基礎服務
主要承載紅包各個系統的公共調用,例如對 DB,redis、tcc 的操作,公共常量和工具類,相當于紅包的基礎工具包
紅包對賬服務
主要承載紅包和財經的對賬邏輯,按天和財經對賬
整體架構
最后整個視頻紅包的系統架構如圖所示
大流量補貼的發放處理
同步獎勵發放
在紅包補貼發放鏈路流程中,為了應對春節的大流量,整個鏈路流程也經歷過幾次方案的迭代。
在最初的方案設計中,我們是按照同步的補貼發放流程來處理的,上游鏈路調用紅包系統接口發券,發券成功后用戶感知到券發放成功,可以使用該券來發放紅包,最初方案的整體流程如下圖:
上面方案的一個問題是在春節活動期間,整個鏈路都需要能扛住活動期間的總流量,而且最終流量都會打到數據庫,而數據庫的資源在春節期間也是比較緊缺的。
異步獎勵發放
為了解決同步獎勵發放的問題,整體流程改為通過 MQ 進行削峰,從而降低下游的流量壓力,相當于是從同步改為異步,用戶參與活動后會先下發一個加密 Token 給客戶端,用于客戶端的展示以及和服務端的交互處理。活動異步發券方案如下圖
這樣解決了大流量的問題,但是相應地引入了其他的問題,在最初方案中,用戶的紅包補貼都會先在紅包系統中落庫,后續用戶對補貼的查詢和核銷我們都能在紅包數據庫中找到對應的記錄,但是在異步的方式中,整個補貼的入賬預估需要 10min,而用戶在 APP 界面感知到發券后可能馬上就會開始使用用補貼來發放視頻紅包,或者會去紅包掛件查看自己已經領取的紅包補貼,而此時補貼還未在紅包系統中入賬。
最終方案
為了解決上面問題,我們對紅包補貼的視頻紅包發放和紅包補貼查詢的整個邏輯進行了修改,在用戶使用紅包補貼進行視頻紅包發放時,我們會先對該補貼進行一個入庫操作,入庫成功后才可以用這個補貼進行紅包發放。另外對于查詢接口,我們無法感知到所有補貼是否完全入賬,因此每次查詢時我們都需要去獎勵發放端查詢全量的 Token 列表,同時我們還需要查詢出數據庫中用戶的補貼,對這兩部分數據進行一次 merge 操作,才能得到全量的補貼列表。
在上面的流程中,為了解決 MQ 異步會有延遲的問題,我們在用戶進行請求時主動地進行入賬,而用戶主動的操作包括使用補貼發放紅包和查詢補貼,我們為什么只在補貼發放紅包時入賬而在查詢補貼時不入賬呢?因為用戶的查詢行為是一個高頻行為,同時涉及到批量的操作,在操作 DB 前我們無法感知該補貼是否入賬,所以會涉及到 DB 的批量處理,甚至用戶每次來查詢時我們都需要重復這個操作,會導致大量的 DB 資源浪費。而補貼的發放時入賬則是一個低頻的,單個補貼的操作,我們只需要在用戶核銷時入賬即可,這樣可以大量減輕數據庫的壓力,節省數據庫資源。
紅包領取方案的選型
在視頻紅包領取的技術方案中,我們也有一些方案的選擇和思考,這里和大家分享下。
悲觀鎖方案
方案一也是最常見的思路,在用戶領取時對數據庫的紅包進行加鎖,然后扣減金額,然后釋放鎖完成整個紅包領取。這個方案的優點是清晰明了,但是這種方案的問題會導致多個用戶同時來領取紅包時,會造成數據庫行鎖的沖突,需要排隊等待,當排隊請求過多時會造成數據庫鏈接的浪費,影響整體系統的性能。同時在上游長時間未收到反饋導致超時,用戶側可能會不停重試,導致整體數據庫鏈接被耗盡,從而導致系統崩潰。
紅包預拆分方案
方案一的問題是多個用同時領取會造成鎖沖突,解鎖鎖沖突可以通過拆分的方式,來將鎖化成更細的粒度,從而提高單個紅包的領取并發量。具體方案如下:
在方案二中,對發紅包的流程進行了一個改動,在發紅包時會對紅包進行一個預拆分的處理,將紅包拆成多個紅包,這樣就完成了鎖粒度的細化,在用戶領取紅包時從之前的爭搶單個紅包鎖變為現在多個紅包鎖分配。從而在領取紅包時問題就變為如何給用戶分配紅包,一種常用的思路是當用戶請求領取紅包時,通過 redis 的自增方法來生成序列號,該序列號即對應該領取那一個紅包。但是這種方式強依賴 redis,在 redis 網絡抖動或者 redis 服務異常時,需要降級到去查詢 DB 還未領取的紅包來獲取序列號,整體實現比較復雜。
最終方案
在視頻紅包的場景中,整個業務流程是用戶拍攝視頻發紅包,然后在視頻推薦 feed 流中刷到視頻時,才會觸發領取。相對于微信和飛書這種群聊場景,視頻紅包中同一個紅包的領取并發數并不會很高,因為用戶刷視頻的操作以及 feed 流本身就完成了流量的打散,所以對于視頻紅包來說,領取的并發數并不會很高。從業務的角度來看,在需求實現上,我們在用戶領取完成后需要能獲取到未領取紅包的個數信息下發給用戶展示,方案一獲取紅包庫存很方便,而方案二獲取庫存比較麻煩。另外從系統開發復雜度和容災情況看,方案一相對來說是一個更合適的選擇。但是方案一中的風險我們需要處理下。我們需要有其他的方式來保護 DB 資源,盡量減少鎖的沖突。具體方案如下:
紅包 redis 限流
為盡可能少的減少 DB 鎖沖突,首先會按照紅包單號進行限流,每次允許剩余紅包個數*1.5 的請求量通過。被限流返回特殊錯誤碼,前端最多輪訓 10 次,在請求量過多的情況下通過這種方式來慢慢處理
內存排隊
除了 redis 限流外,為了減少 DB 鎖,我們在領取流程中加個一個紅包內存鎖,對于單個紅包,只有獲取到內存鎖的請求才能繼續去請求 DB,從而將 DB 鎖的沖突遷移到內存中提前處理,而內存資源相對于 DB 資源來說是非常廉價的,在請求量過大時,我們可以水平擴容。
為了實現內存鎖,我們進行了幾個改動。首先需要保證同一個紅包請求能打到同一個 tce 實例上,這里我們對網關層路由進行了調整,在網關層調用下游服務時,會按照紅包單號進行路由策略,保證同一單號的請求打到同一個實例上。另外我們在紅包系統的 core 服務中基于 channel 實現了一套內存鎖,在領取完成后會釋放該紅包對應的內存鎖。另外為了防止鎖的內存占用過大或者未及時釋放,我們起了一個定時任務去定期地處理。
轉賬異步化
從接口耗時來看,轉賬是一個耗時較長的操作,本身涉及和第三方支付機構交互,會有跨機房請求,響應延時較長,將轉賬異步化可以降低領取紅包接口的時延,提高服務性能和用戶體驗
另外從用戶感知來看,用戶更關注的是領取紅包的點擊開后是否領取成功,至于余額是否同步到賬用戶其實感知沒那么強烈,另外轉賬本身也是有一個轉賬中到轉賬成功的過程,將轉賬異步化對于用戶的感知基本沒有影響
穩定性容災
整個紅包系統的容災我們主要從接口限流,業務降級和多重機制保證狀態機的推進這幾個方式來進行的,下面對這幾個方式分別介紹下:
接口限流
接口限流是一種常見的容災方式,用于保護系統只處理承受范圍內的請求,防止外部請求過大將系統打崩。在進行接口限流前,我們首先需要和上下游以及產品溝通得到一個預估的紅包發放和領取量,然后根據發放和領取量進行分模塊地全鏈路的大盤流量梳理,下面是當時我們梳理的一個 b2c 全鏈路的請求量。
有個各個模塊的請求量后,匯總之后就可以得到各個接口,紅包系統各個服務以及下游依賴的各個服務的流量請求,這個時候再做限流就比較方便了。
業務降級
核心依賴降級
在春節活動期間,紅包系統整個鏈路依賴的服務有很多,這些下游的鏈路依賴可以分為核心依賴和非核心依賴,當下游核心服務異常時,可能某一個鏈路就不可用,此時可以在 API 層直接降級返回一個比較友好的文案提示,等下游服務恢復后再放開。比如在 C2C 的紅包發送流程中,用戶需要完成支付才可以發紅包,如果財經的支付流程異常或者支付成功狀態長時間未完成,會造成用戶支付后紅包發送不成功,也會導致前端來不停的輪訓查詢紅包狀態,導致請求量陡增,造成服務壓力,甚至影響 B2C 的紅包發放和查詢。此時可以通過接口降級的方式,將 C2C 的紅包發放降級返回,減少服務壓力,同時降低對其他業務邏輯的影響。
非核心依賴降級
除核心依賴外,紅包系統還有一些非核心的下游依賴,對于這些依賴,如果服務出現異常,我們可以降低用戶部分體驗的方式來保證服務的可用。比如在 4.2 中我們提到的,用戶在發 B2C 紅包前需要先獲取所有可用的紅包補貼,我們會去獎勵發放端查詢到所有的 Token 列表,然后查詢我們自己的 DB,然后進行 merge 返回。如果獲取 Token 列表的接口異常時,我們可以降級只返回我們自己 DB 中的補貼數據,這樣可以保證用戶在這種情況下還可以進行紅包的發放,只影響部分補貼的展示,而不是影響整個紅包發送鏈路。
多重機制保證狀態機的推進
在紅包系統中,如果某個訂單長時間未到終態,比如用戶領取紅包后長時間未到賬,或者用戶 C2C 紅包未領取長時間未給用戶退款都有可能造成用戶的客訴。因此需要及時準確地保證系統中各個訂單的狀態能推到終態。
這里我們有幾種方式去保證,首先是回調,在依賴方系統訂單處理完后會及時地通知給紅包系統,這種方式也是最及時的一種方式。但是只依賴回調可能會出現依賴方異常或者網絡抖動導致回調丟失,此時我們在紅包的各個階段都會給紅包系統發一個 mq,間隔一定的時間去消費 mq 主動查詢依賴方的訂單狀態進行更新。最后我們對每個狀態機都會有一個定時任務用于兜底,在定時任務多次執行仍未到終態的會 lark 通知,及時人工介入發現問題。
資金安全保證
交易冪等
在編程中,冪等指任意多次執行一個請求所產生的影響與一次執行的影響相同。在資金安全中,通過訂單號來進行相應的冪等邏輯處理可以防止資損的發生。具體來說在紅包系統中,在紅包的發放,領取和退款中,我們都通過訂單號唯一鍵來保證接口冪等。另外紅包系統的補貼發放接口是冪等的,外部同一個單號多次請求發放補貼,我們需要保證只會發一張券。
實現冪等的方案很多,包括有通過數據庫或者 redis 來實現冪等的。最可靠的就是通過數據庫的唯一鍵沖突來實現,但是這種方式在數據庫存在分片實例時會引入一些額外的問題。這里我們就補貼的發放來簡單介紹下,在業務系統的設計中,我們是按照 uid 分片的方式來建立業務的數據庫表,這就導致補貼的分片鍵是 uid,雖然我們也設置了紅包的補貼單號作為唯一鍵。但是其中存在一個風險就是如果上游的系統調用補貼發放時,同一個外部單號更換了 uid,就可能會導致兩個請求分別打到不同的數據庫實例上,導致唯一索引失效,造成資損。為了解決這個問題,我們又額外的引入一個以補貼發放外部單號作為分片鍵的數據庫來解決這個風險。
B2C 紅包核對
除了在開發過程的系統設計上進行相應的資金安全考慮,我們還需要通過對賬的方式來校驗我們的系統是否有資金安全問題。
在 B2C 鏈路中,整個鏈路主要是從補貼發放到紅包領取,我們對這幾個鏈路的上下游的數據都進行相應的小時計 hive 對賬。
C2C 紅包核對
在 C2C 鏈路中,整個主要從用戶發起支付,到用戶領取轉賬以及最后紅包過期退款。在支付,轉賬,退款這三個流程都需要進行相應的核對。同時,還需要保證用戶的紅包發放金額大于等于紅包轉賬金額+紅包退款金額,這里大于等于是因為紅包從發放成功到退款成功整個周期會在 24h 以上,另外可能存在轉賬在途的這種訂導致會有多筆退款單,如果要求嚴格等于的話具體對賬時機沒法控制。
紅包系統的壓測
前面提到過,紅包系統的鏈路包含有多個接口,發領查等,需要模擬用戶的真實行為來進行壓測才能得到系統的真實性能。這里我們使用了壓測平臺的腳本壓測方式來進行壓測。
首先需要對整個壓測鏈路整個改造,和上下游溝通是否可以壓測,不能壓測的需要進行相應的 mock 處理。另外對于存儲服務,數據庫,redis 和 mq 都要確保壓測標的正確傳遞,否則可能會影響到線上。
改造完壓測鏈路后,需要構造相應的壓測腳本,對于 B2C 和 C2C 分為兩個腳本。
B2C 紅包鏈路壓測
上面是 B2C 壓測的整個鏈路,首先是補貼的發放,然后通過查詢補貼,通過補貼來發放紅包,為了模擬多人來領取的情況,我們起了多個 goroutinue 來并發的領取紅包。
C2C 紅包鏈路壓測
C2C 紅包因為涉及到支付相關的操作,整個鏈路又是另外一套流程,因此對于 C2C 也需要有一個單獨的腳本。在壓測流程中,因為涉及到外部系統的依賴,如果等待全鏈路 OK 時再一起壓測可能會導致一些未知的問題出現。因此我們需要自己壓測沒問題后再開始全鏈路一起壓測,在圖中和支付相關的藍色模塊我們都添加了相應的 mock 開關,來控制壓測的結果。在 mock 開關打開時,會直接構造一個結果返回,在 mock 開關關閉時,會正常地去請求財經獲取結果。
后續規劃
服務 Set 化
在前面提到的系統容災中,如果紅包核心服務改掉,或者數據庫 DB 主機房掛掉,將影響所有的用戶,此時只能降級返回,整個系統無法快速切換和恢復。后續考慮將服務改為 set 化的架構。將服務 Server 和對應的存儲劃分為一個單獨的 Set,每個 Set 只處理對應劃分單元內的流量,同時多個單元之間實現流量拆分和故障隔離,以及 Set 之間數據備份。這樣后續在某個單元異常時,可以及時將對應單元的流量切到備份單元中。
加入我們
開放平臺錢包團隊為抖音開放平臺提供底層支付能力支持,目前業務在快速發展中,面臨各種復雜場景和技術挑戰,歡迎郵件聯系:wanghao.rock@bytedance.com 加入我們,一起做挑戰性的事!
總結
以上是生活随笔為你收集整理的2022 春节抖音视频红包系统设计与实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c++ 如何用一个函数实现两个字符串
- 下一篇: java信息管理系统总结_java实现科