本文主要從服務端角度針對 2022 年春節 Flower 活動中錢包提現模塊做一下總結與反思,希望可以對整個開發過程中使用的技術和遇到的問題進行整理和沉淀,在后續類似的活動中可以產生一些幫助。
一、活動背景
與交互流程2022 年春節活動目標是在抖音、火山、西瓜等八端啟動,希望抖音端能夠給多端進行導流,實現“同一個字節,同一個春節”活動。對用戶來說,可以在任意一端參與春節活動并在錢包中看到集卡、紅包雨等玩法獲得的所有收入,最終可以在任一端提現春節收入至個人賬戶中,保證用戶在活動中的獎勵能夠落地,提升用戶春節活動參與度。
交互流程
用戶在進入活動錢包頁后可查看參與活動獲得的獎勵收入,點擊【去提現】按鈕可以跳轉到提現頁面。在提現頁面用戶輸入提現的金額并選擇已綁定的到賬方式, 然后點擊【確認提現】即可提現活動收入到自己選擇的個人賬戶中。
二、大流量下的主要問題
提現即用戶將自己通過參與集卡、紅包雨等活動玩法所活動的獎勵收入提取至用戶的銀行卡、支付寶或抖音零錢等個人賬戶中。由于春節活動存在集中開獎導致的高流量,活動發獎瓜分金額巨大等特點,在開發春節活動提現的過程中,有幾個方面需要重點考慮:
1. 入賬延遲與提現限制
除夕當晚 19 點到 23 點每個整點開放紅包雨,19:30 春節活動集卡開獎與煙火大會啟動。此時面對上百萬 QPS 的用戶獎勵入賬,可能存在部分請求入賬存在延遲,導致用戶在提現的時候看到的金額與活動參與獲得獎勵金額不一致的情況。
此外今年春節活動提現增加了每筆訂單 1 元起提的門檻限制,在除夕晚上集卡開獎、紅包雨和煙火大會多個玩法的加持下,用戶很容易獲得 1 元以上的收入。但如果在獲得收入達到門檻后立即提現,可能會導致參加后續玩法獲得獎勵較少而無法提現的情況。
2. 高并發
集卡開獎與紅包雨后,提現入口打開時將面對幾十萬的請求流量,經過用戶選擇到賬方式和輸入提現金額后也有數萬 QPS 的提現下單請求。錢包服務端收到請求后會操作扣除用戶的活動賬戶余額,接著調用財經側(字節內部的支付中臺)請求出款,財經側維護與各支付機構(支付寶、銀網聯)的接入交互。但各支付機構分配給各單位的出款請求流量有限額,字節這邊獲得的容量與提現出款相差了一個數量級。此時需要在保障用戶的提現體驗不受影響的同時,又能夠確保下游渠道側不會因流量較高導致可用性抖動。
3. 資金安全
提現是春節活動的最后一道流程,公司在用戶的春節活動收入賬戶進行扣款并將資金通過預先設置的備付金賬戶轉入至端上綁定的個人賬戶中,從而使獲得的獎勵最終落地。如果用戶在端上的操作出現打款超額等情況,一旦出款成功則基本不會有追回的可能,因此,資金安全是提現業務開發過程中必須考慮并保證的部分,確保每筆出款有跡可循且符合提現規則。
三、設計方案
為解決上述問題,我們通過 RocketMQ 進行異步出款來保證用戶體驗,同時 RocketMQ 的使用還可以對銀行卡等出款渠道進行削峰來減少下游的過高流量。在資金安全方面,每筆訂單在進行春節活動收入賬戶扣款和現金出款時做了冪等操作,并增加對賬任務對所有流水進行對賬校驗。
1. 延時放量
除夕當晚從 19:30 集卡活動開獎用戶進入主會場即可看到集卡獎勵,同時煙火大會開啟參與活動即可獲得紅包獎勵,此外從 20:00 開始到 23:00 每個整點都會有紅包雨,用戶會不斷獲得春節活動獎勵并進入錢包頁查看個人收入。
為保證用戶集卡開獎和紅包雨活動入賬順利,不會出現看到獎勵但錢包中無收入或收入不足導致提現錯誤的問題,我們在用戶進入提現頁面的時候會根據端上請求參數中的紅包 token 列表進行一次入賬請求,以確保用戶在確認提現下單前賬戶中的金額如果沒有完成入賬的話可通過 token 列表進行一次強制入賬,但為給用戶在提現頁面有較好體驗,此處的入賬為弱依賴請求。當用戶確認提現下單的時候,我們設置了強依賴性的強制入賬作為最終兜底方案,來使得最終提現扣款時獎勵金額已經入賬成功并支持提現。
同時,為保證用戶可以在活動結束后提現參與獲得的所有獎勵,減少因提現門檻導致用戶參與后續玩法獲得獎勵較少而無法提現的情況,我們在春節活動中開啟了延時放量提現。從 19 點到凌晨 1 點之間關閉提現入口,用戶只能在主會場參與活動獲得獎勵,而無法進入錢包頁面中進行提現操作。當用戶在活動錢包頁點擊【去提現】時,會彈窗提示用戶在 2 月 1 日 01:00 后可提現。此外,在彈窗中我們也加入了對用戶的綁卡營銷策略,引導用戶預先綁卡,提現快人一步。
隨著凌晨一點提現入口打開,可能會有大量用戶涌入提現頁面進行提現。此時,為防止瞬間流量突增過高可能引發數據庫連接問題,我們通過在配置平臺上進行配置,針對用戶 id 進行取模后的結果進行分批放開。在有限的情況下,確保用戶入賬無誤,請求不被限流是我們用戶體驗是否良好一個比較重要的評判因素。延遲到凌晨 1 點分批次放開提現有效降低了用戶提現的并發,保障了用戶提現體驗。
2. MQ 異步出款
在除夕當天的晚上 19:00 到春節凌晨 01:00 時間段內,春節活動錢包頁中會暫時關閉提現功能,進行部分營銷導流。而隨著凌晨 01:00 提現開關打開,請求會蜂擁而至逐步上漲至數萬 QPS,但由于銀網聯的處理能力有限,導致銀行卡渠道出款最高可支持的 QPS 只有幾千。此時如果提現模塊不進行限速下單的話,可能存在下游系統被壓垮引起雪崩的風險,同時用戶會給感受到提現功能卡頓并頻繁失敗。
為解決該問題,我們引入了 RocketMQ 來進行異步出款。當用戶在錢包頁進行提現操作時,服務端會在春節活動收入賬戶扣款完成后立即返回結果并跳轉至提現結果頁面展示當前狀態,同時將當前請求參數發送至 MQ 中進行異步消費出款。這樣給用戶的感覺即賬戶余額已扣除,提現出款進行中,稍后也可以通過賬單流水查詢提現結果。
將消息發送到 MQ 后,提現模塊利用 MQ 消費提現訂單的現金出款,通過下游消費者有限的消費能力進行消息處理。同時增加自定義限流器對每個出款渠道進行限流,利用 MQ 進行流量削峰與限流出款兩種方式雙重保證了下游出款不會因流量過高而出現抖動。當消費成功時則順利出款,當消費失敗或被限流時則返回錯誤,MQ 會進行消費重試。我們在這里設置 MQ 最大重試次數為 3 次,如果消息沒有超過最大重試次數,則被放入 retry 隊列;如果消息達到最大重試次數,則放入死信隊列不再處理。
2.1 定時任務
為防止提現訂單因 MQ 多次重試消費失敗或其他原因導致狀態一只卡在某個中間狀態停止更新,我們額外設置了定時任務進行補單操作推進提現狀態。每小時固定從數據庫中撈取已被創建超過 4 個小時且當前還處于未完成狀態的訂單,并根據其當前狀態進行推動:
- 待扣款的訂單,則說明用戶的賬戶收入還未進行扣款,此時則直接將訂單狀態推進為失敗狀態;
- 待出款狀態的訂單,請求財經接口進行出款操作,推動狀態到出款中或出款完成;
- 出款中的訂單,查詢財經出款訂單的狀態,如果財經側已成功或失敗則將該狀態同步更新到提現訂單中,如果財經側查單不到的話則調用財經出款接口進行重試;
- 對于從任一狀態流轉至失敗的訂單,我們會查詢賬戶的訂單流水,如果賬戶側存在余額扣減流水的話,則操作進行余額退回,保證失敗的訂單不會扣減用戶的收入。
3. 提現資金安全
在提現的過程中,一旦技術方案設計有問題,容易存在資金安全問題:賬戶未扣款但現金已轉入用戶的個人賬戶,賬戶多次扣款或者現金多次出款等。因此,在春節活動中提現模塊的設計中,資金安全問題是重點考慮的部分。在提現請求發生時,服務端需要確保每筆訂單一定對應一次賬戶余額扣減,一次現金出款。而提現完成后,需要有對賬任務與賬戶和財經出款進行對賬,分別對提現訂單的金額和狀態進行校驗,保證事件中的驗證無誤。
3.1 訂單冪等
冪等,指任意多次執行所產生的影響均與一次執行的影響相同。提現針對 orderID 做冪等性控制,在賬戶側每個 orderID 只有一筆扣款操作,從而保證用戶的活動賬戶余額不會被重復扣款;同時,在用戶當前訂單提現失敗后進行賬戶余額回滾操作時,首先查詢賬戶側是否存在扣款訂單,如果存在則進行余額退回,退回時控制一筆扣款操作對應一筆退回流水,防止出現多退的情況。
賬戶完成扣款之后,需要調用財經的出款接口將資金從公司預先設置的備付金賬戶轉入至端上綁定的個人賬戶中,此時需要確保每筆提現請求只能有一次出款。在每次操作提現訂單進行現金出款時,我們使用 redis 分布式鎖對 orderID 進行加鎖操作,加鎖成功后判斷當前訂單狀態,如果是待出款狀態則調用財經接口進行現金出款。在接口調用后立即更新訂單狀態為出款中,防止重復調用引發可能出現的重復出款操作。同時,財經側也針對 orderID 做了冪等控制,確保每筆 orderID 都對應一筆出款。
3.2 對賬校驗
涉及到資金流動,需要有對賬任務來保證上下游之間資金數據的一致性,能夠及時發現處理金額或狀態差異導致的資損問題。我們在對賬平臺分別增加了準實時對賬和天級對賬來進行資金的校驗。
準實時對賬
在提現事件發生過程中,我們在對賬平臺中增加了與下游服務(賬戶、財經)提現數據的準實時對賬,確保提現訂單每次狀態變化時都是準確無誤的:
1. 與賬戶側準實時對賬:
a. 在余額扣減成功后賬戶側會保存一筆提現扣款的數據流水,此時需要將扣減流水與提現訂單進行金額和狀態校驗,確保扣款狀態和金額一致;
b. 在提現失敗的時候,如果此時賬戶側已經扣款成功的話則需要將之前扣減的金額退回至用戶的活動賬戶中,此時需要在余額退回成功后進行賬戶金額退回流水與提現訂單狀態、金額做校驗。
2. 與財經側準實時對賬:
a. 在提現訂單狀態更新為成功或失敗時,獲取財經側的出款訂單數據與提現訂單數據進行一致性校驗,判斷雙方數據的狀態、金額是否一致。
對賬平臺中的數據校驗,是基于數據雙方的 binlog 消費進行的準實時對賬,在對賬雙方任一方缺失數據或雙方對賬狀態金額出現不一致的情況下便會發送飛書報警通知。
此外,我們還在線上服務中增加了自主對賬任務,通過消費提現數據庫的 binlog 消息。針對其中到達終態的訂單,我們會根據訂單狀態分別通過接口調用的方式對賬戶、財經側進行查單檢查。成功的提現訂單在賬戶的查單接口中可以查到一筆扣款流水,在財經的查單接口中也會有一筆出款成功的訂單。而失敗的提現訂單在賬戶的查單接口中可以查到一筆扣款和一筆退回余額的流水,在財經的查單接口中如果可以查到訂單的話則只有失敗訂單,如果沒有訂單的話說明提現流程還沒有走到出款便失敗了,此時可忽略缺失的差異。
天級對賬
另外,我們也增加了與下游服務(賬戶、財經)的天級對賬,作為準實時對賬之外的一種兜底對賬。因為上下游之間可能因調用失敗或者回調失敗導致狀態同步不及時,我們增加了定時任務進行訂單狀態推進,保證每筆提現訂單最終都可以達到終態。天級對賬即為了解決狀態同步不及時可能引發的準實時對賬差異,通過每天生成的 hive 數據進行狀態和金額校驗,減少時間差產生的誤報。
四、前期預案
為保證活動上線后用戶能夠在錢包頁中進行正常提現,我們在活動開始前增加了準備預案。
1. 提前演練
在活動正式開始前,我們組織了內部圈定人群、內部所有人、外部圈定城市等三次預演,針對春節活動紅包雨和現金提現進行了提前演練,模擬春節活動的正常流程與突發情況的處理。通過演練,我們可以提前發現整個活動流程是否順利,并將可能存在的問題提前暴露解決。
2. 充分壓測
為支持春節活動過程中產生的大流量請求,保證給用戶提供良好的活動體驗,我們將春節活動現金提現與錢包日常收入提現的功能進行了集群隔離。在代碼開發上線后,申請壓測資源對各業務流程進行了預估流量壓測,集群隔離也使得壓測操作不會對線上正常業務有任何影響。
在提現業務壓測的過程中,有兩個方面需要做一些數據準備:
- 查詢到賬方式接口需要對壓測的 uid 構造已綁定的到賬方式結果返回;
- 確認提現下單接口需要對壓測的 uid 有綁定到賬方式的同時,還需要 uid 對應的活動賬戶中有足夠的金額支持余額扣減。為解決此問題,財經的同學在壓測之前提供了一批已綁定到賬方式的測試 uid 生成的文件, 方便我們在進行到賬方式查詢接口壓測的時候能夠從文件中指定 uid 參數。
另外,在賬戶同學壓測入賬接口的時候,我們提供了該文件讓其幫助對這批 uid 進行入賬。如此在壓測提現下單的時候,我們使用已經綁定到賬方式的測試 uid 數據,其活動賬戶中已經存在余額可支持提現余額扣減。
最終,通過對到賬方式查詢、確認提現下單等接口進行全鏈路壓測后,我們能夠準確評估了為支持春節活動最高 QPS 所需的各項資源容量,使春節活動可按照預先計算的流量支持用戶操作提現。
3. 除夕當日執行劇本
除夕是我們春節活動啟動后的重要時間點,當天有多場紅包雨,同時還有煙火大會和集卡開獎等玩法出現,整個活動會在春節聯歡晚會開始的時候達到高潮。在這種大型活動參與過程中,每個人或多或少都會有一些壓力在身上。縱使代碼經過驗證,前期進行過多次演練均無問題,但還是需要抱有謹慎的態度來應對重要活動的開始。在大腦記憶力有限的情況下,為防止出現遺漏,我們針對除夕當天寫了執行劇本:
- 執行細節。從除夕上午十點開始到初一凌晨兩點,每場紅包雨前需要做什么準備,紅包雨發生時需要查看哪些監控指標,紅包雨后是否需要記錄數據等,執行劇本需要詳細記錄每個時間點需要做的事情。
- 配置校驗。提現業務在春節活動上有活動配置與限流等。在活動開始前需要再次做一遍檢查,確保各項配置和限流均正確無誤。
- 容災方案。除執行細節和配置校驗外,我們還在劇本中加入了容災預案,方便在某項流程出現問題的時候能夠及時根據預案進行處理。
- 交叉檢查。劇本中的各項操作細節和配置檢查均為兩個人分工進行,通過交叉檢查的方式防止出現一人疏忽大意而錯誤改動的情況。
五、活動總結
春節活動上線后,用戶積極參與各種玩法并在其活動錢包中進行現金提現。在除夕當晚,延時放量雖然使用戶在獲得收入的第一時間不能進行提現,但用戶獎勵入賬正常,延時放開后提現請求沒有被出款渠道限流,有效地保障了用戶提現體驗。同時,在渠道側出款能力有限的情況下,通過使用 MQ 進行異步出款有效地限制了對下游服務的請求流量,使其沒有因流量過高而導致出款異常。
此外,在春節活動的整個時間段內,通過對提現流程進行風險梳理,增加對賬平臺準實時與小時級對賬支持和線上服務對賬支持,雙重保障了春節活動現金提現模塊對賬任務的全覆蓋,使用戶在參與活動并收獲獎勵進行提現的過程中未對其造成資金損失,確保了用戶的參與度。