問題描述
如何設計並實現一個秒殺/搶購系統
過去都說台上十分鍾,台下十年功,而秒殺系統更有意思,瞬時的流量峰值可能就三兩分鍾,但你卻必須為此做大量的准備工作。容量評估是否做好了,帶寬是否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()
錯峰提交
搶購開始后,為避免大量流量極短時間內涌入服務端,可以在客戶端要求用戶執行一些操作后才能點擊購按鈕,分流壓力。譬如,
- 計算一個簡單的數學題
- 輸入驗證碼
- 輸入一串中文
- 回答一個調皮的問題
異步顯示搶購結果
搶購與搶購結果的顯示,產品上應避免設計進一個同步流程里,這樣一方面可以為服務端贏得喘息的機會,也可以第一時間響應信息給用戶,提高體驗。
同步流程:
點擊搶購 -> 同步等待服務端結果 -> 顯示結果
異步流程:
點擊搶購 -> 服務端響應201 -> 顯示搶購中的頁面
-> 【若干秒】后異步拉取搶購結果 -> 顯示結果
此處的【若干秒】也有很多想象空間。
譬如說,50%的用戶是5秒后到服務端拉取結果,
50%的用戶是10秒后。這樣也同樣實現了錯峰。
服務層
基於user_id去重,防止刷量
前端保護是非常重要的一環,可以有效攔截普通用戶,但也是最不可控的一環,有許多可以繞過的方法。需要服務端根據一個唯一標識再進行一次單用戶去重攔截。偽代碼與客戶端類似
//####
// 校驗該用戶是否5秒內點擊過
//####
if user_clicked_within_5_seconds
return
限制並發連接數
以IP為條件限制並發連接數,會出現一定的誤殺概率。一般會冗余一定的量,在誤殺與有效攔截間取一個平衡。
使用MQ,攔截到DB的量
-
維護一個請求計數,只通過比實際庫存量稍大的請求到MQ里,其余請求響應已搶完
-
使用若干個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