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

JesLockett 7年前發布 | 13K 次閱讀 數據庫 軟件架構

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

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

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

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

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

系統設計

系統架構圖如圖所示。所有的靜態資源提前部署在了第三方的CDN服務上,系統的核心功能主要劃分到接入層和核心邏輯系統中,各自部署為集群模式并且獨立。接入層主要是對用戶身份鑒權和結果緩存,核心系統重點關注紅包的分發,紅色實線的模塊是核心邏輯,為了保障其可靠性,我們做了包括數據預處理、水平分庫、多級緩存、精簡RPC調用、過載保護等多項設計優化,并且在原生容器、MySQL等服務基礎設施上針對特殊的業務場景做了優化,后面將為讀者一一道來。

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

優化方案

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

1.數據預處理

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

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

2.精簡RPC調用

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

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

3.搶紅包的并發請求處理

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

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

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

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

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

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

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

訪問請求被LB分發到每個分組,一個分組包含若干臺應用容器、獨立的數據庫和Redis節點。Redis節點內存儲的是這個分組可以分發的紅包ID號段,利用Redis單進程的自減數值特性實現分布式紅包ID生成器,服務通過此獲取當前拆到的紅包。落地數據都持久化在獨立的數據庫中,相當于是做了水平分庫。某個分組內處理的請求,只會訪問分組內部的Redis和數據庫,和其他分組隔離開。

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

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

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

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

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

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

5.完善監控

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

服務接口的性能指標

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

系統健康度

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

整體的網絡狀況

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

服務基礎設施

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

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

6.服務降級

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

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

結束語

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

感謝郭蕾對本文的審校。

給InfoQ中文站投稿或者參與內容翻譯工作,請郵件至editors@cn.infoq.com。也歡迎大家通過新浪微博(@InfoQ,@丁曉昀),微信(微信號: InfoQChina )關注我們。

 

來自:http://www.infoq.com/cn/articles/how-to-design-a-small-and-beautiful-spike-system

 

 本文由用戶 JesLockett 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!