如何設計一個小而美的秒殺系統?[轉]

現如今,春節搶紅包的活動已經逐漸變成大家過年的新風俗。親朋好友的相互饋贈,微信、微博、支付寶等各大平臺種類繁多的紅包讓大家收到手軟。雞年春節,公司的老總們也想給 15 萬的全國員工發福利,於是我們構建了一套旨在支撐 10 萬每秒請求峰值的搶紅包系統。經實踐證明,春節期間我們成功的為所有的小夥伴提供了高可靠的服務,紅包總髮放量近百萬,搶紅包的峰值流量達到 3 萬/秒,最快的一輪搶紅包活動 3 秒鐘所有紅包全部搶完,系統運行零故障。



紅包系統面臨的挑戰

紅包系統,類似於電商平臺的秒殺系統,本質上都是在一個很短的時間內面對巨大的請求流量,將有限的庫存商品分發出去,並完成交易操作。比如 12306 搶票,庫存的火車票是有限的,但瞬時的流量非常大,且都是在請求相同的資源,這裡面數據庫的併發讀寫衝突以及資源的鎖請求衝突非常嚴重。現在,我們將分析實現這樣一個紅包系統,需要面臨如下的一些挑戰:

首先,到活動整點時刻,我們有 15 萬員工同時湧入系統搶某輪紅包,瞬間的流量是很大的,而目前我們整個鏈路上的系統和服務基礎設施,都沒有承受過如此高的吞吐量,要在短時間內實現業務需求,在技術上的風險較大。

其次,公司是第一次開展這樣的活動,我們很難預知大家參與活動的情況,極端情況下可能會出現某輪紅包沒搶完,需要合併到下輪接著發放。這就要求系統有一個動態的紅包發放策略和預算控制,其中涉及到的動態計算會是個較大的問題(這也是為系統高吞吐服務),實際的系統實現中我們採用了一些預處理機制。

最後,這個系統是為了春節的慶祝活動而研發的定製系統,且只上線運行一次,這意味著我們無法積累經驗去對服務做持續的優化。並且相關的配套環境沒有經過實際運行檢驗,缺少參考指標,系統的薄弱環節發現的難度大。所以必須要追求設計至簡,儘量減少對環境的依賴(數據路徑越長,出問題的環節越多),並且實現高可伸縮性,需要盡一切努力保證可靠性,即使有某環節失誤,系統依然能夠保障核心的用戶體驗正常。

能負責有技術挑戰的項目,對於工程師來說總是壓力和興趣並存的。接手項目後一個月的時間內我們完成了技術調研,原型設計研發,線上運維等工作。關於這個過程中的細節,下面為讀者一一道來。

系統設計

系統架構圖如圖 1 所示。整個系統的主幹採用主流的 Web 後臺設計結構,子系統各自部署為集群模式並且獨立。APP 客戶端與網關接入層(負責用戶鑑權、流量負載均衡、整合數據緩存等)進行交互,再往後是核心的邏輯系統(用戶資格校驗、紅包分發、數據異步持久化、異步財務到賬、降級等),數據持久化採用的 MySQL 集群。除此之外還有靜態資源的管理(紅包頁面圖片、視頻等資源的訪問優化)以及配套的服務整體運行監控。所有的靜態資源提前部署在了第三方的 CDN 服務上。為了保障整體系統可靠性,我們做了包括數據預處理、水平分庫、多級緩存、精簡 RPC 調用、過載保護等多項設計優化,並且在原生容器、MySQL 等服務基礎設施上針對特殊的業務場景做了優化。

圖 1. 系統架構
如何設計一個小而美的秒殺系統?[轉]

紅包本身的信息通過預處理資源接口獲取。運行中用戶和紅包的映射關係動態生成。底層使用內部開發的 DB 中間件在 MySQL 數據庫集群上做紅包發放結果持久化,以供異步支付紅包金額到用戶賬戶使用。整個系統的絕大部分模塊都有性能和保活監控。

優化方案

優化方案中最重要的目標是保障關鍵流程在應對大量請求時能穩定運行,做到這一點,需要很高的系統可用性。因此,業務流程和數據流程要儘量精簡,減少容易出錯的環節。此外,cache、DB、網絡、容器環境,任何一個部分都有可能會出現短時故障,我們需要提前做處理預案。針對以上的目標難點,我們總結了如下的實踐經驗。

數據預處理

我們結合活動預案要求,將紅包本身的屬性信息(金額,狀態,祝福語,發放策略),使用一定的算法提前生成好所有的信息,這些數據所佔空間不是很大。為了最大化提升性能,我們事先將這些紅包數據,我們事先存儲在數據庫中,然後在容器加載服務啟動時,直接加載到本地緩存中當作只讀數據。另外,我們將員工信息也做了一定的裁剪,最基本的信息也和紅包數據一樣,預先生成,服務啟動時加載。

此外,我們的活動頁面,有很多視頻和圖片資源,如果這麼多的用戶從網關實時訪問,帶寬很可能直接就被這些大流量的請求佔滿了,用戶體驗可想而知。最後這些靜態資源,我們都部署在了 CDN 上,通過數據預熱的方式加速客戶端的訪問速度,網關的流量主要是來自於搶紅包期間的小數據請求。

精簡 RPC 調用

服務請求流程通常是在接入層訪問用戶中心進行用戶鑑權,然後轉發請求到後端服務,後端服務根據業務邏輯調用其他上游服務,並且查詢數據庫資源,再更新服務/數據庫的數據。每一次 RPC 調用都會有額外的開銷,所以,比如上面一點所說的預加載,使得每個節點在系統運行期間都有全量的查詢數據可在本地訪問。搶紅包的核心流程就被簡化為了生成紅包和人的映射關係,以及發放紅包的後續操作。再比如,我們採用了異步拉的方式進行紅包發放到賬,用戶搶紅包的請求不再經過發放這一步,只記錄關係,性能得到進一步提升。 如圖 2 所示。

圖 2. 服務依賴精簡示意圖
如何設計一個小而美的秒殺系統?[轉]

實際上有些做法的可伸縮性是極強的。例如紅包數據的預生成信息,在當時的場景下是能夠作為本地內存緩存加速訪問的。當紅包數據量很大的時候,在每個服務節點上使用本地數據庫、本地數據文件,甚至是本地 Redis/MC 緩存服務,都是可以保證空間足夠的,並且還有額外的好處,越少的 RPC,服務抖動越少,我們只需要關注系統本身的健壯性即可,不需要考慮外部系統 QoS。

搶紅包的併發請求處理

春節整點時刻,同一個紅包會被成千上萬的人同時請求,如何控制併發請求,確保紅包會且僅會被一個用戶搶到?

  • 做法一:使用加鎖操作先佔有鎖資源,再佔有紅包。

可以使用分佈式全局鎖的方式(各種分佈式鎖組件或者數據庫鎖),先申請 lock 該紅包資源且成功後再做後續操作。優點是不會出現髒數據問題,某一個時刻只有一個應用線程持有 lock,紅包只會被至多一個用戶搶到,數據一致性有保障。缺點是,所有請求同一時刻都在搶紅包 A,下一個時刻又都在搶紅包 B,並且只有一個搶成功,其他都失敗,效率很低。

  • 做法二:單獨開發請求排隊調度模塊。

排隊模塊接收用戶的搶紅包請求,以 FIFO 模式保存下來,調度模塊負責 FIFO 隊列的動態調度,一旦有空閒資源,便從隊列頭部把用戶的訪問請求取出後交給真正提供服務的模塊處理。優點是,具有中心節點的統一資源管理,對系統的可控性強,可深度定製。缺點是,所有請求流量都會有中心節點參與,效率必然會比分佈式無中心繫統低,並且,中心節點也很容易成為整個系統的性能瓶頸。

  • 做法三:巧用 Redis 特性,使其成為分佈式序號生成器(我們最終採用的做法)。

前文已經提到,紅包系統所使用的紅包數據都是預先生成好的,我們使用數字 ID 來標識,這個 ID 是全局唯一的,所有圍繞紅包的操作都使用這個 ID 作為數據的關聯項。在實際的請求流量過來時,我們採用了"分組"處理流量的方式,如下圖 3 所示。

訪問請求被負載均衡器分發到每個 Service Cluster 的分組 Bucket,一個分組 Bucket 包含若干臺應用容器、獨立的數據庫和 Redis 節點。Redis 節點內存儲的是這個分組可以分發的紅包 ID 號段,利用 Redis 特性實現紅包分發,各服務節點通過 Redis 原語獲取當前 拆到的紅包。這種做法的思路是,Redis 本身是單進程工作模型,來自分佈式系統各個節點的操作請求天然的被 Redis Server 做了一個同步隊列,只要每個請求執行的足夠快,這個隊列就不會引起阻塞及請求超時。而本例中我們使用了 DECR 原語,性能上是可以滿足需求的。Redis 在這裡相當於是充當一個分佈式序號發生器的功能,分發紅包 ID。

此外,落地數據都持久化在獨立的數據庫中,相當於是做了水平分庫。某個分組內處理的請求,只會訪問分組內部的 Redis 和數據庫,和其他分組隔離開。

整個處理流程核心的思想是,分組的方式使得整個系統實現了高內聚,低耦合的原則,能將數據流量分而治之,提升了系統的可伸縮性,當面臨更大流量的需求時,通過線性擴容的方法,即可應對。並且當單個節點出現故障時,影響面能夠控制在單個分組內部,系統也就具有了較好的隔離性。

圖 3. 系統部署邏輯視圖
如何設計一個小而美的秒殺系統?[轉]

系統容量評估,藉助數據優化,過載保護

由於是首次開展活動,我們缺乏實際的運營數據,一切都是摸著石頭過河。所以從項目伊始,我們便強調對系統各個層次的預估,既包括了活動參與人數、每個 APP 界面上的功能點潛在的高峰流量值、後端請求的峰值、緩存系統請求峰值和數據庫讀寫請求峰值等,還包括了整個業務流程和服務基礎設施中潛在的薄弱環節。後者的難度更大因為很難量化。此前我們連超大流量的全鏈路性能壓測工具都較缺乏,所以還是有很多實踐的困難的。

在這裡內心真誠的感謝開源社區的力量,在我們制定完系統的性能指標參考值後,藉助如 wrk 等優秀的開源工具,我們在有限的資源裡實現了對整個系統的端到端全鏈路壓測。實測中,我們的核心接口在單個容器上可以達到 20,000 以上的 QPS,整個服務集群在 110,000 以上的 QPS 壓力下依然能穩定工作。

正是一次次的全鏈路壓測參考指標,幫助我們瞭解了性能的基準,並以此做了代碼設計層面、容器層面、JVM 層面、MySQL 數據庫層面、緩存集群層面的種種優化,極大的提升了系統的可用性。具體做法限於篇幅不在此贅述,有興趣的讀者歡迎交流。

此外,為了確保線上有超預估流量時系統穩定,我們做了過載保護。超過性能上限閾值的流量,系統會快速返回特定的頁面結果,將此部分流量清理掉,保障已經接受的有效流量可以正常處理。

完善監控

系統在線上運行過程中,我們需要對運行情況實時獲取信息,以便能夠對出現的問題進行排查定位,及時採取措施。所以我們必須有一套有效的監控系統,能夠幫我們觀測到關鍵的指標。在實際的操作層面,我們主要關注瞭如下指標:

  • 服務接口的性能指標

藉助系統的請求日誌,觀測服務接口的 QPS,接口的實時響應總時間。同時通過 HTTP 的狀態碼觀測服務的語義層面的可用性。

  • 系統健康度

結合總的性能指標以及各個模塊應用層的性能日誌,包括模塊接口返回耗時,和應用層日誌的邏輯錯誤日誌等,判斷系統的健康度。

  • 整體的網絡狀況

儘量觀測每個點到點之間的網絡狀態,包括應用服務器的網卡流量、Redis 節點、數據庫節點的流量,以及入口帶寬的佔用情況。如果某條線路出現過高流量,便可及時採取擴容等措施緩解。

  • 服務基礎設施

應用服務器的 CPU、Memory、磁盤 IO 狀況,緩存節點和數據庫的相應的數據,以及他們的連接數、連接時間、資源消耗檢測數據,及時的去發現資源不足的預警信息。

對於關鍵的數據指標,在超過預估時制定的閾值時,還需要監控系統能夠實時的通過手機和郵件實時通知的方式讓相關人員知道。另外,我們在系統中還做了若干邏輯開關,當某些資源出現問題並且自動降級和過載保護模塊失去效果時,我們可以根據狀況直接人工介入,在服務不停機的前提下,手動觸發邏輯開關改變系統邏輯,達到快速響應故障,讓服務儘快恢復穩定的目的。

服務降級

當服務器壓力劇增的時候,如果某些依賴的服務設施或者基礎組件超出了工作負荷能力,發生了故障,這時候極其需要根據當前的業務運行情況對系統服務進行有策略的降級運行措施,使得核心的業務流程能夠順利進行,並且減輕服務器資源的壓力,最好在壓力減小後還能自動恢復升級到原工作機制。

我們在開發紅包系統時,考慮到原有 IDC 機房的解決方案對於彈性擴容和流量帶寬支持不太完美,選擇了使用 AWS 的公有云作為服務基礎環境。對於第三方的服務,缺少實踐經驗的把握,於是從開發到運維過程中,我們都保持了一種防禦式的思考方式,包括數據庫、緩存節點故障,以及應用服務環境的崩潰、網絡抖動,我們都認為隨時可能出問題,都需要對應的自動替換降級策略,嚴重時甚至可手動觸發配置開關修改策略。當然,如果組件自身具有降級功能,可以給上層業務節約很多成本資源,要自己實現全部環節的降級能力的確是一件比較耗費資源的事情,這也是一個公司技術慢慢積累的過程。

結束語

以上就是我們整個系統研發運維的一些經驗分享。對於這類瞬時大流量的秒殺系統而言,高可用是最大的優化目標,分而治之是核心的架構思想;防禦式思維,假定任何環節都可能有弱點,能夠提升系統的穩定性。另外,一定要加強監控,及時發現問題,解決問題。做好了如上幾點,相信各位讀者在應對類似問題時,也能更加全面的思考,少走一些彎路,做出更加優秀的系統。

這次春節紅包活動,在資源有限的情況下成功抵抗超乎平常的流量峰值壓力,對於技術而言是一次很大的挑戰,也是一件快樂的事情,讓我們從中積累了很多實踐經驗。未來我們將不斷努力,希望能夠將部分轉化成較為通用的技術,沉澱為基礎架構組件,去更好的推動業務成功。真誠希望本文的分享能夠對大家的技術工作有所幫助。



參考資源

  • WRK,一種強悍的大流量請求壓測工具。
  • 一號店秒殺系統的參考做法。
  • 電商系統可用的秒殺系統做法。
  • JVM 優化建議。
  • Ganglia,開源集群監視框架。

原文地址:https://www.ibm.com/developerworks/cn/web/wa-design-small-and-good-kill-system/index.html


分享到:


相關文章: