前言
秒殺大家都不陌生。自2011年首次出現以來,無論是雙十一購物還是 12306 搶票,秒殺場景已隨處可見。簡單來說,秒殺就是在同一時刻大量請求爭搶購買同一商品並完成交易的過程。從架構視角來看,秒殺系統本質是一個高效能、高一致、高可用的三高系統。而打造並維護一個超大流量的秒殺系統需要進行哪些關注,就是本文討論的話題。
整體思考
1 秒殺存在的問題
對於一個日常平穩的業務系統,如果直接開通秒殺功能的話,往往會出現很多問題——
干係人 | 問題分類 | 業務出現的問題 | 設計要求 |
---|---|---|---|
使用者 | 體驗較差 | 秒殺開始,系統瞬間承受平時數十倍甚至上百倍的流量,直接宕掉 | 高效能 |
使用者 | 體驗較差 | 使用者下單後卻付不了款,顯示商品已經被其他人買走了 | 一致性 |
商家 | 商品超賣 | 100 件商品,卻出現 200 人下單成功,成功下單買到商品的人數遠遠超過活動商品數量的上限 | 一致性 |
商家 | 資金受損 | 競爭對手透過惡意下單的方式將活動商品全部下單,導致庫存清零,商家無法正常售賣 | 高可用 |
商家 | 營銷失效 | 秒殺器猖獗,黃牛透過秒殺器掃貨,商家無法達到營銷目的 | 高可用 |
平臺 | 風險不可控 | 系統的其它與秒殺活動不相關的模組變得異常緩慢,業務影響面擴散 | 高可用 |
平臺 | 拖垮網站 | 線上人數創新高,核心鏈路涉及的上下游服務從前到後都在告警 | 高效能 |
平臺 | 單點故障 | 庫存只有一份,所有請求集中讀寫同一個資料,DB 出現單點 | 高效能 |
2 設計方向的思考
秒殺本質是要求一個瞬時高發下的承壓系統,這也是其區別於其他業務的核心場景。對日常系統秒殺產生的問題逐一進行拆解分類,秒殺對應到架構設計,其實就是高可用、一致性和高效能的要求。關於秒殺系統的設計思考,本文即基於此 3 層依次推進,簡述如下——
高效能
1 動靜分離
大家可能會注意到,秒殺過程中你是不需要重新整理整個頁面的,只有時間在不停跳動。這是因為一般都會對大流量的秒殺系統做系統的靜態化改造,即資料意義上的動靜分離。動靜分離三步走:1、資料拆分;2、靜態快取;3、資料整合。
1.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 有以下幾個問題需要解決:
-
失效問題。任何一個快取都應該是有時效的,尤其對於一個秒殺場景。所以,系統需要保證全國各地的 CDN 在秒級時間內失效掉快取資訊,這實際對 CDN 的失效系統要求是很高的
-
命中率問題。高命中是快取系統最為核心的效能要求,不然快取就失去了意義。如果將資料放到全國各地的 CDN ,勢必會導致請求命中同一個快取的可能性降低,那麼命中率就成為一個問題
因此,將資料放到全國所有的 CDN 節點是不太現實的,失效問題、命中率問題都會面臨比較大的挑戰。更為可行的做法是選擇若干 CDN 節點進行靜態化改造,節點的選取通常需要滿足以下幾個條件: -
臨近訪問量集中的地區
-
距離主站較遠的地區
-
節點與主站間網路質量良好的地區
基於以上因素,選擇 CDN 的二級快取比較合適,因為二級快取數量偏少,容量也更大,訪問量相對集中,這樣就可以較好解決快取的失效問題以及命中率問題,是當前比較理想的一種 CDN 化方案。部署方式如下圖所示:
1.3 資料整合
分離出動靜態資料之後,前端如何組織資料頁就是一個新的問題,主要在於動態資料的載入處理,通常有兩種方案:ESI(Edge Side Includes)方案和 CSI(Client Side Include)方案。
- ESI 方案:Web 代理伺服器上請求動態資料,並將動態資料插入到靜態頁面中,使用者看到頁面時已經是一個完整的頁面。這種方式對服務端效能要求高,但使用者體驗較好
- CSI 方案:Web 代理伺服器上只返回靜態頁面,前端單獨發起一個非同步 JS 請求動態資料。這種方式對服務端效能友好,但使用者體驗稍差
1.4 小結
動靜分離對於效能的提升,抽象起來只有兩點,一是資料要儘量少,以便減少沒必要的請求,二是路徑要儘量短,以便提高單次請求的效率。具體方法其實就是基於這個大方向進行的。
2 熱點最佳化
熱點分為熱點操作和熱點資料,以下分開進行討論。
2.1 熱點操作
零點重新整理、零點下單、零點新增購物車等都屬於熱點操作。熱點操作是使用者的行為,不好改變,但可以做一些限制保護,比如使用者頻繁重新整理頁面時進行提示阻斷。
2.2 熱點資料
熱點資料的處理三步走,一是熱點識別,二是熱點隔離,三是熱點最佳化。
2.2.1 熱點識別
熱點資料分為靜態熱點和動態熱點,具體如下:
-
靜態熱點:能夠提前預測的熱點資料。大促前夕,可以根據大促的行業特點、活動商家等緯度資訊分析出熱點商品,或者透過賣家報名的方式提前篩選;另外,還可以透過技術手段提前預測,例如對買家每天訪問的商品進行大資料計算,然後統計出 TOP N 的商品,即可視為熱點商品
-
動態熱點:無法提前預測的熱點資料。冷熱資料往往是隨實際業務場景發生交替變化的,尤其是如今直播賣貨模式的興起——帶貨商臨時做一個廣告,就有可能導致一件商品在短時間內被大量購買。由於此類商品日常訪問較少,即使在快取系統中一段時間後也會被逐出或過期掉,甚至在db中也是冷資料。瞬時流量的湧入,往往導致快取被擊穿,請求直接到達DB,引發DB壓力過大
因此秒殺系統需要實現熱點資料的動態發現能力,一個常見的實現思路是: -
非同步採集交易鏈路各個環節的熱點 Key 資訊,如 Nginx採集訪問URL或 Agent 採集熱點日誌(一些中介軟體本身已具備熱點發現能力),提前識別潛在的熱點資料
-
聚合分析熱點資料,達到一定規則的熱點資料,透過訂閱分發推送到鏈路系統,各系統根據自身需求決定如何處理熱點資料,或限流或快取,從而實現熱點保護
需要注意的是: -
熱點資料採集最好採用非同步方式,一方面不會影響業務的核心交易鏈路,一方面可以保證採集方式的通用性
-
熱點發現最好做到秒級實時,這樣動態發現才有意義,實際上也是對核心節點的資料採集和分析能力提出了較高的要求
2.2.2 熱點隔離
熱點資料識別出來之後,第一原則就是將熱點資料隔離出來,不要讓 1% 影響到另外的 99%,可以基於以下幾個層次實現熱點隔離:
- 業務隔離。秒殺作為一種營銷活動,賣家需要單獨報名,從技術上來說,系統可以提前對已知熱點做快取預熱
- 系統隔離。系統隔離是執行時隔離,透過分組部署和另外 99% 進行分離,另外秒殺也可以申請單獨的域名,入口層就讓請求落到不同的叢集中
- 資料隔離。秒殺資料作為熱點資料,可以啟用單獨的快取叢集或者DB服務組,從而更好的實現橫向或縱向能力擴充套件
當然,實現隔離還有很多種辦法。比如,可以按照使用者來區分,為不同的使用者分配不同的 Cookie,入口層路由到不同的服務介面中;再比如,域名保持一致,但後端呼叫不同的服務介面;又或者在資料層給資料打標進行區分等等,這些措施的目的都是把已經識別的熱點請求和普通請求區分開來。
2.2.3 熱點最佳化
熱點資料隔離之後,也就方便對這 1% 的請求做針對性的最佳化,方式無外乎兩種:
- 快取:熱點快取是最為有效的辦法。如果熱點資料做了動靜分離,那麼可以長期快取靜態資料
- 限流:流量限制更多是一種保護機制。需要注意的是,各服務要時刻關注請求是否觸發限流並及時進行review
2.2.4 小結
資料的熱點最佳化與動靜分離是不一樣的,熱點最佳化是基於二八原則對資料進行了縱向拆分,以便進行針對性地處理。熱點識別和隔離不僅對“秒殺”這個場景有意義,對其他的高效能分散式系統也非常有參考價值。
3 系統最佳化
對於一個軟體系統,提高效能可以有很多種手段,如提升硬體水平、調優JVM 效能,這裡主要關注程式碼層面的效能最佳化——
- 減少序列化:減少 Java 中的序列化操作可以很好的提升系統效能。序列化大部分是在 RPC 階段發生,因此應該儘量減少 RPC 呼叫,一種可行的方案是將多個關聯性較強的應用進行 “合併部署”,從而減少不同應用之間的 RPC 呼叫(微服務設計規範)
- 直接輸出流資料:只要涉及字串的I/O操作,無論是磁碟 I/O 還是網路 I/O,都比較耗費 CPU 資源,因為字元需要轉換成位元組,而這個轉換又必須查表編碼。所以對於常用資料,比如靜態字串,推薦提前編碼成位元組並快取,具體到程式碼層面就是透過 OutputStream() 類函式從而減少資料的編碼轉換;另外,熱點方法toString()不要直接呼叫ReflectionToString實現,推薦直接硬編碼,並且只列印DO的基礎要素和核心要素
- 裁剪日誌異常堆疊:無論是外部系統異常還是應用本身異常,都會有堆疊打出,超大流量下,頻繁的輸出完整堆疊,只會加劇系統當前負載。可以透過日誌配置檔案控制異常堆疊輸出的深度
- 去元件框架:極致最佳化要求下,可以去掉一些元件框架,比如去掉傳統的 MVC 框架,直接使用 Servlet 處理請求。這樣可以繞過一大堆複雜且用處不大的處理邏輯,節省毫秒級的時間,當然,需要合理評估你對框架的依賴程度
4 總結一下
效能最佳化需要一個基準值,所以系統還需要做好應用基線,比如效能基線(何時效能突然下降)、成本基線(去年大促用了多少機器)、鏈路基線(核心流程發生了哪些變化),透過基線持續關注系統效能,促使系統在程式碼層面持續提升編碼質量、業務層面及時下掉不合理呼叫、架構層面不斷最佳化改進。
一致性
秒殺系統中,庫存是個關鍵資料,賣不出去是個問題,超賣更是個問題。秒殺場景下的一致性問題,主要就是庫存扣減的準確性問題。
1 減庫存的方式
電商場景下的購買過程一般分為兩步:下單和付款。“提交訂單”即為下單,“支付訂單”即為付款。基於此設定,減庫存一般有以下幾個方式:
- 下單減庫存。買家下單後,扣減商品庫存。下單減庫存是最簡單的減庫存方式,也是控制最為精確的一種
- 付款減庫存。買家下單後,並不立即扣減庫存,而是等到付款後才真正扣減庫存。但因為付款時才減庫存,如果併發比較高,可能出現買家下單後付不了款的情況,因為商品已經被其他人買走了
- 預扣庫存。這種方式相對複雜一些,買家下單後,庫存為其保留一定的時間(如 15 分鐘),超過這段時間,庫存自動釋放,釋放後其他買家可以購買
能夠看到,減庫存方式是基於購物過程的多階段進行劃分的,但無論是在下單階段還是付款階段,都會存在一些問題,下面進行具體分析。
2 減庫存的問題
2.1 下單減庫存
優勢:使用者體驗最好。下單減庫存是最簡單的減庫存方式,也是控制最精確的一種。下單時可以直接透過資料庫事務機制控制商品庫存,所以一定不會出現已下單卻付不了款的情況。
劣勢:可能賣不出去。正常情況下,買家下單後付款機率很高,所以不會有太大問題。但有一種場景例外,就是當賣家參加某個促銷活動時,競爭對手透過惡意下單的方式將該商品全部下單,導致庫存清零,那麼這就不能正常售賣了——要知道,惡意下單的人是不會真正付款的,這正是 “下單減庫存” 的不足之處。
2.2 付款減庫存
優勢:一定實際售賣。“下單減庫存” 可能導致惡意下單,從而影響賣家的商品銷售, “付款減庫存” 由於需要付出真金白銀,可以有效避免。
劣勢:使用者體驗較差。使用者下單後,不一定會實際付款,假設有 100 件商品,就可能出現 200 人下單成功的情況,因為下單時不會減庫存,所以也就可能出現下單成功數遠遠超過真正庫存數的情況,這尤其會發生在大促的熱門商品上。如此一來就會導致很多買家下單成功後卻付不了款,購物體驗自然是比較差的。
2.3 預扣庫存
優勢:緩解了以上兩種方式的問題。預扣庫存實際就是“下單減庫存”和 “付款減庫存”兩種方式的結合,將兩次操作進行了前後關聯,下單時預扣庫存,付款時釋放庫存。
劣勢:並沒有徹底解決以上問題。比如針對惡意下單的場景,雖然可以把有效付款時間設定為 10 分鐘,但惡意買家完全可以在 10 分鐘之後再次下單。
2.4 小結
減庫存的問題主要體現在使用者體驗和商業訴求兩方面,其本質原因在於購物過程存在兩步甚至多步操作,在不同階段減庫存,容易存在被惡意利用的漏洞。
3 實際如何減庫存
業界最為常見的是預扣庫存。無論是外賣點餐還是電商購物,下單後一般都有個 “有效付款時間”,超過該時間訂單自動釋放,這就是典型的預扣庫存方案。但如上所述,預扣庫存還需要解決惡意下單的問題,保證商品賣的出去;另一方面,如何避免超賣,也是一個痛點。
- 賣的出去:惡意下單的解決方案主要還是結合安全和反作弊措施來制止。比如,識別頻繁下單不付款的買家並進行打標,這樣可以在打標買家下單時不減庫存;再比如為大促商品設定單人最大購買件數,一人最多隻能買 N 件商品;又或者對重複下單不付款的行為進行次數限制阻斷等
- 避免超賣:庫存超賣的情況實際分為兩種。對於普通商品,秒殺只是一種大促手段,即使庫存超賣,商家也可以透過補貨來解決;而對於一些商品,秒殺作為一種營銷手段,完全不允許庫存為負,也就是在資料一致性上,需要保證大併發請求時資料庫中的庫存欄位值不能為負,一般有多種方案:一是在透過事務來判斷,即保證減後庫存不能為負,否則就回滾;二是直接設定資料庫欄位型別為無符號整數,這樣一旦庫存為負就會在執行 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 上升,吞吐量會受到嚴重影響——注意,這裡假設資料庫已基於上文【效能最佳化】完成資料隔離,以便於討論聚焦 。
解決併發鎖的問題,有兩種辦法:
- 應用層排隊。透過快取加入叢集分散式鎖,從而控制叢集對資料庫同一行記錄進行操作的併發度,同時也能控制單個商品佔用資料庫連線的數量,防止熱點商品佔用過多的資料庫連線
- 資料層排隊。應用層排隊是有損效能的,資料層排隊是最為理想的。業界中,阿里的資料庫團隊開發了針對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 答題
早期秒殺只是簡單的點選秒殺按鈕,後來才增加了答題。為什麼要增加答題呢?主要是透過提升購買的複雜度,達到兩個目的:
- 防止作弊。早期秒殺器比較猖獗,存在惡意買家或競爭對手使用秒殺器掃貨的情況,商家沒有達到營銷的目的,所以增加答題來進行限制
- 延緩請求。零點流量的起效時間是毫秒級的,答題可以人為拉長峰值下單的時長,由之前的 <1s 延長到 <10s。這個時間對於服務端非常重要,會大大減輕高峰期併發壓力;另外,由於請求具有先後順序,答題後置的請求到來時可能已經沒有庫存了,因此根本無法下單,此階段落到資料層真正的寫也就非常有限了
需要注意的是,答題除了做正確性驗證,還需要對提交時間做驗證,比如<1s 人為操作的可能性就很小,可以進一步防止機器答題的情況。
答題目前已經使用的非常普遍了,本質是透過在入口層削減流量,從而讓系統更好地支撐瞬時峰值。
1.2 排隊
最為常見的削峰方案是使用訊息佇列,透過把同步的直接呼叫轉換成非同步的間接推送緩衝瞬時流量。除了訊息佇列,類似的排隊方案還有很多,例如:
-
執行緒池加鎖等待
-
本地記憶體蓄洪等待
-
本地檔案序列化寫,再順序讀
排隊方式的弊端也是顯而易見的,主要有兩點: -
請求積壓。流量高峰如果長時間持續,達到了佇列的水位上限,佇列同樣會被壓垮,這樣雖然保護了下游系統,但是和請求直接丟棄也沒多大區別
-
使用者體驗。非同步推送的實時性和有序性自然是比不上同步呼叫的,由此可能出現請求先發後至的情況,影響部分敏感使用者的購物體驗
排隊本質是在業務層將一步操作轉變成兩步操作,從而起到緩衝的作用,但鑑於此種方式的弊端,最終還是要基於業務量級和秒殺場景做出妥協和平衡。
1.3 過濾
過濾的核心結構在於分層,透過在不同層次過濾掉無效請求,達到資料讀寫的精準觸發。常見的過濾主要有以下幾層:
1、讀限流:對讀請求做限流保護,將超出系統承載能力的請求過濾掉
2、讀快取:對讀請求做資料快取,將重複的請求過濾掉
3、寫限流:對寫請求做限流保護,將超出系統承載能力的請求過濾掉
4、寫校驗:對寫請求做一致性校驗,只保留最終的有效資料
過濾的核心目的是透過減少無效請求的資料IO保障有效請求的IO效能。
1.4 小結
系統可以透過入口層的答題、業務層的排隊、資料層的過濾達到流量削峰的目的,本質是在尋求商業訴求與架構效能之間的平衡。另外,新的削峰手段也層出不窮,以業務切入居多,比如零點大促時同步發放優惠券或發起抽獎活動,將一部分流量分散到其他系統,這樣也能起到削峰的作用。
2 Plan B
當一個系統面臨持續的高峰流量時,其實是很難單靠自身調整來恢復狀態的,日常運維沒有人能夠預估所有情況,意外總是無法避免。尤其在秒殺這一場景下,為了保證系統的高可用,必須設計一個 Plan B 方案來進行兜底。
高可用建設,其實是一個系統工程,貫穿在系統建設的整個生命週期。
具體來說,系統的高可用建設涉及架構階段、編碼階段、測試階段、釋出階段、執行階段,以及故障發生時,逐一進行分析:
-
架構階段:考慮系統的可擴充套件性和容錯性,避免出現單點問題。例如多地單元化部署,即使某個IDC甚至地市出現故障,仍不會影響系統運轉
-
編碼階段:保證程式碼的健壯性,例如RPC呼叫時,設定合理的超時退出機制,防止被其他系統拖垮,同時也要對無法預料的返回錯誤進行預設的處理
-
測試階段:保證CI的覆蓋度以及Sonar的容錯率,對基礎質量進行二次校驗,並定期產出整體質量的趨勢報告
-
釋出階段:系統部署最容易暴露錯誤,因此要有前置的checklist模版、中置的上下游周知機制以及後置的回滾機制
-
執行階段:系統多數時間處於執行態,最重要的是執行時的實時監控,及時發現問題、準確報警並能提供詳細資料,以便排查問題
-
故障發生:首要目標是及時止損,防止影響面擴大,然後定位原因、解決問題,最後恢復服務
對於日常運維而言,高可用更多是針對執行階段而言的,此階段需要額外進行加強建設,主要有以下幾種手段: -
預防:建立常態壓測體系,定期對服務進行單點壓測以及全鏈路壓測,摸排水位
-
管控:做好線上執行的降級、限流和熔斷保護。需要注意的是,無論是限流、降級還是熔斷,對業務都是有損的,所以在進行操作前,一定要和上下游業務確認好再進行。就拿限流來說,哪些業務可以限、什麼情況下限、限流時間多長、什麼情況下進行恢復,都要和業務方反覆確認
-
監控:建立效能基線,記錄效能的變化趨勢;建立報警體系,發現問題及時預警
-
恢復:遇到故障能夠及時止損,並提供快速的資料訂正工具,不一定要好,但一定要有
在系統建設的整個生命週期中,每個環節中都可能犯錯,甚至有些環節犯的錯,後面是無法彌補的或者成本極高的。所以高可用是一個系統工程,必須放到整個生命週期中進行全面考慮。同時,考慮到服務的增長性,高可用更需要長期規劃並進行體系化建設。
3 總結一下
高可用其實是在說 “穩定性”,穩定性是一個平時不重要,但出了問題就要命的事情,然而它的落地又是一個問題——平時業務發展良好,穩定性建設就會降級給業務讓路。解決這個問題必須在組織上有所保障,比如讓業務負責人背上穩定性績效指標,同時在部門中建立穩定性建設小組,小組成員由每條線的核心力量兼任,績效由穩定性負責人來打分,這樣就可以把體系化的建設任務落實到具體的業務系統中了。
個人總結
一個秒殺系統的設計,可以根據不同級別的流量,由簡單到複雜打造出不同的架構,本質是各方面的取捨和權衡。當然,你可能注意到,本文並沒有涉及具體的選型方案,因為這些對於架構來說並不重要,作為架構師,應該時刻提醒自己主線是什麼。
同時也在這裡抽象、提煉一下,主要是個人對於秒殺設計的提綱式整理,方便各位同學進行參考——!
轉載自: https://segmentfault.com/a/1190000020970562