本文來自:http://mp.weixin.qq.com/s/npdAbY3Pp6TMWz4wCZk2gA,作者:劉鵬
現(xiàn)如今,春節(jié)搶紅包的活動已經(jīng)逐漸變成大家過年的新風俗。親朋好友的相互饋贈,微信、微博、支付寶等各大平臺種類繁多的紅包讓大家收到手軟。雞年春節(jié),鏈家也想給15萬的全國員工包個大紅包,于是我們構建了一套旨在支撐10萬每秒請求峰值的搶紅包系統(tǒng)。經(jīng)實踐證明,春節(jié)期間我們成功的為所有的小伙伴提供了高可靠的服務,紅包總發(fā)放量近百萬,搶紅包的峰值流量達到3萬/秒,最快的一輪搶紅包活動3秒鐘所有紅包全部搶完,系統(tǒng)運行0故障。
紅包系統(tǒng),類似于電商平臺的秒殺系統(tǒng),本質上都是在一個很短的時間內(nèi)面對巨大的請求流量,將有限的庫存商品分發(fā)出去,并完成交易操作。比如12306搶票,庫存的火車票是有限的,但瞬時的流量非常大,且都是在請求相同的資源,這里面數(shù)據(jù)庫的并發(fā)讀寫沖突以及資源的鎖請求沖突非常嚴重。就我們實現(xiàn)這樣一個紅包系統(tǒng)本身來說,面臨著如下的一些挑戰(zhàn):
首先,到活動整點時刻,我們有15萬員工在固定時間點同時涌入系統(tǒng)搶某輪紅包,瞬間的流量是很大的,而目前我們整個鏈路上的系統(tǒng)和服務基礎設施,都沒有承受過如此高的吞吐量,要在短時間內(nèi)實現(xiàn)業(yè)務需求,在技術上的風險較大。
其次,公司是第一次開展這樣的活動,我們很難預知大家參與活動的情況,極端情況下可能會出現(xiàn)某輪紅包沒搶完,需要合并到下輪接著發(fā)放。這就要求系統(tǒng)有一個動態(tài)的紅包發(fā)放策略和預算控制,其中涉及到的動態(tài)計算會是個較大的問題(這也是為系統(tǒng)高吞吐服務),實際的系統(tǒng)實現(xiàn)中我們采用了一些預處理機制。
最后,這個系統(tǒng)是為了春節(jié)的慶祝活動而研發(fā)的定制系統(tǒng),且只上線運行一次,這意味著我們無法積累經(jīng)驗去對服務做持續(xù)的優(yōu)化。并且相關的配套環(huán)境沒有經(jīng)過實際運行檢驗,缺少參考指標,系統(tǒng)的薄弱環(huán)節(jié)發(fā)現(xiàn)的難度大。所以必須要追求設計至簡,盡量減少對環(huán)境的依賴(數(shù)據(jù)路徑越長,出問題的環(huán)節(jié)越多),并且實現(xiàn)高可伸縮性,需要盡一切努力保證可靠性,即使有某環(huán)節(jié)失誤,系統(tǒng)依然能夠保障核心的用戶體驗正常。
系統(tǒng)設計
系統(tǒng)架構圖如圖所示。所有的靜態(tài)資源提前部署在了第三方的CDN服務上,系統(tǒng)的核心功能主要劃分到接入層和核心邏輯系統(tǒng)中,各自部署為集群模式并且獨立。接入層主要是對用戶身份鑒權和結果緩存,核心系統(tǒng)重點關注紅包的分發(fā),紅色實線的模塊是核心邏輯,為了保障其可靠性,我們做了包括數(shù)據(jù)預處理、水平分庫、多級緩存、精簡RPC調(diào)用、過載保護等多項設計優(yōu)化,并且在原生容器、MySQL等服務基礎設施上針對特殊的業(yè)務場景做了優(yōu)化,后面將為讀者一一道來。
紅包本身的信息通過預處理資源接口獲取。運行中用戶和紅包的映射關系動態(tài)生成。底層使用內(nèi)部開發(fā)的DB中間件在MySQL數(shù)據(jù)庫集群上做紅包發(fā)放結果持久化,以供異步支付紅包金額到用戶賬戶使用。整個系統(tǒng)的絕大部分模塊都有性能和保活監(jiān)控。
優(yōu)化方案
優(yōu)化方案中最重要的目標是保障關鍵流程在應對大量請求時穩(wěn)定運行,這需要很高的系統(tǒng)可用性。所以,業(yè)務流程和數(shù)據(jù)流程要盡量精簡,減少容易出錯的環(huán)節(jié)。此外,緩存、DB、網(wǎng)絡、容器環(huán)境,任何一個部分都要假設可能會短時出現(xiàn)故障,要有處理預案。針對以上的目標難點,我們總結了如下的實踐經(jīng)驗。
紅包本身的屬性信息(金額,狀態(tài),祝福語,發(fā)放策略),我們結合活動預案要求,使用一定的算法提前生成好所有的信息,數(shù)據(jù)總的空間不是很大。為了最大化提升性能,這些紅包數(shù)據(jù),我們事先存儲在數(shù)據(jù)庫中,然后在容器加載服務啟動時,直接加載到本地緩存中當作只讀數(shù)據(jù)。另外,我們的員工信息,我們也做了一定的裁剪,最基本的信息也和紅包數(shù)據(jù)一樣,預先生成,服務啟動時加載。
此外,我們的活動頁面,有很多視頻和圖片資源,如果這么多的用戶從我們的網(wǎng)關實時訪問,很可能我們的帶寬直接就被這些大流量的請求占滿了,用戶體驗可想而知。最后這些靜態(tài)資源,我們都部署在了CDN上,通過數(shù)據(jù)預熱的方式加速客戶端的訪問速度,網(wǎng)關的流量主要是來自于搶紅包期間的小數(shù)據(jù)請求。
通常的服務請求流程,是在接入層訪問用戶中心進行用戶鑒權,然后轉發(fā)請求到后端服務,后端服務根據(jù)業(yè)務邏輯調(diào)用其他上游服務,并且查詢數(shù)據(jù)庫資源,再更新服務/數(shù)據(jù)庫的數(shù)據(jù)。每一次RPC調(diào)用都會有額外的開銷,所以,比如上一點所說的預加載,使得系統(tǒng)在運行期間每個節(jié)點都有全量的查詢數(shù)據(jù)可在本地訪問,搶紅包的核心流程就被簡化為了生成紅包和人的映射關系,以及發(fā)放紅包的后續(xù)操作。再比如,我們采用了異步拉的方式進行紅包發(fā)放到賬,用戶搶紅包的請求不再經(jīng)過發(fā)放這一步,只記錄關系,性能得到進一步提升。
實際上有些做法的可伸縮性是極強的。例如紅包數(shù)據(jù)的預生成信息,在當時的場景下我們是能夠作為本地內(nèi)存緩存加速訪問的。當紅包數(shù)據(jù)量很大的時候,在每個服務節(jié)點上使用本地數(shù)據(jù)庫,或者本地數(shù)據(jù)文件,甚至是本地Redis/MC緩存服務,都是可以保證空間足夠的,并且還有額外的好處,越少的RPC,越少的服務抖動,只需要關注系統(tǒng)本身的健壯性即可,不需要考慮外部系統(tǒng)QoS。
春節(jié)整點時刻,同一個紅包會被成千上萬的人同時請求,如何控制并發(fā)請求,確保紅包會且僅會被一個用戶搶到?
做法一,使用加鎖操作先占有鎖資源,再占有紅包。
可以使用分布式全局鎖的方式(各種分布式鎖組件或者數(shù)據(jù)庫鎖),申請lock該紅包資源成功后再做后續(xù)操作。優(yōu)點是,不會出現(xiàn)臟數(shù)據(jù)問題,某一個時刻只有一個應用線程持有l(wèi)ock,紅包只會被至多一個用戶搶到,數(shù)據(jù)一致性有保障。缺點是,所有請求同一時刻都在搶紅包A,下一個時刻又都在搶紅包B,并且只有一個搶成功,其他都失敗,效率很低。
做法二,單獨開發(fā)請求排隊調(diào)度模塊。
排隊模塊接收用戶的搶紅包請求,以FIFO模式保存下來,調(diào)度模塊負責FIFO隊列的動態(tài)調(diào)度,一旦有空閑資源,便從隊列頭部把用戶的訪問請求取出后交給真正提供服務的模塊處理。優(yōu)點是,具有中心節(jié)點的統(tǒng)一資源管理,對系統(tǒng)的可控性強,可深度定制。缺點是,所有請求流量都會有中心節(jié)點參與,效率必然會比分布式無中心系統(tǒng)低,并且,中心節(jié)點也很容易成為整個系統(tǒng)的性能瓶頸。
做法三,巧用Redis特性,使其成為分布式序號生成器。(我們最終采用的做法)。
前文已經(jīng)提到,紅包系統(tǒng)所使用的紅包數(shù)據(jù)都是預先生成好的,我們使用數(shù)字ID來標識,這個ID是全局唯一的,所有圍繞紅包的操作都使用這個ID作為數(shù)據(jù)的關聯(lián)項。在實際的請求流量過來時,我們采用了“分組”處理流量的方式,如下圖所示。
訪問請求被LB分發(fā)到每個分組,一個分組包含若干臺應用容器、獨立的數(shù)據(jù)庫和Redis節(jié)點。Redis節(jié)點內(nèi)存儲的是這個分組可以分發(fā)的紅包ID號段,利用Redis單進程的自減數(shù)值特性實現(xiàn)分布式紅包ID生成器,服務通過此獲取當前拆到的紅包。落地數(shù)據(jù)都持久化在獨立的數(shù)據(jù)庫中,相當于是做了水平分庫。某個分組內(nèi)處理的請求,只會訪問分組內(nèi)部的Redis和數(shù)據(jù)庫,和其他分組隔離開。
分組的方式使得整個系統(tǒng)實現(xiàn)了高內(nèi)聚,低耦合的原則,能將數(shù)據(jù)流量分而治之,提升了系統(tǒng)的可伸縮性,當面臨更大流量的需求時,通過線性擴容的方法,即可應對。并且當單個節(jié)點出現(xiàn)故障時,影響面能夠控制在單個分組內(nèi)部,系統(tǒng)也就具有了較好的隔離性。
由于是首次開展活動,我們?nèi)狈嶋H的運營數(shù)據(jù),一切都是摸著石頭過河。所以從項目伊始,我們便強調(diào)對系統(tǒng)各個層次的預估,既包括了活動參與人數(shù)、每個功能feature用戶的高峰流量、后端請求的峰值、緩存系統(tǒng)請求峰值和數(shù)據(jù)庫讀寫請求峰值等,還包括了整個業(yè)務流程和服務基礎設施中潛在的薄弱環(huán)節(jié)。后者的難度更大因為很難量化。此前我們連超大流量的全鏈路性能壓測工具都較缺乏,所以還是有很多實踐的困難的。
在這里內(nèi)心真誠的感謝開源社區(qū)的力量,在我們制定完系統(tǒng)的性能指標參考值后,借助如wrk等優(yōu)秀的開源工具,我們在有限的資源里實現(xiàn)了對整個系統(tǒng)的端到端全鏈路壓測。實測中,我們的核心接口在單個容器上可以達到20,000以上的QPS,整個服務集群在110,000以上的QPS壓力下依然能穩(wěn)定工作。
正是一次次的全鏈路壓測參考指標,幫助我們了解了性能的基準,并以此做了代碼設計層面、容器層面、JVM層面、MySQL數(shù)據(jù)庫層面、緩存集群層面的種種優(yōu)化,極大的提升了系統(tǒng)的可用性。具體做法限于篇幅不在此贅述,有興趣的讀者歡迎交流。
此外,為了確保線上有超預估流量時系統(tǒng)穩(wěn)定,我們做了過載保護。超過性能上限閾值的流量,系統(tǒng)會快速返回特定的頁面結果,將此部分流量清理掉,保障已經(jīng)接受的有效流量可以正常處理。
系統(tǒng)在線上運行過程中,我們很需要對其實時的運行情況獲取信息,以便能夠對出現(xiàn)的問題進行排查定位,及時采取措施。所以我們必須有一套有效的監(jiān)控系統(tǒng),能夠幫我們觀測到關鍵的指標。在實際的操作層面,我們主要關注了如下指標:
服務接口的性能指標
借助系統(tǒng)的請求日志,觀測服務接口的QPS,接口總的實時響應時間。同時通過HTTP的狀態(tài)碼觀測服務的語義層面的可用性。
系統(tǒng)健康度
結合總的性能指標以及各個模塊應用層的性能日志,包括模塊接口返回耗時,和應用層日志的邏輯錯誤日志等,判斷系統(tǒng)的健康度。
整體的網(wǎng)絡狀況
盡量觀測每個點到點之間的網(wǎng)絡狀態(tài),包括應用服務器的網(wǎng)卡流量、Redis節(jié)點、數(shù)據(jù)庫節(jié)點的流量,以及入口帶寬的占用情況。如果某條線路出現(xiàn)過高流量,便可及時采取擴容等措施緩解。
服務基礎設施
應用服務器的CPU、Memory、磁盤IO狀況,緩存節(jié)點和數(shù)據(jù)庫的相應的數(shù)據(jù),以及他們的連接數(shù)、連接時間、資源消耗檢測數(shù)據(jù),及時的去發(fā)現(xiàn)資源不足的預警信息。
對于關鍵的數(shù)據(jù)指標,在超過預估時制定的閾值時,還需要監(jiān)控系統(tǒng)能夠實時的通過手機和郵件實時通知的方式讓相關人員知道。另外,我們在系統(tǒng)中還做了若干邏輯開關,當某些資源出現(xiàn)問題并且自動降級和過載保護模塊失去效果時,我們可以根據(jù)狀況直接人工介入,在服務不停機的前提前通過手動觸發(fā)邏輯開關改變系統(tǒng)邏輯,達到快速響應故障,讓服務盡快恢復穩(wěn)定的目的。
當服務器壓力劇增的時候,如果某些依賴的服務設施或者基礎組件超出了工作負荷能力,發(fā)生了故障,這時候極其需要根據(jù)當前的業(yè)務運行情況對系統(tǒng)服務進行有策略的降級運行措施,使得核心的業(yè)務流程能夠順利進行,并且減輕服務器資源的壓力,最好在壓力減小后還能自動恢復升級到原工作機制。
我們在開發(fā)紅包系統(tǒng)時,考慮到原有IDC機房的解決方案對于彈性擴容和流量帶寬支持不太完美,選擇了使用AWS的公有云作為服務基礎環(huán)境。對于第三方的服務,缺少實踐經(jīng)驗的把握,于是從開發(fā)到運維過程中,我們都保持了一種防御式的思考方式,包括數(shù)據(jù)庫、緩存節(jié)點故障,以及應用服務環(huán)境的崩潰、網(wǎng)絡抖動,我們都認為隨時可能出問題,都需要對應的自動替換降級策略,嚴重時甚至可通過手動觸發(fā)配置開關修改策略。當然,如果組件自身具有降級功能,可以給上層業(yè)務節(jié)約很多成本資源,要自己實現(xiàn)全部環(huán)節(jié)的降級能力的確是一件比較耗費資源的事情,這也是一個公司技術慢慢積累的過程。
結束語
以上是我們整個系統(tǒng)研發(fā)運維的一些體會。這次春節(jié)紅包活動,在資源有限的情況下成功抵抗超乎平常的流量峰值壓力,對于技術而言是一次很大的挑戰(zhàn),也是一件快樂的事情,讓我們從中積累了很多實踐經(jīng)驗。未來我們將不斷努力,希望能夠將部分轉化成較為通用的技術,去更好的推動業(yè)務成功。真誠希望本文的分享能夠對大家的技術工作有所幫助。