高頻面試題:秒殺場景設計

科技繆繆發表於2020-11-04

秒殺這個話題到現在來說已經是一個老生常談的話題了,不過因為又臨近一年一度的雙11,而且發現前段時間無論是阿里還是騰訊一些大廠其實還是在頻繁的問到這個場景題,所以還是準備拿出來說說。

秒殺從規模上來說可以分為大秒和小秒。大秒指的是比如雙11這種特定的節日,商品規模超大、價格超低、流量超大的這種型別活動,小秒一般指的是商家自己配置的一些時段型別的活動,由商家自己指定時間上架。從形式來說還可以分為單時段秒殺和多時段秒殺。但是在這個場景裡,我們一般就是指的單時段大型秒殺。

秒殺設計要面對的壓力和難度有幾點:

  1. 怎麼保證超高的流量和併發下系統的穩定性?如果峰值的QPS達到幾十萬,面對巨大的流量的壓力系統怎麼設計保證不被打崩?

  2. 怎麼保證資料最終一致性?比如庫存不能超賣,超賣了那虧本的要麼就是商家要麼就是平臺,使用者反正不背這個鍋,超賣了就今年325預訂。

當然,涉及到這種大型的活動,還需要考慮到資料統計分析,總不能活動做完了,效果不知道怎麼樣。

系統架構

假設今年的雙11預估峰值QPS將會有50萬(我隨便扯的),而根據我們平時的經驗單機8C8G的機器可以達到1000左右的QPS,那麼從理論上來說我們只要500臺機器就可以抗住了,就有錢任性不行?這麼設計的話只能出門右轉不送了。

流量過濾

本質上,參與秒殺的使用者很多,但是商品的數量是有限的,真正能搶到的使用者並不多,那麼第一步就是要過濾掉大部分無效的流量。

  1. 活動開始前前端頁面的Button置灰,防止活動未開始無效的點選產生流量
  2. 前端新增驗證碼或者答題,防止瞬間產生超高的流量,可以很好的起到錯峰的效果,現在的驗證碼花樣繁多,題庫有的還要做個小學題,而且題庫更新頻繁,想暴力破解怕是很難。當然我知道的還有一種人工打碼的方式,不過這個也是需要時間的,不像機器無限刷你的介面。
  3. 活動校驗,既然是活動,那麼活動的參與使用者,參加條件,使用者白名單之類的要首先做一層校驗攔截,還有其他的比如使用者終端、IP地址、參與活動次數、黑名單使用者的校驗。比如活動主要針對APP端的使用者校驗,那麼根據引數其他端的使用者將被攔截,針對IP、mac地址、裝置ID和使用者ID可以對使用者參與活動的次數做校驗,黑名單根據平時的活動經驗攔截掉一部分羊毛黨等異常使用者。
  4. 非法請求攔截,做了以上攔截如果還有使用者能繞過限制,那不得不說太牛X了。比如雙11零點開始還做了答題限制,那麼正常人怎麼也需要1秒的時間來答題吧,就算單身30年手速我想也不能超過0.5秒了,那麼針對剛好0點或者在0.5秒以內的請求就可以完全攔截掉。
  5. 限流,假設秒殺10000件商品,我們有10臺伺服器,單機的QPS在1000,那麼理論上1秒就可以搶完,針對微服務就可以做限流配置,避免後續無效的流量打到資料庫造成不必要的壓力。針對限流還有另外一種柵欄方式限流,這是一種純靠運氣的限流方式,就是在系統約定的請求開始的時間內隨機偏移一段時間,針對每個請求的偏移量不同,如果在偏移時間之內就會被攔截,反之通過。

效能優化

做完無效流量的過濾,那麼可能你的無效請求已經過濾掉了90%,剩下的有效流量會大大的降低系統的壓力。之後就是需要針對系統的效能做出優化了。

  1. 頁面靜態化,參與秒殺活動的商品一般都是已知的,可以針對活動頁面做靜態化處理,快取到CDN。假設我們一個頁面300K大小,1千萬使用者的流量是多少?這些請求要請求後端伺服器、資料庫,壓力可想而知,快取到CDN使用者請求不經過伺服器,大大減小了伺服器的壓力。
  2. 活動預熱,針對活動的活動庫存可以獨立出來,不和普通的商品庫存共享服務,活動庫存活動開始前提前載入到redis,查詢全部走快取,最後扣減庫存再視情況而定。
  3. 獨立部署,資源充足的情況下可以考慮針對秒殺活動單獨部署一套環境,這套環境中可以剝離一些可能無用的邏輯,比如不用考慮使用優惠券、紅包、下單後贈送積分的一些場景,或者這些場景可以活動結束後非同步的統一發放。這只是一個舉例,實際上單獨針對秒殺活動的話你肯定有很多無用的業務程式碼是可以剝離的,這樣可以提高不少效能。

經過這兩步之後,最終我們的流量應該是呈漏斗狀。

超賣

秒殺除開高併發高流量下的服務穩定性之外,剩下的核心大概就是怎麼保證庫存不超賣了,也可以說要保證的是最終一致性。一般來說,針對下單和庫存有兩種方式:

  1. 下單即扣庫存,這是最常規的大部分的做法。但是可能在活動中會碰到第二點說到的情況。

  2. 支付完成扣庫存,這種設計我碰到過就是酒店行業,廉價房放出來之後被黃牛下單搶佔庫存導致正常使用者無法下單,然後黃牛可以用稍高的價格再售賣給使用者從中牟利,所以會有在一些活動的時候採取支付成功後才佔用庫存的做法。不過這種方式實現起來比較複雜,可能造成大量的無效訂單,在秒殺的場景中不太適用

針對秒殺建議選擇下單扣庫存的方式,實現相對簡單而且是常規做法。

方案

  1. 首先查詢redis快取庫存是否充足
  2. 先扣庫存再落訂單資料,可以防止訂單生成了沒有庫存的超賣問題
  3. 扣庫存的時候先扣資料庫庫存,再扣減redis庫存,保證在同一個事務裡,無論兩者哪一個發生了異常都會回滾。有一個問題是可能redis扣成功了由於網路問題返回失敗,事務回滾,導致資料庫和快取不一致,這樣實際少賣了,可以放到下輪秒殺去。

這種做法能一定程度上解決問題,但是也有可能會有其他問題。比如當大量請求落在同一條庫存記錄上去做update時,行鎖導致大量的鎖競爭會使得資料庫的tps急劇下降,效能無法滿足要求。

另外一種做法就是排隊,在服務層進行排隊,針對同一個商品ID的也就是資料庫是一條庫存記錄的做一個記憶體佇列,序列化去扣減庫存,可以一定程度上緩解資料庫的併發壓力。

質量保障

為了保證系統的穩定性,防止你的系統被秒殺,一些質量監控就不得不做。

  1. 熔斷限流降級,老生常談,根據壓測情況進行限流,可以使用sentinel或者hystrix。另外前端後端都該有降級開關。
  2. 監控,該上的都上,QPS監控、容器監控、CPU、快取、IO監控等等。
  3. 演練,大型秒殺事前演練少不了,不能冒冒失失的就上了吧。
  4. 核對、預案,事後庫存訂單 金額、數量核對,是否發生超賣了?金額是否正常?都是必須的。預案可以在緊急情況下進行降級。

資料統計

活動做完了,資料該怎麼統計?

  1. 前端埋點
  2. 資料大盤,通過後臺服務的打點配合監控系統可以通過大盤直觀的看到一些活動的監控和資料
  3. 離線資料分析,事後活動的資料可以同步到離線數倉做進一步的分析統計

總結

總的來說,面對巨量的流量我們的方式就是首先通過各種條件先篩選掉無效流量,進行流量錯峰,然後再對現有的系統效能做出優化,比如頁面靜態化,庫存商品預熱,也可以通過獨立部署的方式和其他的環境做隔離,最後還要解決高併發下快取一致性、庫存不能超賣的問題,防止大量的併發打爆你的資料庫。

一個完整的活動從前端到後端是一個完整的鏈路,中間有事前的演練工作,事後的資料分析等都是必不可少的環節。

- END -

相關文章