秒殺系統

lovevivi121發表於2024-11-28

前言

秒殺大家都不陌生。自2011年首次出現以來,無論是雙十一購物還是 12306 搶票,秒殺場景已隨處可見。簡單來說,秒殺就是在同一時刻大量請求爭搶購買同一商品並完成交易的過程。從架構視角來看,秒殺系統本質是一個高效能、高一致、高可用的三高系統。而打造並維護一個超大流量的秒殺系統需要進行哪些關注,就是本文討論的話題。

整體思考

1 秒殺存在的問題

對於一個日常平穩的業務系統,如果直接開通秒殺功能的話,往往會出現很多問題——

干係人 問題分類 業務出現的問題 設計要求
使用者 體驗較差 秒殺開始,系統瞬間承受平時數十倍甚至上百倍的流量,直接宕掉 高效能
使用者 體驗較差 使用者下單後卻付不了款,顯示商品已經被其他人買走了 一致性
商家 商品超賣 100 件商品,卻出現 200 人下單成功,成功下單買到商品的人數遠遠超過活動商品數量的上限 一致性
商家 資金受損 競爭對手透過惡意下單的方式將活動商品全部下單,導致庫存清零,商家無法正常售賣 高可用
商家 營銷失效 秒殺器猖獗,黃牛透過秒殺器掃貨,商家無法達到營銷目的 高可用
平臺 風險不可控 系統的其它與秒殺活動不相關的模組變得異常緩慢,業務影響面擴散 高可用
平臺 拖垮網站 線上人數創新高,核心鏈路涉及的上下游服務從前到後都在告警 高效能
平臺 單點故障 庫存只有一份,所有請求集中讀寫同一個資料,DB 出現單點 高效能

2 設計方向的思考

秒殺本質是要求一個瞬時高發下的承壓系統,這也是其區別於其他業務的核心場景。對日常系統秒殺產生的問題逐一進行拆解分類,秒殺對應到架構設計,其實就是高可用、一致性和高效能的要求。關於秒殺系統的設計思考,本文即基於此 3 層依次推進,簡述如下——

高效能

1 動靜分離

大家可能會注意到,秒殺過程中你是不需要重新整理整個頁面的,只有時間在不停跳動。這是因為一般都會對大流量的秒殺系統做系統的靜態化改造,即資料意義上的動靜分離。動靜分離三步走:1、資料拆分;2、靜態快取;3、資料整合。

1.1 資料拆分

動靜分離的首要目的是將動態頁面改造成適合快取的靜態頁面。因此第一步就是分離出動態資料,主要從以下 2 個方面進行:

  1. 使用者。使用者身份資訊包括登入狀態以及登入畫像等,相關要素可以單獨拆分出來,透過動態請求進行獲取;與之相關的廣平推薦,如使用者偏好、地域偏好等,同樣可以透過非同步方式進行載入
  2. 時間。秒殺時間是由服務端統一管控的,可以透過動態請求進行獲取
    這裡你可以開啟電商平臺的一個秒殺頁面,看看這個頁面裡都有哪些動靜資料。

1.2 靜態快取

分離出動靜態資料之後,第二步就是將靜態資料進行合理的快取,由此衍生出兩個問題:1、怎麼快取;2、哪裡快取

1.2.1 怎麼快取

靜態化改造的一個特點是直接快取整個 HTTP 連線而不是僅僅快取靜態資料,如此一來,Web 代理伺服器根據請求 URL,可以直接取出對應的響應體然後直接返回,響應過程無需重組 HTTP 協議,也無需解析 HTTP 請求頭。而作為快取鍵,URL唯一化是必不可少的,只是對於商品系統,URL 天然是可以基於商品 ID 來進行唯一標識的,比如淘寶的 https://item.taobao.com/item....。

1.2.2 哪裡快取

靜態資料快取到哪裡呢?可以有三種方式:1、瀏覽器;2、CDN ;3、服務端。

瀏覽器當然是第一選擇,但使用者的瀏覽器是不可控的,主要體現在如果使用者不主動重新整理,系統很難主動地把訊息推送給使用者(注意,當討論靜態資料時,潛臺詞是 “相對不變”,言外之意是 “可能會變”),如此可能會導致使用者端在很長一段時間內看到的資訊都是錯誤的。對於秒殺系統,保證快取可以在秒級時間內失效是不可或缺的。

服務端主要進行動態邏輯計算及載入,本身並不擅長處理大量連線,每個連線消耗記憶體較多,同時 Servlet 容器解析 HTTP 較慢,容易侵佔邏輯計算資源;另外,靜態資料下沉至此也會拉長請求路徑。

因此通常將靜態資料快取在 CDN,其本身更擅長處理大併發的靜態檔案請求,既可以做到主動失效,又離使用者儘可能近,同時規避 Java 語言層面的弱點。需要注意的是,上 CDN 有以下幾個問題需要解決:

  1. 失效問題。任何一個快取都應該是有時效的,尤其對於一個秒殺場景。所以,系統需要保證全國各地的 CDN 在秒級時間內失效掉快取資訊,這實際對 CDN 的失效系統要求是很高的

  2. 命中率問題。高命中是快取系統最為核心的效能要求,不然快取就失去了意義。如果將資料放到全國各地的 CDN ,勢必會導致請求命中同一個快取的可能性降低,那麼命中率就成為一個問題
    因此,將資料放到全國所有的 CDN 節點是不太現實的,失效問題、命中率問題都會面臨比較大的挑戰。更為可行的做法是選擇若干 CDN 節點進行靜態化改造,節點的選取通常需要滿足以下幾個條件:

  3. 臨近訪問量集中的地區

  4. 距離主站較遠的地區

  5. 節點與主站間網路質量良好的地區
    基於以上因素,選擇 CDN 的二級快取比較合適,因為二級快取數量偏少,容量也更大,訪問量相對集中,這樣就可以較好解決快取的失效問題以及命中率問題,是當前比較理想的一種 CDN 化方案。部署方式如下圖所示:

1.3 資料整合

分離出動靜態資料之後,前端如何組織資料頁就是一個新的問題,主要在於動態資料的載入處理,通常有兩種方案:ESI(Edge Side Includes)方案和 CSI(Client Side Include)方案。

  1. ESI 方案:Web 代理伺服器上請求動態資料,並將動態資料插入到靜態頁面中,使用者看到頁面時已經是一個完整的頁面。這種方式對服務端效能要求高,但使用者體驗較好
  2. CSI 方案:Web 代理伺服器上只返回靜態頁面,前端單獨發起一個非同步 JS 請求動態資料。這種方式對服務端效能友好,但使用者體驗稍差

1.4 小結

動靜分離對於效能的提升,抽象起來只有兩點,一是資料要儘量少,以便減少沒必要的請求,二是路徑要儘量短,以便提高單次請求的效率。具體方法其實就是基於這個大方向進行的。

2 熱點最佳化

熱點分為熱點操作和熱點資料,以下分開進行討論。

2.1 熱點操作

零點重新整理、零點下單、零點新增購物車等都屬於熱點操作。熱點操作是使用者的行為,不好改變,但可以做一些限制保護,比如使用者頻繁重新整理頁面時進行提示阻斷。

2.2 熱點資料

熱點資料的處理三步走,一是熱點識別,二是熱點隔離,三是熱點最佳化。

2.2.1 熱點識別

熱點資料分為靜態熱點和動態熱點,具體如下:

  1. 靜態熱點:能夠提前預測的熱點資料。大促前夕,可以根據大促的行業特點、活動商家等緯度資訊分析出熱點商品,或者透過賣家報名的方式提前篩選;另外,還可以透過技術手段提前預測,例如對買家每天訪問的商品進行大資料計算,然後統計出 TOP N 的商品,即可視為熱點商品

  2. 動態熱點:無法提前預測的熱點資料。冷熱資料往往是隨實際業務場景發生交替變化的,尤其是如今直播賣貨模式的興起——帶貨商臨時做一個廣告,就有可能導致一件商品在短時間內被大量購買。由於此類商品日常訪問較少,即使在快取系統中一段時間後也會被逐出或過期掉,甚至在db中也是冷資料。瞬時流量的湧入,往往導致快取被擊穿,請求直接到達DB,引發DB壓力過大
    因此秒殺系統需要實現熱點資料的動態發現能力,一個常見的實現思路是:

  3. 非同步採集交易鏈路各個環節的熱點 Key 資訊,如 Nginx採集訪問URL或 Agent 採集熱點日誌(一些中介軟體本身已具備熱點發現能力),提前識別潛在的熱點資料

  4. 聚合分析熱點資料,達到一定規則的熱點資料,透過訂閱分發推送到鏈路系統,各系統根據自身需求決定如何處理熱點資料,或限流或快取,從而實現熱點保護
    需要注意的是:

  5. 熱點資料採集最好採用非同步方式,一方面不會影響業務的核心交易鏈路,一方面可以保證採集方式的通用性

  6. 熱點發現最好做到秒級實時,這樣動態發現才有意義,實際上也是對核心節點的資料採集和分析能力提出了較高的要求

2.2.2 熱點隔離

熱點資料識別出來之後,第一原則就是將熱點資料隔離出來,不要讓 1% 影響到另外的 99%,可以基於以下幾個層次實現熱點隔離:

  1. 業務隔離。秒殺作為一種營銷活動,賣家需要單獨報名,從技術上來說,系統可以提前對已知熱點做快取預熱
  2. 系統隔離。系統隔離是執行時隔離,透過分組部署和另外 99% 進行分離,另外秒殺也可以申請單獨的域名,入口層就讓請求落到不同的叢集中
  3. 資料隔離。秒殺資料作為熱點資料,可以啟用單獨的快取叢集或者DB服務組,從而更好的實現橫向或縱向能力擴充套件
    當然,實現隔離還有很多種辦法。比如,可以按照使用者來區分,為不同的使用者分配不同的 Cookie,入口層路由到不同的服務介面中;再比如,域名保持一致,但後端呼叫不同的服務介面;又或者在資料層給資料打標進行區分等等,這些措施的目的都是把已經識別的熱點請求和普通請求區分開來。

2.2.3 熱點最佳化

熱點資料隔離之後,也就方便對這 1% 的請求做針對性的最佳化,方式無外乎兩種:

  1. 快取:熱點快取是最為有效的辦法。如果熱點資料做了動靜分離,那麼可以長期快取靜態資料
  2. 限流:流量限制更多是一種保護機制。需要注意的是,各服務要時刻關注請求是否觸發限流並及時進行review

2.2.4 小結

資料的熱點最佳化與動靜分離是不一樣的,熱點最佳化是基於二八原則對資料進行了縱向拆分,以便進行針對性地處理。熱點識別和隔離不僅對“秒殺”這個場景有意義,對其他的高效能分散式系統也非常有參考價值。

3 系統最佳化

對於一個軟體系統,提高效能可以有很多種手段,如提升硬體水平、調優JVM 效能,這裡主要關注程式碼層面的效能最佳化——

  1. 減少序列化:減少 Java 中的序列化操作可以很好的提升系統效能。序列化大部分是在 RPC 階段發生,因此應該儘量減少 RPC 呼叫,一種可行的方案是將多個關聯性較強的應用進行 “合併部署”,從而減少不同應用之間的 RPC 呼叫(微服務設計規範)
  2. 直接輸出流資料:只要涉及字串的I/O操作,無論是磁碟 I/O 還是網路 I/O,都比較耗費 CPU 資源,因為字元需要轉換成位元組,而這個轉換又必須查表編碼。所以對於常用資料,比如靜態字串,推薦提前編碼成位元組並快取,具體到程式碼層面就是透過 OutputStream() 類函式從而減少資料的編碼轉換;另外,熱點方法toString()不要直接呼叫ReflectionToString實現,推薦直接硬編碼,並且只列印DO的基礎要素和核心要素
  3. 裁剪日誌異常堆疊:無論是外部系統異常還是應用本身異常,都會有堆疊打出,超大流量下,頻繁的輸出完整堆疊,只會加劇系統當前負載。可以透過日誌配置檔案控制異常堆疊輸出的深度
  4. 去元件框架:極致最佳化要求下,可以去掉一些元件框架,比如去掉傳統的 MVC 框架,直接使用 Servlet 處理請求。這樣可以繞過一大堆複雜且用處不大的處理邏輯,節省毫秒級的時間,當然,需要合理評估你對框架的依賴程度

4 總結一下

效能最佳化需要一個基準值,所以系統還需要做好應用基線,比如效能基線(何時效能突然下降)、成本基線(去年大促用了多少機器)、鏈路基線(核心流程發生了哪些變化),透過基線持續關注系統效能,促使系統在程式碼層面持續提升編碼質量、業務層面及時下掉不合理呼叫、架構層面不斷最佳化改進。

一致性

秒殺系統中,庫存是個關鍵資料,賣不出去是個問題,超賣更是個問題。秒殺場景下的一致性問題,主要就是庫存扣減的準確性問題。

1 減庫存的方式

電商場景下的購買過程一般分為兩步:下單和付款。“提交訂單”即為下單,“支付訂單”即為付款。基於此設定,減庫存一般有以下幾個方式:

  1. 下單減庫存。買家下單後,扣減商品庫存。下單減庫存是最簡單的減庫存方式,也是控制最為精確的一種
  2. 付款減庫存。買家下單後,並不立即扣減庫存,而是等到付款後才真正扣減庫存。但因為付款時才減庫存,如果併發比較高,可能出現買家下單後付不了款的情況,因為商品已經被其他人買走了
  3. 預扣庫存。這種方式相對複雜一些,買家下單後,庫存為其保留一定的時間(如 15 分鐘),超過這段時間,庫存自動釋放,釋放後其他買家可以購買
    能夠看到,減庫存方式是基於購物過程的多階段進行劃分的,但無論是在下單階段還是付款階段,都會存在一些問題,下面進行具體分析。

2 減庫存的問題

2.1 下單減庫存

優勢:使用者體驗最好。下單減庫存是最簡單的減庫存方式,也是控制最精確的一種。下單時可以直接透過資料庫事務機制控制商品庫存,所以一定不會出現已下單卻付不了款的情況。

劣勢:可能賣不出去。正常情況下,買家下單後付款機率很高,所以不會有太大問題。但有一種場景例外,就是當賣家參加某個促銷活動時,競爭對手透過惡意下單的方式將該商品全部下單,導致庫存清零,那麼這就不能正常售賣了——要知道,惡意下單的人是不會真正付款的,這正是 “下單減庫存” 的不足之處。

2.2 付款減庫存

優勢:一定實際售賣。“下單減庫存” 可能導致惡意下單,從而影響賣家的商品銷售, “付款減庫存” 由於需要付出真金白銀,可以有效避免。

劣勢:使用者體驗較差。使用者下單後,不一定會實際付款,假設有 100 件商品,就可能出現 200 人下單成功的情況,因為下單時不會減庫存,所以也就可能出現下單成功數遠遠超過真正庫存數的情況,這尤其會發生在大促的熱門商品上。如此一來就會導致很多買家下單成功後卻付不了款,購物體驗自然是比較差的。

2.3 預扣庫存

優勢:緩解了以上兩種方式的問題。預扣庫存實際就是“下單減庫存”和 “付款減庫存”兩種方式的結合,將兩次操作進行了前後關聯,下單時預扣庫存,付款時釋放庫存。

劣勢:並沒有徹底解決以上問題。比如針對惡意下單的場景,雖然可以把有效付款時間設定為 10 分鐘,但惡意買家完全可以在 10 分鐘之後再次下單。

2.4 小結

減庫存的問題主要體現在使用者體驗和商業訴求兩方面,其本質原因在於購物過程存在兩步甚至多步操作,在不同階段減庫存,容易存在被惡意利用的漏洞。

3 實際如何減庫存

業界最為常見的是預扣庫存。無論是外賣點餐還是電商購物,下單後一般都有個 “有效付款時間”,超過該時間訂單自動釋放,這就是典型的預扣庫存方案。但如上所述,預扣庫存還需要解決惡意下單的問題,保證商品賣的出去;另一方面,如何避免超賣,也是一個痛點。

  1. 賣的出去:惡意下單的解決方案主要還是結合安全和反作弊措施來制止。比如,識別頻繁下單不付款的買家並進行打標,這樣可以在打標買家下單時不減庫存;再比如為大促商品設定單人最大購買件數,一人最多隻能買 N 件商品;又或者對重複下單不付款的行為進行次數限制阻斷等
  2. 避免超賣:庫存超賣的情況實際分為兩種。對於普通商品,秒殺只是一種大促手段,即使庫存超賣,商家也可以透過補貨來解決;而對於一些商品,秒殺作為一種營銷手段,完全不允許庫存為負,也就是在資料一致性上,需要保證大併發請求時資料庫中的庫存欄位值不能為負,一般有多種方案:一是在透過事務來判斷,即保證減後庫存不能為負,否則就回滾;二是直接設定資料庫欄位型別為無符號整數,這樣一旦庫存為負就會在執行 SQL 時報錯;三是使用 CASE WHEN 判斷語句——
UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END

業務手段保證商品賣的出去,技術手段保證商品不會超賣,庫存問題從來就不是簡單的技術難題,解決問題的視角是多種多樣的。

4 一致性效能的最佳化

庫存是個關鍵資料,更是個熱點資料。對系統來說,熱點的實際影響就是 “高讀” 和 “高寫”,也是秒殺場景下最為核心的一個技術難題。

4.1 高併發讀

秒殺場景解決高併發讀問題,關鍵詞是“分層校驗”。即在讀鏈路時,只進行不影響效能的檢查操作,如使用者是否具有秒殺資格、商品狀態是否正常、使用者答題是否正確、秒殺是否已經結束、是否非法請求等,而不做一致性校驗等容易引發瓶頸的檢查操作;直到寫鏈路時,才對庫存做一致性檢查,在資料層保證最終準確性。

因此,在分層校驗設定下,系統可以採用分散式快取甚至LocalCache來抵抗高併發讀。即允許讀場景下一定的髒資料,這樣只會導致少量原本無庫存的下單請求被誤認為是有庫存的,等到真正寫資料時再保證最終一致性,由此做到高可用和一致性之間的平衡。

實際上,分層校驗的核心思想是:不同層次儘可能過濾掉無效請求,只在“漏斗” 最末端進行有效處理,從而縮短系統瓶頸的影響路徑。

4.2 高併發寫

高併發寫的最佳化方式,一種是更換DB選型,一種是最佳化DB效能,以下分別進行討論。

4.2.1 更換DB選型

秒殺商品和普通商品的減庫存是有差異的,核心區別在資料量級小、交易時間短,因此能否把秒殺減庫存直接放到快取系統中實現呢,也就是直接在一個帶有持久化功能的快取中進行減庫存操作,比如 Redis?

如果減庫存邏輯非常單一的話,比如沒有複雜的 SKU 庫存和總庫存這種聯動關係的話,個人認為是完全可以的。但如果有比較複雜的減庫存邏輯,或者需要使用到事務,那就必須在資料庫中完成減庫存操作。

4.2.2 最佳化DB效能

庫存資料落地到資料庫實現其實是一行儲存(MySQL),因此會有大量執行緒來競爭 InnoDB 行鎖。但併發越高,等待執行緒就會越多,TPS 下降,RT 上升,吞吐量會受到嚴重影響——注意,這裡假設資料庫已基於上文【效能最佳化】完成資料隔離,以便於討論聚焦 。

解決併發鎖的問題,有兩種辦法:

  1. 應用層排隊。透過快取加入叢集分散式鎖,從而控制叢集對資料庫同一行記錄進行操作的併發度,同時也能控制單個商品佔用資料庫連線的數量,防止熱點商品佔用過多的資料庫連線
  2. 資料層排隊。應用層排隊是有損效能的,資料層排隊是最為理想的。業界中,阿里的資料庫團隊開發了針對InnoDB 層上的補丁程式(patch),可以基於DB層對單行記錄做併發排隊,從而實現秒殺場景下的定製最佳化——注意,排隊和鎖競爭是有區別的,如果熟悉 MySQL 的話,就會知道 InnoDB 內部的死鎖檢測,以及 MySQL Server 和 InnoDB 的切換都是比較消耗效能的。另外阿里的資料庫團隊還做了很多其他方面的最佳化,如 COMMIT_ON_SUCCESS 和 ROLLBACK_ON_FAIL 的補丁程式,透過在 SQL 里加入提示(hint),實現事務不需要等待實時提交,而是在資料執行完最後一條 SQL 後,直接根據 TARGET_AFFECT_ROW 的結果進行提交或回滾,減少網路等待的時間(毫秒級)。目前阿里已將包含這些補丁程式的 MySQL 開源:AliSQL

4.3 小結

高讀和高寫的兩種處理方式大相徑庭。讀請求的最佳化空間要大一些,而寫請求的瓶頸一般都在儲存層,最佳化思路的本質還是基於 CAP 理論做平衡。

5 總結一下

當然,減庫存還有很多細節問題,例如預扣的庫存超時後如何進行回補,再比如第三方支付如何保證減庫存和付款時的狀態一致性,這些也是很大的挑戰。

高可用

盯過秒殺流量監控的話,會發現它不是一條蜿蜒而起的曲線,而是一條挺拔的直線,這是因為秒殺請求高度集中於某一特定的時間點。這樣一來就會造成一個特別高的零點峰值,而對資源的消耗也幾乎是瞬時的。所以秒殺系統的可用性保護是不可或缺的。

1 流量削峰

對於秒殺的目標場景,最終能夠搶到商品的人數是固定的,無論 100 人和 10000 人參加結果都是一樣的,即有效請求額度是有限的。併發度越高,無效請求也就越多。但秒殺作為一種商業營銷手段,活動開始之前是希望有更多的人來刷頁面,只是真正開始後,秒殺請求不是越多越好。因此係統可以設計一些規則,人為的延緩秒殺請求,甚至可以過濾掉一些無效請求。

1.1 答題

早期秒殺只是簡單的點選秒殺按鈕,後來才增加了答題。為什麼要增加答題呢?主要是透過提升購買的複雜度,達到兩個目的:

  1. 防止作弊。早期秒殺器比較猖獗,存在惡意買家或競爭對手使用秒殺器掃貨的情況,商家沒有達到營銷的目的,所以增加答題來進行限制
  2. 延緩請求。零點流量的起效時間是毫秒級的,答題可以人為拉長峰值下單的時長,由之前的 <1s 延長到 <10s。這個時間對於服務端非常重要,會大大減輕高峰期併發壓力;另外,由於請求具有先後順序,答題後置的請求到來時可能已經沒有庫存了,因此根本無法下單,此階段落到資料層真正的寫也就非常有限了
    需要注意的是,答題除了做正確性驗證,還需要對提交時間做驗證,比如<1s 人為操作的可能性就很小,可以進一步防止機器答題的情況。

答題目前已經使用的非常普遍了,本質是透過在入口層削減流量,從而讓系統更好地支撐瞬時峰值。

1.2 排隊

最為常見的削峰方案是使用訊息佇列,透過把同步的直接呼叫轉換成非同步的間接推送緩衝瞬時流量。除了訊息佇列,類似的排隊方案還有很多,例如:

  1. 執行緒池加鎖等待

  2. 本地記憶體蓄洪等待

  3. 本地檔案序列化寫,再順序讀
    排隊方式的弊端也是顯而易見的,主要有兩點:

  4. 請求積壓。流量高峰如果長時間持續,達到了佇列的水位上限,佇列同樣會被壓垮,這樣雖然保護了下游系統,但是和請求直接丟棄也沒多大區別

  5. 使用者體驗。非同步推送的實時性和有序性自然是比不上同步呼叫的,由此可能出現請求先發後至的情況,影響部分敏感使用者的購物體驗
    排隊本質是在業務層將一步操作轉變成兩步操作,從而起到緩衝的作用,但鑑於此種方式的弊端,最終還是要基於業務量級和秒殺場景做出妥協和平衡。

1.3 過濾

過濾的核心結構在於分層,透過在不同層次過濾掉無效請求,達到資料讀寫的精準觸發。常見的過濾主要有以下幾層:

1、讀限流:對讀請求做限流保護,將超出系統承載能力的請求過濾掉
2、讀快取:對讀請求做資料快取,將重複的請求過濾掉
3、寫限流:對寫請求做限流保護,將超出系統承載能力的請求過濾掉
4、寫校驗:對寫請求做一致性校驗,只保留最終的有效資料

過濾的核心目的是透過減少無效請求的資料IO保障有效請求的IO效能。

1.4 小結

系統可以透過入口層的答題、業務層的排隊、資料層的過濾達到流量削峰的目的,本質是在尋求商業訴求與架構效能之間的平衡。另外,新的削峰手段也層出不窮,以業務切入居多,比如零點大促時同步發放優惠券或發起抽獎活動,將一部分流量分散到其他系統,這樣也能起到削峰的作用。

2 Plan B

當一個系統面臨持續的高峰流量時,其實是很難單靠自身調整來恢復狀態的,日常運維沒有人能夠預估所有情況,意外總是無法避免。尤其在秒殺這一場景下,為了保證系統的高可用,必須設計一個 Plan B 方案來進行兜底。

高可用建設,其實是一個系統工程,貫穿在系統建設的整個生命週期。

具體來說,系統的高可用建設涉及架構階段、編碼階段、測試階段、釋出階段、執行階段,以及故障發生時,逐一進行分析:

  1. 架構階段:考慮系統的可擴充套件性和容錯性,避免出現單點問題。例如多地單元化部署,即使某個IDC甚至地市出現故障,仍不會影響系統運轉

  2. 編碼階段:保證程式碼的健壯性,例如RPC呼叫時,設定合理的超時退出機制,防止被其他系統拖垮,同時也要對無法預料的返回錯誤進行預設的處理

  3. 測試階段:保證CI的覆蓋度以及Sonar的容錯率,對基礎質量進行二次校驗,並定期產出整體質量的趨勢報告

  4. 釋出階段:系統部署最容易暴露錯誤,因此要有前置的checklist模版、中置的上下游周知機制以及後置的回滾機制

  5. 執行階段:系統多數時間處於執行態,最重要的是執行時的實時監控,及時發現問題、準確報警並能提供詳細資料,以便排查問題

  6. 故障發生:首要目標是及時止損,防止影響面擴大,然後定位原因、解決問題,最後恢復服務
    對於日常運維而言,高可用更多是針對執行階段而言的,此階段需要額外進行加強建設,主要有以下幾種手段:

  7. 預防:建立常態壓測體系,定期對服務進行單點壓測以及全鏈路壓測,摸排水位

  8. 管控:做好線上執行的降級、限流和熔斷保護。需要注意的是,無論是限流、降級還是熔斷,對業務都是有損的,所以在進行操作前,一定要和上下游業務確認好再進行。就拿限流來說,哪些業務可以限、什麼情況下限、限流時間多長、什麼情況下進行恢復,都要和業務方反覆確認

  9. 監控:建立效能基線,記錄效能的變化趨勢;建立報警體系,發現問題及時預警

  10. 恢復:遇到故障能夠及時止損,並提供快速的資料訂正工具,不一定要好,但一定要有
    在系統建設的整個生命週期中,每個環節中都可能犯錯,甚至有些環節犯的錯,後面是無法彌補的或者成本極高的。所以高可用是一個系統工程,必須放到整個生命週期中進行全面考慮。同時,考慮到服務的增長性,高可用更需要長期規劃並進行體系化建設。

3 總結一下

高可用其實是在說 “穩定性”,穩定性是一個平時不重要,但出了問題就要命的事情,然而它的落地又是一個問題——平時業務發展良好,穩定性建設就會降級給業務讓路。解決這個問題必須在組織上有所保障,比如讓業務負責人背上穩定性績效指標,同時在部門中建立穩定性建設小組,小組成員由每條線的核心力量兼任,績效由穩定性負責人來打分,這樣就可以把體系化的建設任務落實到具體的業務系統中了。

個人總結
一個秒殺系統的設計,可以根據不同級別的流量,由簡單到複雜打造出不同的架構,本質是各方面的取捨和權衡。當然,你可能注意到,本文並沒有涉及具體的選型方案,因為這些對於架構來說並不重要,作為架構師,應該時刻提醒自己主線是什麼。

同時也在這裡抽象、提煉一下,主要是個人對於秒殺設計的提綱式整理,方便各位同學進行參考——!

轉載自: https://segmentfault.com/a/1190000020970562

相關文章