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

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

紅包系統面臨的挑戰

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

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

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

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

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

系統設計

系統架構圖如圖 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 的公有云做爲服務基礎環境。對於第三方的服務,缺乏實踐經驗的把握,因而從開發到運維過程當中,咱們都保持了一種防護式的思考方式,包括數據庫、緩存節點故障,以及應用服務環境的崩潰、網絡抖動,咱們都認爲隨時可能出問題,都須要對應的自動替換降級策略,嚴重時甚至可手動觸發配置開關修改策略。固然,若是組件自身具備降級功能,能夠給上層業務節約不少成本資源,要本身實現所有環節的降級能力的確是一件比較耗費資源的事情,這也是一個公司技術慢慢積累的過程。

結束語

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

此次春節紅包活動,在資源有限的狀況下成功抵抗超乎日常的流量峯值壓力,對於技術而言是一次很大的挑戰,也是一件快樂的事情,讓咱們從中積累了不少實踐經驗。將來咱們將不斷努力,但願可以將部分轉化成較爲通用的技術,沉澱爲基礎架構組件,去更好的推進業務成功。真誠但願本文的分享可以對你們的技術工做有所幫助。

參考資源

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

相關文章
相關標籤/搜索