背景
營銷類業務中經常有一些抽獎活動類的玩法,如大轉盤、紅包雨、老虎機、搖一搖等。在業務支持過程中,經常遇到需求頻繁變更,定製化需求多等問題。
比如某次抽獎需求:
1.每個用戶都有多次抽獎機會,但每次的概率又不同;
2.抽獎活動周期內,為控制預算並保證活動效果,對每天的獎品數量進行控量;
3.為應對「非正常」用戶,在每天指定的時間段,降低中獎概率;
4.……
等等的此類抽獎玩法需求,每場抽獎活動形式大同小異,但背後的業務需求又存在差異,定製化程度特別高。
早期我們的處理方式是每次都針對需求重新開發,那時需求較為簡單,抽獎活動數量也較少。隨著業務規模日益擴大,抽獎活動頻率越來越高,我們先開發一套簡單的配置式後臺,支持簡單的獎品和概率的配置。
但是實際使用中,也有一些問題,比如:
1.差異化抽獎規則很多,根本無法通過簡單的概率配置,滿足業務需求,定製化成本很高;
2.對於大型專題中抽獎活動的並發控制、數據一致性等提出很多挑戰;
3.兄弟業務線也有類似的需求,但基本需要重新實現一遍,系統復用性很低。
考慮到以上問題,加之抽獎玩法是一個相對獨立、應用場景廣泛的業務,於是我們開發一套通用的抽獎系統,提供系統級業務復用的能力,系統單獨維護、單獨部署,保證通用性和可伸縮性。下面從:
等幾個方面介紹一下這個系統。
為保證整體的靈活性,本系統採用領域模型驅動設計(DDD),對系統中不同業務模塊進行抽象:
上圖中的業務概念:
活動:抽獎活動的主體,多數場景下,一場抽獎活動就是一個活動對象。
輪次:與活動是一對多的關係,例如:一場秒殺活動,可能有多個場次,多個場次可以只報名一次,但每個場次的獎品又不同。類似場景,可以採用多個輪次解決。
輪次獎品:多輪抽獎,獎品雖然一樣,但是概率不同。於是引入輪次獎品的概念,不同輪次就可以針對同一個獎品,設置不同的概率。庫存同理,在輪次+獎品的維度維護其對應關係,而不是直接綁定在獎品上。不同輪次可選相同的獎品、不同的庫存。
概率組:多條抽獎概率信息,組成一個概率組,如:用戶首次抽獎,中獎概率是50%,第2到第5次抽獎,中獎概率提高為70%,於是系統把概率獨立出來,並引入概率組的概念。這樣就可以在一個概率組裡配置兩條概率,一條是第1次的概率50%,一條是第2到第5次的概率為70%。
中獎規則:描述每次抽獎行為響應結果的方式,比如:活動周期內,用戶有3次抽獎機會,但是只能中獎1次,即:之前中過獎,就不能再中。這時通過把中獎規則獨立出來,並且在抽獎活動和輪次上都關聯中獎規則,這樣創建一個抽3次中1次的中獎規則,然後在抽獎活動和輪次都選這個規則,從而實現需求。
反作弊規則:抽獎活動經常面臨被一些黑產薅羊毛的處境,識別正常用戶和非正常用戶的成本較高,且較難保證準確。雖然前端可以通過圖片驗證碼、簡訊驗證碼等方式來提高門檻,但是道高一尺魔高一丈,還是有被突破的風險。因此本系統引入反作弊規則的概念,在特定用戶、時間段等維度,對抽獎行為進行限制。
回調規則:作為一個獨立系統,其他業務系統調用抽獎後,可能需要做記錄中獎信息,發送簡訊等異步處理,於是加入異步回調功能。
業務實體模型如下:
系統實現過程如下:
1. 根據設計的模型,為每一個實體定義接口和一個工廠類,通過接口只暴露行為,工廠類依據不同的類型屬性,去構建對應的實例。不同類型的實現類可以根據需要使用不同的屬性欄位去實現功能。
2.在進行一次抽獎時,需要明確到某一個輪次,構建一個抽獎輪次,除了輪次數據外,還有抽獎活動、獎品等其他數據,顯然多次資料庫查詢性能肯定不能滿足接口要求,因此我們引入自主開發的分布式實時內存緩存組件,提升查詢性能的同時,也保證了數據的實時性。
3. 當構建好一個輪次實例後,便開始處理抽獎流程,過程依次分為:前置驗證、執行抽獎邏輯、後置驗證、入庫、返回結果五個步驟:
A. 前置驗證:主要基於Redis,包括抽獎規則驗證,風控驗證,庫存驗證。B. 執行抽獎邏輯:根據輪次的輪次獎品集合進行抽獎。實現上,採用離散算法,實現按權重進行抽獎的邏輯,主要包括三種情況:第二種,概率之和小於1:
即P1+P2+P3<1,由此計算出為中獎概率1-P2-P2-P3=0.1,若random落在0.9-1中間,則未中獎,落在其他區間,則命中對應的獎品
第三種,概率之和大於1:
即P1+P2+P3>1,此類情況則為100%中獎,每個獎品的權重按概率之和的佔比計算,如P1的權重計算公式為:P1/(P1+P2+P3)。
用實際數字舉例:P1權重= 20/(20+40+60) = 0.16,然後取隨機數判斷中獎結果。
。
C. 後置驗證:由於價值類獎品存在隨機問題(如:現金紅包有不同金額),只有在抽中之後才會明確價值,因此需要後置驗證庫存,防止超賣問題。系統設計上,支持活動和獎品共用庫存,此處分兩種情況,共用庫存時,只驗證統一庫存;非共用時,需同時驗證活動庫存和獎品庫存,此處驗證同樣基於Redis。
D. 入庫:保存中獎記錄並扣減庫存,需保證在一個事務中執行,以保證數據完整性和一致性,同時這裡利用資料庫鎖進行庫存扣減,防止庫存超賣。
E. 返回結果:中獎結果響應包括實時與異步兩種,實時結果返回一些簡要信息,異步結果返回詳細中獎信息,供調用方後續處理。
系統只保存中獎用戶記錄,防止大量無效抽獎記錄信息擠佔資料庫資源。同時在抽獎過程中,每一個中斷處(所有導致未中獎而退出的地方)異步記錄抽獎日誌,便於問題排查。
為保證良好的擴展性,上述業務對象在實現時都增加了類型欄位(如圖3),通過策略模式實現。如庫存早期只有兩種類型,後來業務需求指定日期設置庫存數量,只需要增加一種按日期設置次數的庫存類型,實現一個對應的子類即可,其他邏輯保持不變。
前文介紹的大部分系統概念都是採用這種實現方式,若有新的需求接入,可增加新的實現類,然後進行重新組合,系統的擴展採用「重新組合「的方式替代原有的」邏輯調整「。最終業務中約90%的場景都可以通過配置來實現,剩下的10%也可以通過在某一個對象或者多個對象增加類型來快速支持。
系統經歷了兩次汽車之家818百城車展、及多次大訪問量業務考驗,這歸功於系統在並發能力上的優化。
在接入層,系統對接了汽車之家API網關,對於請求進行限流,特別是直播間秒殺大額獎品的情況,瞬時流量可能達到數萬QPS。這種情況下,對於非預期內的超量請求,直接返回預設中獎結果。
在應用層,對性能也有較高要求。一是查詢性能,需要支持在多個業務系統的展示,二是抽獎操作的性能,要做抽獎資格校驗、抽獎次數校驗、中獎次數校驗、庫存校驗、中獎規則校驗等。考慮到獎品配置等信息變動頻率低,訪問頻次高,變化時要求及時生效,系統引入了支持本地緩存實時更新的基礎數據組件,將獎品配置信息緩存在了應用本地緩存中,後臺修改時會觸發MQ廣播消息,實現緩存實時更新,既保證性能,又保證及時更新。
獎品配置信息緩存在本地緩存中,查詢性能基本得到了保證。對於抽獎操作,除了要獲取獎品配置信息,還需要做抽獎基礎規則校驗、抽獎資格校驗、抽獎次數校驗、中獎次數校驗、庫存校驗、反作弊規則校驗、二次庫存校驗(金額類的庫存,本次中獎金額+已中獎金額<=總金額),這些校驗中用到的配置信息可以從本地緩存拿到,用戶資格、抽獎次數、中獎次數等都存到Redis中,以此保證抽獎操作的性能。
為了方便業務方使用,我們搭建了一個獨立的後臺管理系統。
主要功能包括:抽獎活動創建和編輯,抽獎記錄維護等。
另外,對於一些常見的抽獎活動玩法(比如:大轉盤、紅包雨等),我們開發了抽獎活動玩法庫(抽獎活動類型不斷更新中),如下圖5。
業務組件基於VUE開發,組件通過綁定抽獎活動Id,可以實現自動加載抽獎活動配置的獎品、UI樣式等。開發人員對接這些抽獎活動玩法時,僅需要引入相應的VUE抽獎活動組件即可使用整套抽獎系統。
抽獎活動的獎品和概率配置完全在管理後臺進行維護,這部分工作可以由運營或者產品同學去完成,這樣就實現了職責分離。開發只需要關心組件需要加載哪個抽獎活動信息即可,不用關心獎品,中獎概率。甚至組件的樣式和文案都不用關心,因為樣式和文案也可以通過後臺系統配置。產品和運營可以在抽獎活動上線後,在不需要開發介入的情況下,隨時對產品需求進行變更。
為了更好的理解以上內容,我們將整個系統完整的內容展示在下圖中:
上線不到一年時間,目前該系統已經完整支持了幾百場抽獎活動,累計抽獎千萬次,整體運行穩定。
我們的系統也提供了抽獎活動全流程的支持,運營後臺、服務端應用、UI前端組件、獎品發放及最後統計報表,用戶可以靈活選擇其中的全部或者部分流程使用到自己的業務中。我們在系統的使用過程中,文章開頭提到的痛點問題,基本都得到了解決。
1. 提供了:概率(組)、中獎規則、反作弊規則等概念,靈活支持各種多變的需求,並在代碼層面採用策略模式進行抽象,方便系統擴展;
2. 使用網關實現流量控制,系統層面,借用Redis+DB雙查詢方式,保證實時性和可靠性;
3. 完整的Saas平臺加UI組件的模式,基本實現百分百復用,目前部門內部有多個業務使用我們的系統,節約大量開發工作的同時,快速支持業務。