如何設計一個秒殺系統?

假如你是一名架構師,你會如何設計一個秒殺系統?這篇文章,我們就來聊一聊。

1. 什麼是秒殺?

所謂秒殺,就是在同一個時刻有大量的客戶端請求爭搶同一個商品並完成交易的過程,瞬時會產生大量的併發讀和併發寫。

秒殺系統本質上就是一個滿足高併發、高性能和高可用的分佈式系統,下面給出一張下單交互概要圖:

2. 秒殺系統的特點

高性能

秒殺涉及大量的併發讀和併發寫,因此秒系統必須能支持高併發訪問,而且 RT(響應時間) 需要在一定的範圍內,通常是 200ms

一致性

秒殺系統中通常會使用緩存,如何保證緩存和數據庫中庫存數據的一致性,保證商品庫存的準確性

高可用

秒殺系統會在瞬間收到大量的讀寫操作,如何能保證服務能穩定的運行,設計系統時是否考慮到系統容災問題,保證服務的高可用

可擴展性

當服務達到瓶頸時,如何能實現快速擴容。

3. 如何設計秒殺系統

從在上述秒殺概要圖中,我們可以知道,整個秒殺流程需要從前端和後端 2 個核心部分進行,因此我們就從這 2 個部分來講解秒殺系統是如何設計的。

3.1 前端秒殺設計

服務高可用

前端是秒殺的入口,用戶首先是到前端界面進行商品瀏覽,然後加購自己想要的商品進行下單付款操作。 所以,前端服務一定要保證高可用,要不然秒殺的入口都沒有了,談何秒殺。

頁面靜態化

前端數據源動靜分離,靜態的數據可以放到 CDN,前端從 CDN 獲取,動態的數據放到服務器。 靜態數據,比如商品的詳情信息,圖片等;動態數據,商品的數量,價格等。 比如:可以通過 Url 地址作爲 key 來存儲靜態數據

控制對服務器請求的頻率

控制對服務器請求頻率能在一定程度上緩解服務器的壓力,限頻的方式有很多, 比如 秒殺按鈕點擊後置灰一定的時長後才能再次點擊, 前端 答題正確後才向服務器發起請求,前端將請求加入隊列進行排隊,當有多個秒殺活動時,可以分時段進行,這些方式都是無損的。

控制對服務器請求參數的大小

因爲秒殺期間,瞬時會有大量的請求湧向服務器,所以前端和服務器的數據交互要儘量的少,減少網絡傳輸以及編解碼的開銷

限流,降級

當下遊服務器達到瓶頸時,可以採用前端限流方式,降低對服務器的 TPS 和 QPS。但是當客戶端比較分散時,限流閾值的設置是一個比較大的挑戰:閾值設的太小,會導致服務端沒有達到瓶頸時客戶端已經被限制;設的太大,則起不到限制的作用。

3.2 後端秒殺設計

服務高可用

後端是處理請求的核心服務,所以必須做好高可用部署,容災設計 (異地多活)

降級,限流,拒絕服務

降級,就是當系統的容量達到一定程度時,限制或者關閉系統的某些非核心功能,從而把有限的資源保留給更核心的業務。所以降級一般需要前後端配合執行,可以通過開關係統來實現。比如: 當 QPS 達到一個閾值時,可是設置開關,將原來分頁查 50 條數據,變成查 10 條,減少一次交互的數據量。

限流,就是當系統容量達到瓶頸時,通過限制一部分流量來保護系統,限流可以是接口級別,服務器級別,iP 級別等等,此處的限流是有損操作,限流的閾值一般可以根據壓測結果來設置

直接拒絕服務,如果限流還不能解決問題,那就直接拒絕服務以求自保,這也是最差的一種兜底情況。

獨立部署秒殺服務

秒殺系統和普通的售賣有一定的差異點,秒殺一般是持續時間短,併發量高,所以爲了不影響正常的售賣,可以單獨部署一套秒殺服務,在物理級別進行隔離,也適合服務端靈活伸縮容以及做一些特殊的個性化處理。 有條件的團隊可以實施。

流量削峯當服務流量過大時,可以將請求存入 MQ 消息中間件進行削峯處理,客戶端可以採用輪詢的方式向服務器獲取結果 (服務器會受到很多結果查詢的請求),或者服務主動 push 結果給客戶端 (服務需要保留很多和客戶端的長鏈接),2 種方式各有優劣,一般生產上輪詢查詢結果用的比較多。

熱點數據探測

很多時候,一個商品不屬於秒殺,但是很多用戶購買,可能會成爲熱點數據,請求量不亞於秒殺,所以網關需要有熱點數據探測的功能,實現的方式有很多,比如:統計客戶端的請求數

增加緩存

秒殺一般遵從讀多寫少的 28 法則,所以可以在服務端增加緩存應對高併發讀。緩存可以設置 2 層,第一層是本地緩存,可以使用 Google guava 的緩存框架,失效時間一般可以秒級別,本地緩存是屬於 jvm 級別的,每次失效後可以從 redis 緩存中加載,redis 緩存要特別注意緩存失效,緩存擊穿,緩存雪崩的問題。

緩存擊穿:緩存中不存在,數據庫存在,這樣就會導致請求直接到達數據庫,當請求量比較大時,可能直接把數據庫打垮。解決方法:

  1. 可以考慮緩存永遠不過期

  2. 同步返回 null,異步加鎖查詢數據庫,更新緩存

緩存穿透:請求的數據在緩存和數據庫中都不存在,解決辦法:

  1. 業務層進行合法校驗,攔截大部分不合法的請求

  2. 使用布隆過濾器,針對一個或者多個維度,把可能存在的數據值 hash 到 bitmap 中,bitmap 中不存在則該數據一定不存在,bitmap 中存該數據可能存在

  3. 對空的結果進行緩存,設置得較短過期時間,當有數據庫變更時,必須同時刷新緩存,否則會導致不一致的問題存在

緩存雪崩:指緩存在同一時刻失效,請求都到數據庫上,解決的辦法:

  1. 可以考慮緩存永遠不過期

  2. 失效時間儘量隨機,避免同時過期

  3. 多級緩存,數據緩存到 A 和 B,A 設置過期時間,B 不設置過期時間,如果 A 爲空的時候去讀 B,同時異步去更新緩存,需要同時更新兩個緩存

4. 如何保證不超賣?

秒殺一般都是優惠售賣,所以庫存不超賣是前提,一般來說,防止超賣需要前後端配合,以下是幾種主要的實現方式:

4.1 庫存扣減方式

下單減庫存: 買家下單後,扣減商品總庫存。下單減庫存是最簡單也是控制最精確的一種,下單時直接通過數據庫的事務機制控制商品庫存,一定不會出現超賣的情況。出現的問題: 惡意刷單,某些人下單後佔用庫存不付款。

付款減庫存: 買家付款之後,扣減商品總庫存。這種方式產生的問題是,庫存超賣。

預扣庫存: 買家下單後,預扣庫存,在一定的時間內未付款,庫存將會自動釋放。在買家付款前,需再次校驗庫存是否保留,如果沒有保留,則再次嘗試預扣;如果庫存不足則不允許繼續付款;如果預扣成功,則完成付款並實際地減去庫存。這種方式在生產上用的比較多。

4.2 服務端庫存處理

將庫存操作的邏輯放到 lua 腳本中,通過 redis 的單線程特性,保證 Lua 腳本執行不會被打斷,從而保證庫存操作的原子性

5. 面試中如何回答

  1. 先分析整個流程,然後再按前端和後端兩部分去分析

  2. 前端用到什麼技術,目的是什麼,會出現什麼問題,如何解決

  3. 後端是側重點,可從業務架構,技術架構,分佈式鎖,緩存,數據一致性,容災來分析

  4. 最後,做個總結

6. 總結

下面給出一張秒殺系統常用的架構圖,百種業務百種架構,一個秒殺系統看似簡單,其實包含了很多架構的思想,從前端到後端,怎麼全局把控,對於各個服務怎麼去做高可用,高性能,可擴展保證。如何設計緩存,如何保證緩存和數據庫的數據一致性,服務達到瓶頸時,如何做服務降級,限流。

一般我們遵從幾個原則:

  1. 前後端交互的數據儘量少

  2. 前端儘量控制對後端的無效請求

  3. 服務之間的依賴儘量少

  4. 請求路徑儘量短

  5. 服務或者中間件不要有單點,要有容災

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/nZRnTvqTHT3BdnyYbN9P0g