漲姿勢!手Q刷一刷紅包後台設計總結

文 / 騰訊 鄧建俊

1. 前言

2016除夕夜註定是一個不平凡的夜晚,除了陪家人吃團圓飯、看春晚,還得刷一刷、搖一搖、咻一咻,忙得不亦樂。相信大部分讀者也已經體驗過手Q的刷一刷搶紅包,玩法簡單中獎率高,得到了許多用戶的好評。

那麼對於後台而言,要實現這個億萬級用戶的搶紅包系統,我們將會面臨哪些問題?

(1)海量的並發請求,預估峰值800w/s

800w/s的預估峰值請求,紅包系統必須要保證在如此高並發的請求下,能夠對每一個請求都及時正確的處理並做出響應。

(2)億萬數量的紅包如何派發

刷一刷紅包的獎品,除了現金紅包外,還有各種卡券(如京東券、滴滴打車券等)和個性裝扮類獎品(如明星氣泡、掛件等),如何保證億萬數量的獎品公平公正的發放,不多發、不錯發,並且保證獎品儘可能的發放出去,是紅包系統面臨的一道難題。另外,與各類獎品相對應的業務,他們的能力也不盡相同,獎品發放速度的控制也成為了後端業務系統正常運作的關鍵。

(3)安全如何保障

對於一個掌握著億萬資金髮放的系統,一些不法分子肯定會想方設法利用各種漏洞來盜取資金,那麼一套完備的安全防護措施就顯得及其重要。

(4)異常情況如何處理

刷一刷紅包系統部署上線,真正提供搶紅包服務也就幾個小時的時間,常規系統的灰度發布、逐步優化的流程顯然不適用。所以我們必須分析出各個可能過載或故障的節點,並對這些節點設計好柔性策略。

2. 抽獎最小系統

在解決上面提及到的問題前,我們先看看一個能基本運作的紅包最小系統,如下圖所示:

主要的處理流程如下:

首先,刷一刷是以活動的形式進行的,每輪活動的信息資源和商家素材資源會預先從CDN預下載到客戶端,客戶端在需要使用的時候,可以直接從本地載入,避免在抽獎峰值時下載從而對CDN帶寬造成衝擊,同時本地取資源也可以有更好的用戶體驗;

客戶端判斷到活動開始時,會展示出刷一刷的入口,用戶進行刷的操作,會產生抽獎請求,抽獎請求發送到手Q接入層SSO,然後轉發到抽獎邏輯層;

抽獎邏輯層經過一系列的邏輯判斷,給客戶端返回一個結果:中了現金、中了虛擬獎品或者沒中獎,中獎後相關信息會保存在存儲層;

如果是中了現金,交由財付通的支付系統對用戶進行充值並發送公眾號消息通知用戶中獎;

如果是中了虛擬獎品,會調用基礎IM後台的介面發送公眾號消息通知用戶中獎並提供領取入口。用戶點擊領取獎品,抽獎系統會給對應的業務返回領用資格和設置已領用標誌。另外,對於某些內外部卡券類的獎品,抽獎系統統一隻對接增值部的禮包發貨系統,用戶領用這部分獎品,會調用禮包發貨系統介面,走MP營銷平台進行發貨。

那麼,如何完善上述的系統,使之既能夠滿足業務的需求,又能夠應對文章開頭所描述的幾個問題呢?

3. 完善抽獎系統架構

3.1 緩存機制

業務要求在每個刷一刷的活動中,能對用戶中獎次數、參與時間(30秒)進行限制。常規的實現里,用戶的每個抽獎請求到來時,先到存儲層獲取用戶的中獎歷史信息,判定用戶是否還有資格進行抽獎,但在高並發的請求下,對存儲層勢必也會造成巨大的壓力。所以這裡我們引入了本地內存緩存層,用於保存用戶的中獎歷史信息,每次請求到來時,會先到緩存層獲取用戶的中獎歷史信息,如果在緩存層沒找到,才會到存儲層獲取,這樣就不會對存儲層造成大壓力,同時也能實現業務的需求。緩存層使用Memcached實現,但單靠這個緩存機制是不是就解決了存儲層訪問壓力的問題了呢?

3.2 一致性hash定址

雖然引入緩存層能夠緩解存儲層的訪問壓力,但別忘了,抽獎邏輯層是一個分散式的系統,在使用普通的路由演算法下,同一個用戶的請求不一定會落在同一台邏輯機器處理,也就是說,不一定能夠命中之前保存在本地的緩存。所以為了使緩存生效,我們在手Q接入層SSO使用了一致性hash的路由演算法進行定址,同一個uin的請求會落在同一台邏輯機器進行處理。

3.3 存儲層選型

存儲層的設計向來都是後台架構設計中的重點和難點。目前公司內部較成熟的存儲系統有CMEM、Grocery,經過一番對比我們選擇使用Grocery,主要原因有以下幾點:

(1)強大的帶條件判斷的分散式原子算數運算

抽獎邏輯里需要對每個獎品進行計數,避免多發少發,所以一個高效可靠的分散式原子加計數器顯得格外重要,Grocery支持帶條件判斷的原子加計數器,調用一次介面就能完成獎品計數值與配額的判斷以及獎品計數值的增加;

(2)靈活的數據類型

Grocery支持Key-Key-Row類型的數據存儲格式,可以靈活的存儲用戶的紅包中獎信息,為獲取用戶單個紅包或者紅包列表提供了豐富的介面;

(3)部署、擴容方便

Grocery在公司內部有專門的團隊支持,易於部署和擴容。

3.4 獎品配額系統設計

刷一刷紅包的玩法是以活動的形式存在的,業務要求每場活動要發放多少份的現金,多少份的虛擬獎品,每種獎品的發放比例是多少,這些都是該場活動的配額數據。另外,業務還要求獎品的發放額度要能夠根據用戶參與數靈活配置,保證在用戶多的時候獎品多,而且不管在活動的任何一個有效時間點進來,都有機會中獎。

在活動期間,如果某個獎品對應的業務出現了故障,需要停止發放該獎品(發放比例修改為0),要求各台抽獎邏輯機器能夠快速穩定的同步到修改的配置。基於以上幾點我們設計了這樣一個配額系統:

在當天活動開始前,根據產品給的配額數據生成一份當天所有活動的Json格式配置;

配額管理工具可以對該Json配置進行相關合法性檢查,檢查OK後,把配置導入到Grocery資料庫,並更新Seq;

運行在各台抽獎邏輯機器的配額agent會定期的檢查Grocery里的Seq,如果Seq發生了變化,表明配額數據發生了變化,然後就會從Grocery獲取最新的配置,並更新到抽獎邏輯的本地共享內存shm;

抽獎邏輯的每個進程也會定期檢查本地shm的內容,發現有變化,就會重新載入shm的配置。

這樣配額系統就實現了可以秒級別控制獎品的發放額度,可以隨時根據現場情況調整發放比例,並在短時間內(10s)同步配置到所有的抽獎邏輯機器。

3.5 獎品發放限頻設計

每一種獎品,對應的業務都有他們自己的能力,且各業務的能力也不盡相同(如黃鑽8w/s,京東1w/s)。一般的,我們在配置配額時(按照100%的兌換率),都不會配置到各業務的峰值,但配額系統有一個風險的不可控制的,就是進來的用戶數是不確定的,但為了保證獎品都能發放出去,獎品前1秒沒發完的額度,會累加到下1秒發放,這樣獎品發放峰值就有可能超過業務能力。

舉個例子,黃鑽的處理能力為8w/s,第1秒有5w的配額,第2秒有5w的配額,假如從第1秒開始就有足夠多的用戶參與抽獎,用戶中獎後100%會去兌換黃鑽,那麼就有5w/s的請求到達黃鑽業務,業務能夠正常處理。假如由於一些意想不到的情況(例如xx寶在第1秒也在搞活動吸引走了部分用戶),第1秒只有1w的用戶,第2秒突然湧進9w/s的用戶,那麼第2秒將有9w/s的請求到達業務致使業務過載。

基於以上原因,獎品發放限頻邏輯就成為了保障後端業務正常運作的關鍵。那麼,如何設計這個限頻邏輯呢?首先想到的方法就是到存儲層Grocery獲取當前獎品計數器的值,拿回本地與前1秒的值做判斷,即可保證不超峰值,但這種做法會對存儲層造成巨大的衝擊,顯然的不可行的。那麼如何在保證性能的前提下,精確實現這個限頻邏輯呢?

我們是這麼做的,活動前抽獎邏輯層的機器數是確定的,我們只要控制每台機器獎品的最高發放頻率,就能整體控制該獎品的發放頻率,最壞的情況可能是某些邏輯層機器故障了,可能會造成該獎品在配額充足的情況下不能按峰值發放,但我們可以通過修改配置重新設置每台機器該獎品最高的發放頻率來解決這個問題。對於單機獎品發放頻率計數我們使用了Memcached的increment原子加操作,以時間和獎品ID作為Key,計數值作為Value存儲在內存里,即可實現精確的計數。

上圖左側是我們一開始的實現,當時時間是以秒作為計數周期的,也就是說,如果配額和用戶數都充足的話,獎品會在這1秒的最開始全部發送出去,這樣的話,問題又來了,一般來說業務方給的能力例如8w/s,是指在這1秒內相對平均的來了8w的請求,業務方剛好能夠正確處理這8w的請求。但是如果請求是在1秒的最開始全部涌到業務方,受限於業務方不同的架構實現,有可能會觸發業務方的頻率限制或者是過載保護。因此,我們將計數周期調小到百毫秒,這樣獎品就會在1秒內相對平均的發放,從而解決了上述問題。

3.6 抽獎演算法設計

抽獎演算法的設計並不複雜,大體流程如下:

需要注意的是,每個獎品都有發放的時間段,只有在該時間段內,才會把該獎品放入獎池裡。另外,從獎池裡按照比例挑選獎品,只要該獎品未成功派發,就繼續從獎池再次挑選,直到獎池空為止,這樣就能保證獎品儘可能的派發出去。

3.7 流水系統設計

流水系統主要用於活動後對獎品發放和設置領用進行統計和對賬。同時,該系統還對設置領用失敗的請求進行重做,確保獎品發放到用戶賬戶里。流水系統架構如下:

由於流水需要記錄用戶中獎的信息和設置領用的的情況,數據量巨大,所以抽獎邏輯層本地採用順序寫文件的方式進行記錄。抽獎邏輯層會定期的把本地的流水文件同步到遠程流水系統進行匯總和備份,同時,流水系統會對領用失敗的流水進行重做,發送請求到抽獎邏輯層,抽獎邏輯層會調用支付系統的介面完成領用操作。

3.8 安全防護設計

安全防護對於抽獎系統來說,也是必不可少的,我們有以下幾種措施來保障系統的安全:

(1)賬號鑒權

對於到達抽獎系統的請求,都要求帶上登錄態,利用基礎IM後台完善的PTLogin介面,對用戶進行身份驗證。

(2)實時安全審計

對於每一個抽獎請求,我們都會記錄必要的信息(如客戶端IP、版本、序列號等),加上原始請求一併轉發到安全部門,由安全部門對所有的抽獎請求進行多維度的監控,對惡意請求進行打擊。

(3)uin請求頻率限制

對於每個用戶(uin)的每種請求(抽獎、設置領用等),我們都做了請求頻率限制,超過頻率限制的請求會直接返回錯誤。頻率限制必要的信息(時間、令牌)存儲在Memcached里。

3.9 完善後的抽獎系統

4. 異常處理策略

4.1 接入層SSO過載

接入層SSO是手Q終端使用後台服務的大門,如果SSO出現問題,後果將不堪設想。抽獎請求在SSO預估的峰值為800w/s,雖然評估預留了一定的餘量,但評估的數據還是具有風險的,所以在接入層SSO我們做了以下保護措施:

(1)錯峰機制

抽獎活動開始後,用戶會隨機被錯峰一定的時間,避免大量用戶在同一時間發起抽獎。錯峰的最大時間配置會隨活動配置一起保存在CDN資源庫中,在活動開始前預下載到客戶端本地。獎品的配額數據也會根據錯峰後的用戶模型進行合理配置,保證公平性。

(2)實時可調的抽獎間隔

客戶端發起抽獎請求是有時間間隔的,默認間隔也是保存在活動配置里。如果發現SSO即將過載,SSO可以在抽獎回包裡帶上新的抽獎時間間隔,從而達到減少抽獎請求的目的。

4.2 抽獎邏輯層過載

抽獎邏輯層設計的峰值能力為300w/s,假如即將超過該峰值,會使用如下措施進行保護:

(1)SSO調整請求透過率

SSO設計了一個抽獎請求透過率的配置,默認100%透過,正常情況下,客戶端發起的抽獎請求到達SSO後,都會轉發到抽獎邏輯層。假如抽獎邏輯層即將過載,SSO調整該透過率,就會按照比例隨機對請求進行直接回包,回包內容指示用戶未中獎。

4.3 存儲層Grocery過載

Grocery如果即將過載,也可以通過調整SSO透過率來減少請求。

4.4 財付通現金充值介面過載

財付通現金充值介面能力為20w/s,配額系統配置該峰值也是配置成20w/s,如果發現20w/s財付通出現異常,可以減小峰值,然後通過配額系統快速同步修改後的峰值到所有的邏輯層機器。

4.5 虛擬獎品對應業務過載

每個虛擬獎品對應的業務都有自己的峰值能力,配額系統也是按照業務給定的峰值能力進行配置,如果業務反饋異常,可以實時修改業務對應獎品的峰值或直接關閉該獎品的發放。

4.6 其他異常處理

其他模塊的異常一般都是可以通過調整中獎率、關閉開關或關閉重試進行處理:

5. 運營部署

接入層和抽獎邏輯層IDC級容災部署;

業務接入織雲運維自動化平台,支持平滑擴容,進程實時監控並支持秒起;

存儲層數據雙份容災。

推薦閱讀:

後台開發 和 伺服器開發有什麼 異同 ?
使用了https後,還有必要對數據進行簽名來確保數據沒有被篡改嗎?
軟體測試職業道路怎麼走?
Mac下Web開發為為什麼都用Sublime而不用VIM呢?
如何做一個優秀的後台產品經理?優秀的後台產品經理有哪些?

TAG:后台开发 | 移动开发 | 后台技术 |