大家好,我是樹哥。
秒殺系統的設計是高階職位面試中非常高頻的一道題目,它可以較好地考察候選人的知識體系情況。對於我們來說,學習秒殺系統的設計,能夠讓我們學以致用,設計系統的時候考慮得更加全面。今天就讓樹哥帶你一起來看看怎麼設計一個秒殺系統!
活動一般出現在電商的促銷活動中,一般是指定了很少數量的商品,以極低的價格,讓大量的使用者參與,從而造成大量使用者在極短的時間內參與活動,進而造成系統在極短的時間內有極高的流量。系統設計的目的是使系統能夠穩定地支撐活動的進行,因此其穩定性、高可用是我們考慮的第一位。
要知道如何進行秒殺系統的優化,那我們需要先對請求的整個流程有個全域性的認識。一般來說,秒殺活動請求以公網為劃分點,可以分為:前端部分、後端部分。 前端部分指的是從使用者端到進入後端服務前的部分,包括了行動端的處理、DNS 解析、公網的資料傳遞等。後端部分指的是經公網進入了後端的伺服器網路里,包括了前置的負載均衡(Nginx 等)、應用伺服器、資料庫層等。秒殺活動的整個流程可以用下圖來表示。
我們要去設計一個秒殺系統,那自然也是從這兩大部分來進行優化。整體思路是儘量將流量擋在前面,讓儘量少的流量留到後端部分。因為越往後端,我們的處理邏輯就越重,其處理能力也越弱。
對於前端部分來說,常見的優化手段有:頁面靜態化 + CDN、請求頻率限制。
一般來說,活動頁面是流量最大的地方。活動頁面上絕大部分內容都是固定的,比如:商品描述、圖片等。這時候沒有必要每次都去請求伺服器端,而是將這些靜態的內容放到 CDN 上。每次開啟頁面的時候,直接去請求 CDN 伺服器,能極大地減少後端的請求流量。加入了 CDN 之後,其請求過程如下:
所謂的 CDN 就是內容分發網路,它由非常多臺分佈在世界各地的快取伺服器組成。每次使用者請求特定域名的時候,會轉發到對應 CDN 的 DNS 解析伺服器,隨後會返回一臺離使用者地理位置最近的一臺 CDN 伺服器。隨後,使用者直接請求這臺 CDN 伺服器獲取資料,從而極大地減少了長途網路傳輸的時間,並且也減少了後端伺服器的壓力。
因此,對於秒殺活動設計來說,我們可以將所有可以靜態化的內容全部靜態化,然後將其設定在 CDN 伺服器上。這樣既提高了使用者開啟頁面的時間,又減少了後端伺服器的壓力。
請求頻率限制,指的是根據業務的特點,在前端做一些流量攔截,減少後端伺服器的壓力。常見的攔截方式有:
通過這種方式,我們可以減少很大一部分流量。但在具體實現的時候,可能需要考慮安全問題,預防某些使用者直接呼叫後臺介面,繞過前端的頻率檢查。常見的方法是在頻率檢查時生成一個引數,隨後請求後端服務時攜帶上該引數。沒有該引數的請求,都視為非法請求,直接拒絕該請求。
無論我們做多大的努力,始終還是會有不少流量會來到後端伺服器這裡。一般來說,後端的優化有如下幾種方式:
如果我們所有資料都去讀取資料庫,資料庫可能無法承受較大的流量,此時一個常見的優化就是增加快取層。
當我們需要查詢資料庫之前,我們先去查詢快取,這樣可以減少絕大部分的資料庫請求,減輕資料庫壓力。如果在快取中找不到資料,我們再去請求資料庫,隨後再將資料快取到快取中。在引入快取層的時候,我們需要考慮快取擊穿、快取穿透的可能性,在寫相關程式碼的時候就要做好這些優化。
另外,我們在秒殺活動開始之前,可以手動將熱點資料載入到快取中,從而避免秒殺時去請求資料庫。
我們知道秒殺活動一般涉及搶購、下單、支付、發貨等階段,而搶購與後續的幾個階段是可以非同步執行的。為了避免對下單、支付、發貨等階段產生影響,我們可以將搶購階段與後續階段用 MQ 進行解耦處理。當用戶搶購成功後,往訊息佇列中丟入一臺訊息,隨後再由訂單系統消費進行下單處理。
通過各系統之間的解耦處理,我們可以將原本同步的處理方式變為非同步處理,從而大大的減少了請求的處理時間,提高了系統的並行處理能力。其次,也能避免系統之間相互影響,提高了整體系統的穩定性。
雖然我們做了非常多的優化措施,但還是可能存在請求超量的可能性,那怎麼辦呢?
我們可以在每個業務系統做限流操作,從而避免因為請求太多,導致整個系統都無法運作。當並行請求在正常範圍內時,我們正常處理請求。當超過設定的限流閾值時,我們則直接拒絕該請求,提示使用者搶購失敗。
如果沒有限流操作,那麼系統直接崩潰了,一個請求都處理不了。而通過限流這種方式,系統至少還可以保持正常工作,而不至於一個請求都處理不了。而超量的需求,本來就處理不了,因此提示失敗也是情理之中。
除了限流之外,不同的系統還可以採用熔斷、降級的服務治理措施。
熔斷指的是請求的錯誤次數超過閾值時,不再到用後端服務,直接返回失敗。同時每隔一定時間放幾個請求去重試後端服務,看看是否正常。如果正常則關閉熔斷狀態,如果失敗則繼續快速失敗。熔斷的目的是避免因下游短暫的異常,導致上游不斷重試,最終造成下游有太多請求,最終壓垮下游系統。
降級指的是當服務失敗或異常後,返回指定的預設資訊。降級的目的是保證有基本的資訊,當下遊異常時,與其返回空資訊,不如返回一個有業務含義的預設資訊,可以提高使用者體驗。
一般來說,經過上述的整體優化之後,系統已經能夠比較穩當地應對秒殺活動了。如果此時還是流量比較大,那麼或許應該從業務側去進行優化了。
例如 12306 剛開始的時候,購買時間都在同一時刻,這導致同一時刻並行量太大,系統經常支撐不住。後來 12306 將購票週期放長,可以提前 20 天購買火車票。通過業務側的優化,我們將本來在 1 個小時的搶購分攤到了 20 天,伺服器壓力一下子降低了 480 倍!
張小龍也說過:如果公司最厲害的程式設計師來實現業務都覺得複雜,那很可能就是業務確實不合理,這時候應該從業務側進行優化。
例如一個儲存了 10 億條記錄的訊息記錄表,業務側既想查詢速度快,又想進行 1 年資料範圍的資料查詢,這無論如何都是無法實現的。這時候就需要從業務需求側進行優化,否則是無法兩全其美的。對於這個場景,一個合理的實現方式是:要實現 1 年資料範圍的查詢,那麼只能根據訊息 ID 進行,因為這樣可以使用上索引。而要根據時間範圍進行查詢,只能縮短查詢時間到 3 天內,這樣也可以滿足業務需求。
因此從業務側進行優化,是一個四兩撥千斤的辦法,可以極大地降低技術側實現的難度。
設計一個秒殺系統,整體而言可以從前端與後端進行優化。
對於前端優化而言,可以從「頁面靜態化 + CDN」、請求頻率限制進行優化。
其中「頁面靜態化 + CDN」指的是將不變的靜態資料固定下來,然後放入 CDN 伺服器,從而降低使用者請求的響應速度,降低伺服器的並行壓力。請求頻率限制,則是通過搶購概率與搶購頻率限制,降低後端伺服器的服務壓力。
對於後端優化而言,一般有「增加快取層 + 預熱資料」、「MQ 非同步處理」、「限流、熔斷、降級」、業務側優化這 4 種優化方式。
其中「增加快取層 + 預熱資料」指的是將熱點資料存入快取,並在活動開始前提前載入到快取中,降低資料庫層的讀取壓力。「MQ 非同步處理」指的是對於非必要的業務邏輯,通過 MQ 進行非同步處理,降低請求處理延時,同時提高業務系統整體穩定性。
「限流、熔斷、降級」是對於整體微服務的保護,其中限流指的是對請求進行限制,當超過限流閾值時,直接拒絕請求,保護系統本身;熔斷指的是保護下游系統,當請求下游系統連續錯誤超過閾值時,自動不去請求下游系統,避免因重試流量過大擊垮下游系統。降級指的是當請求失敗時,自動返回預設資料,提高使用者體驗。業務側優化,則是指從業務層面去進行邏輯優化,從而降低技術複雜度,使得業務與技術複雜度達到一個平衡的狀態,有利於更好地實現秒殺系統的高可用與高並行。
上面說到的 6 個優化思路,是設計秒殺系統常見的優化思路。但在實際業務場景中,除了要保障正常的功能設計之外,還還考慮防刷、安全、黑產等問題,此時可能需要多考慮一些其他優化,例如:黃牛利用搶購工具搶購,導致正常使用者無法搶到商品等。這時候可能需要考慮增加驗證碼,用 App 裝置指紋等風控措施。此外,對於秒殺系統而言,做好業務指標和系統指標的埋點監控也是非常重要的。