秒殺系統設計與實現


問題描述

如何設計並實現一個秒殺/搶購系統

過去都說台上十分鍾,台下十年功,而秒殺系統更有意思,瞬時的流量峰值可能就三兩分鍾,但你卻必須為此做大量的准備工作。容量評估是否做好了,帶寬是否ready,前后端截流是否完備,是否需要隊列化請求等等。

設計難點

瞬時峰值

瞬時峰值會挑戰服務器帶寬

秒殺的一瞬間,帶寬可能是平常時的幾倍幾十倍,一瞬間帶寬可能就跑滿了。

瞬時峰值會挑戰應用服務器資源

幾十倍的流量,如果后端架構沒有足額的設計。會在極短的時間內雪崩,秒殺類的業務,活動結束的時候流量又會斷崖式的下跌,沒有前期良好的設計,幾乎不可能在出現峰值的短短三兩分鍾里給出有效的應急方案。另外,如果服務間沒有良好的隔離,也會影響其他業務服務的運作。

瞬時峰值會挑戰DB負荷能力

如果大量的請求落到DB,海量的請求,讀寫一份庫存數據,讀寫沖突,會出現大量的鎖與等待,接下來就是龜速響應跟崩潰了。

思路

越早攔截,成本越低,吞吐量越大

簡單抽象,常見的應用架構是這樣的

接口應用層(APP/瀏覽器等)-> 服務層 -> 存儲層

核心思路是要把請求到達存儲層之前盡可能多地攔截掉,越多的請求走到后面,架構與硬件成本就越高。搭建百萬並發又支持事務的DB集群,可比一個百萬級並發讀的靜態HTTP服務集群難多了。

如何攔截

接口應用層(APP/瀏覽器等客戶端)

按鈕置灰,防止重復點擊

//####
// 防止一個頁面中重復點擊
//####
if button_is_clicked
    return
    
button_is_clicked = yes
post_data()

//####
// 校驗是否5秒內點擊過,
// 防止多個頁面重復點擊
//####
if clicked_within_5_seconds
    return

//跨頁面存儲在本地,如cookie
clicked_within_5_seconds = yes
post_data()

錯峰提交

搶購開始后,為避免大量流量極短時間內涌入服務端,可以在客戶端要求用戶執行一些操作后才能點擊購按鈕,分流壓力。譬如,

  1. 計算一個簡單的數學題
  2. 輸入驗證碼
  3. 輸入一串中文
  4. 回答一個調皮的問題

異步顯示搶購結果

搶購與搶購結果的顯示,產品上應避免設計進一個同步流程里,這樣一方面可以為服務端贏得喘息的機會,也可以第一時間響應信息給用戶,提高體驗。

同步流程:

點擊搶購 -> 同步等待服務端結果 -> 顯示結果


異步流程:

點擊搶購 -> 服務端響應201 -> 顯示搶購中的頁面 

-> 【若干秒】后異步拉取搶購結果 -> 顯示結果

此處的【若干秒】也有很多想象空間。

譬如說,50%的用戶是5秒后到服務端拉取結果,

50%的用戶是10秒后。這樣也同樣實現了錯峰。

服務層

基於user_id去重,防止刷量

前端保護是非常重要的一環,可以有效攔截普通用戶,但也是最不可控的一環,有許多可以繞過的方法。需要服務端根據一個唯一標識再進行一次單用戶去重攔截。偽代碼與客戶端類似

//####
// 校驗該用戶是否5秒內點擊過
//####
if user_clicked_within_5_seconds
    return

限制並發連接數

以IP為條件限制並發連接數,會出現一定的誤殺概率。一般會冗余一定的量,在誤殺與有效攔截間取一個平衡。

使用MQ,攔截到DB的量

  1. 維護一個請求計數,只通過比實際庫存量稍大的請求到MQ里,其余請求響應已搶完

  2. 使用若干個worker更新庫存搶購


//請求數小於庫存量130%
if mq_count < quantity * 1.3
    push_to_mq()
    mq_count++
    return 201

response('搶光啦')


//worker更新庫存
while mq_has_data
    //樂觀並發鎖
    update_quantity_where_version_is_1()

緩存的使用

扛多讀少寫的並發,緩存的合理使用尤為關鍵。

1,客戶端緩存:靜態資源放到CDN里,回源請求要盡可能少。

2,服務端緩存

(1)熱點讀取的數據,需要預熱好放到redis/memcache,甚至本地緩存中

(2)超量的請求響應,避免在運行時拼裝,可以建立一套網關機制,直接響應本地緩存。(如:Nginx-Lua)

常見問題

如何點亮搶購

1,客戶端計時,時間到之前置灰按鈕

優點:實現簡單

缺點:客戶限制很容易被繞過

2,服務端維護一個計時服務器,當計時完成,推送結果到各個服務器,秒殺開始。

優點:實現簡單,worker只需要監聽推送結果即可。

缺點:計時服務器有單點問題,且推送到各個服務器時間上有先后,容易出現有些請求落下來搶購開始,有些沒有開始。瞬間壓力會撐爆搶購開始的機子上。

3,Redis中存儲一個ttl為搶購開始時間的key,各個服務器通過校驗key是否過期,來判別活動開始。

推薦使用:一個高可用的Redis集群,能輕松扛過10萬+的並發讀

如何托底

最外層的LB,如果是基於硬件的。需要有一個崩潰閾值,一旦超量,要么直接拋棄連接。要么路由到一個CDN里的文件,提示“搶光啦”等。

超賣問題

加鎖,通過前面的攔截,DB層的量已經所剩無幾。果斷加樂觀並發鎖。

帶寬/服務器擴容

活動前需要進行容量評估,秒殺系統的部署也需要獨立於其他的應用服務器。類似阿里雲/騰訊雲的按量付費服務器是個不錯的選擇,活動結束后再把數據同步回自己的服務器。

肉雞問題

這是最讓人頭疼的問題,職業羊毛一般有大量的賬號。往往從各個維度上看,都是正常的用戶,防不勝防。但依然有一些方式可以防范

1,IP風險評估

2,實名認證

3,根據賬號過往的交易進行風險評估,過濾高風險的賬戶

4,如果依賴於第三方平台,可以使用他們系統本身的風控功能,比如:騰訊的天御 https://cloud.tencent.com/document/api/295/1774


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM