每次打完滴滴, 咱們均可以分享領券頁面到朋友圈, 讓你們一塊兒來領券. 而領完券後, 一大堆5折券到帳的感受必定很爽(惋惜如今的折扣愈來愈少了). 想必你們都對滴滴的優惠券影響深入. 滴滴的用戶規模如此之大, 送券力度如此之高, 若是由咱們來作,該如何構架這樣一個穩定且有擴展性的系統呢?數據庫
咱們這裏主要考慮這兩個方面的擴展:緩存
業務擴展
變動或新增業務邏輯時, 儘可能對已有的核心模塊影響最小,保證系統總體的穩定性.微信
性能擴展
保證系統是能夠水平擴展的, 從而具備應對更高的負載能力.多線程
一個系統的穩定性,除了須要由健壯的代碼來保證外, 架構上的拆分,也會有至關大的影響.
好比:咱們將易變的模塊(需求變化頻繁)和穩定的模塊糅合在一塊兒部署的話, 易變模塊的變化形成整個系統頻繁的部署,就會對整個系統帶來極大的風險.
因此擴展性在系統設計之初就須要着重考慮.架構
拋開具體業務的架構都是耍流氓, 那咱們先從功能和性能需求上對要作的優惠券系統有個總體上的認識, 來看看哪些需求是易變的,哪些需求是穩定的.併發
優惠券做爲在線交易系統(電商,O2O)的一種重要營銷手段, 每張優惠券的生命週期都由兩個階段組成:app
發券
能夠由運營建立一個優惠券活動, 讓用戶主動領券, 也能夠由運營對指定範圍內的用戶批量發券.性能
用券
消費者收到優惠券後,在結算頁從優惠券列表中選擇優惠券並使用.優化
運營人員根據促銷檔期和財務計劃, 制定優惠券活動的方案,包括:spa
發券方式: 用戶主動領取仍是被動接收
發券內容: 發券的種類, 發券數量, 是否多種券組合發放.
促銷方式: 見下文
使用限制: 見下文
通知方式: 短信通知,郵件通知,app通知
從瞬時單次發券的用戶數量來看, 發券活動分爲:
對單用戶發券:
用戶大促抽獎領券,新用戶註冊領券,訂單完成後返券以及對投訴用戶補償券時會採用這種領券活動.
從功能上看,對單用戶發券的玩法多種多樣,屬於易變的需求.
從性能上看,單用戶發券請求量較高,同時也很是強調券到帳的及時性,在用戶發出領券請求後,系統應當儘快將券送達帳戶.
對多用戶發券:
從功能上看,在補貼大戰,提高交易額以及喚醒沉睡的老用戶時會採用,通常會結合短信通知和郵件通知的方式來讓用戶知曉有券到帳. 多用戶發券的業務較爲穩定,通常是運營人員經過後臺選擇特定範圍內用戶列表後觸發.
從性能上看,對多用戶發券的請求量較小,但發券量比較大,可能一次發放上百萬張券到用戶,因此多用戶發券更強調發券系統的穩定性.
從券的促銷方式來看, 優惠券種類通常有:
立減:
優惠金額固定的優惠券, 好比滴滴的現金券.
折扣:
優惠金額跟訂單金額成比例的優惠券, 好比滴滴的折扣券.
其餘促銷
不一樣的優惠券促銷方式, 會對應不一樣的優惠金額計算邏輯.
從用戶使用角度看, 優惠券還會有多種使用限制:
限時間:
優惠券的有效期
限平臺
好比設定一種券只能在微信h5平臺上使用
限地域
好比只能在上海使用該優惠券
其餘限制
限制邏輯保證了優惠券促銷花的錢,都用到了提高對應維度的運營數據上.
需求總結:
易變的需求:
對單用戶發券的方式(請求量大)
穩定的需求:
發券活動管理(請求量小)
對多用戶發券的方式(請求量小)
優惠券的促銷方式
優惠券的使用限制
根據功能與性能需求,咱們對優惠券系統模塊作如下劃分:
優惠券發券平臺
發券系統做爲核心系統,須要應對極大的訪問量. 服務須要水平擴展,必須使其成爲無狀態, 所以服務的狀態數據存儲在Redis中.發券系統提供如下API:
活動管理API
批量發券API
單用戶發券API
將單用戶發券API和批量發券API拆分是由於兩個接口的使用場景不太同樣:批量發券的發券量很是大,但時效性要求不高, 能夠作更多的優化.
查詢優惠券API
提供查詢全部優惠券和查詢計算頁可用優惠券的功能
用券API
優惠券活動平臺
提供運營人員管理優惠後臺, 給用戶抽獎領券的功能.其子模塊屬於易變的模塊, 因此用子系統單獨部署的方式對系統總體穩定性更高.子系統包括:
活動管理
批量發放後臺
領券子系統(抽獎)
領券子系統(註冊返券)
領券子系統(訂單返券)
優惠券數據統計平臺
經過讀取線上優惠券數據的備份,進行數據報表生成的平臺, 提供運營數據分析是使用, 後續再也不作介紹.
領券子系統都依賴於優惠券發券平臺的單用戶發券接口,屬於優惠券上層業務子系統. 將上層業務子系統跟發券核心系統分離,能夠在保證發券系統穩定性的前提下, 經過提供單用戶發券API, 來更靈活的實現多種發券活動業務,好比咱們能夠輕鬆的構建一個領券子系統: 兌換碼, 來實現用戶線下掃碼兌換優惠券的功能.
運營人員建立優惠券活動時, 會設定本次活動可發優惠券數量.對於用戶主動領券的活動,當該活動的優惠券被領完後,用戶沒法再次領取改活動的優惠券. 要保證優惠券不超發,有兩種方案:
經過全局計數器的方式,保證併發下發券的數量變動的原子性.
經過先預生成優惠券存放到隊列來實現.用戶領券時從隊列pop取出優惠券,再和用戶進行綁定完成發券操做.當隊列pop不到優惠券時,就表明優惠券發完了.
優惠券會有一個根據指定規則生成的全局惟一碼做爲交換ID, 在優惠券各個子系統中傳遞.好比:
XXYYZZ-20171016-001234567
XXYYZZ前綴表明業務碼
中間20171016爲時間
後綴001234567表明當前碼的生成數量
若是咱們在用戶主動領券時才生成惟一碼, 會存在多節點,多線程併發的問題,致使序號重複生成.通常須要加鎖處理,會下降系統的性能.解決這個問題,咱們可使用預生成的方式,在單節點單線程中生成惟一碼,再存入Redis隊列中提供後續pop使用,從而避免加鎖帶來的性能降低.
結合以上兩個問題, 咱們採用Redis隊列做爲存放預生成的優惠券的券池.能夠極大的提高系統的性能和穩定性.
當優惠券活動開始時, 用戶的領券操做會修改數據.當用戶在結算頁使用優惠券時,查詢完優惠券後,會立刻使用優惠券. 因此整個流程優惠券數據的讀寫比操做接近1:1.
常規的緩存策略爲:
用戶的全部優惠券爲讀緩存
在優惠券被寫入時, 失效用戶的全部優惠券緩存.
當用戶優惠券緩存不存在時從新從數據庫加載全部緩存.
這種緩存策略雖然簡單,但實際應用中緩存命中率很是低.由於大多數場景都是用戶領完券後當即使用.
另外用戶的發券操做會涉及到寫數據庫,大量的寫請求會形成數據庫的瓶頸. 要提升系統性能.就必須使用寫緩存, 即:
當用戶寫入優惠券時,先不寫入數據庫,直接寫入集中式緩存Redis,
全部查詢操做都直接從Redis中查詢.
Redis中的新數據經過指定數量的線程, 源源不斷的同步到數據庫.
整個過程如圖所示: