2017年1月28日,正月初一,微信公佈了用戶在除夕當天收發微信紅包的數量——142億個,而其收發峯值也已達到76萬每秒。百億級別的紅包,如何保障併發性能與資金安全?這給微信帶來了超級挑戰。面對挑戰,微信紅包在分析了業界「秒殺」系統解決方案的基礎上,採用了SET化、請求排隊串行化、雙維度分庫表等設計,造成了獨特的高併發、資金安全系統解決方案。實踐證實,該方案表現穩定,且實現了除夕夜系統零故障運行。數據庫
本文將爲讀者介紹百億級別紅包背後的系統高併發設計方案,包括微信紅包的兩大業務特色、微信紅包系統的技術難點、解決高併發問題一般使用的方案,以及微信紅包系統的高併發解決方案。緩存
微信紅包(尤爲是發在微信羣裏的紅包,即羣紅包)業務形態上很相似網上的普通商品「秒殺」活動。安全
用戶在微信羣裏發一個紅包,等同因而普通商品「秒殺」活動的商品上架;微信羣裏的全部用戶搶紅包的動做,等同於「秒殺」活動中的查詢庫存;用戶搶到紅包後拆紅包的動做,則對應「秒殺」活動中用戶的「秒殺」動做。服務器
不過除了上面的相同點以外,微信紅包在業務形態上與普通商品「秒殺」活動相比,還具有自身的特色:微信
首先,微信紅包業務比普通商品「秒殺」有更海量的併發要求。架構
微信紅包用戶在微信羣裏發一個紅包,等同於在網上發佈一次商品「秒殺」活動。假設同一時間有10萬個羣裏的用戶同時在發紅包,那就至關於同一時間有10萬個「秒殺」活動發佈出去。10萬個微信羣裏的用戶同時搶紅包,將產生海量的併發請求。併發
其次,微信紅包業務要求更嚴格的安全級別。異步
微信紅包業務本質上是資金交易。微信紅包是微信支付的一個商戶,提供資金流轉服務。分佈式
用戶發紅包時,至關於在微信紅包這個商戶上使用微信支付購買一筆「錢」,而且收貨地址是微信羣。當用戶支付成功後,紅包「發貨」到微信羣裏,羣裏的用戶拆開紅包後,微信紅包提供了將「錢」轉入折紅包用戶微信零錢的服務。memcached
資金交易業務比普通商品「秒殺」活動有更高的安全級別要求。普通的商品「秒殺」商品由商戶提供,庫存是商戶預設的,「秒殺」時能夠容許存在「超賣」(即實際被搶的商品數量比計劃的庫存多)、「少賣」(即實際被搶的商戶數量比計劃的庫存少)的狀況。可是對於微信紅包,用戶發100元的紅包絕對不能夠被拆出101元;用戶發100元只被領取99元時,剩下的1元在24小時過時後要精確地退還給發紅包用戶,不能多也不能少。
以上是微信紅包業務模型上的兩大特色。
在介紹微信紅包系統的技術難點以前,先介紹下簡單的、典型的商品「秒殺」系統的架構設計,以下圖所示。
該系統由接入層、邏輯服務層、存儲層與緩存構成。Proxy處理請求接入,Server承載主要的業務邏輯,Cache用於緩存庫存數量、DB則用於數據持久化。
一個「秒殺」活動,對應DB中的一條庫存記錄。當用戶進行商品「秒殺」時,系統的主要邏輯在於DB中庫存的操做上。通常來講,對DB的操做流程有如下三步:
a. 鎖庫存
b. 插入「秒殺」記錄
c. 更新庫存
其中,鎖庫存是爲了不併發請求時出現「超賣」狀況。同時要求這三步操做須要在一個事務中完成(所謂的事務,是指做爲單個邏輯工做單元執行的一系列操做,要麼徹底地執行,要麼徹底地不執行)。
「秒殺」系統的設計難點就在這個事務操做上。商品庫存在DB中記爲一行,大量用戶同時「秒殺」同一商品時,第一個到達DB的請求鎖住了這行庫存記錄。在第一個事務完成提交以前這個鎖一直被第一個請求佔用,後面的全部請求須要排隊等待。同時參與「秒殺」的用戶越多,併發進DB的請求越多,請求排隊越嚴重。所以,併發請求搶鎖,是典型的商品「秒殺」系統的設計難點。
微信紅包業務相比普通商品「秒殺」活動,具備海量併發、高安全級別要求的特色。在微信紅包系統的設計上,除了併發請求搶鎖以外,還有如下兩個突出難點:
首先,事務級操做量級大。上文介紹微信紅包業務特色時提到,廣泛狀況下同時會有數以萬計的微信羣在發紅包。這個業務特色映射到微信紅包系統設計上,就是有數以萬計的「併發請求搶鎖」同時在進行。這使得DB的壓力比普通單個商品「庫存」被鎖要大不少倍。
其次,事務性要求嚴格。微信紅包系統本質上是一個資金交易系統,相比普通商品「秒殺」系統有更高的事務級別要求。
普通商品「秒殺」活動系統,解決高併發問題的方案,大致有如下幾種:
方案一,使用內存操做替代實時的DB事務操做。
如圖2所示,將「實時扣庫存」的行爲上移到內存Cache中操做,內存Cache操做成功直接給Server返回成功,而後異步落DB持久化。
這個方案的優勢是用內存操做替代磁盤操做,提升了併發性能。
可是缺點也很明顯,在內存操做成功但DB持久化失敗,或者內存Cache故障的狀況下,DB持久化會丟數據,不適合微信紅包這種資金交易系統。
方案二,使用樂觀鎖替代悲觀鎖。
所謂悲觀鎖,是關係數據庫管理系統裏的一種併發控制的方法。它能夠阻止一個事務以影響其餘用戶的方式來修改數據。若是一個事務執行的操做對某行數據應用了鎖,那只有當這個事務把鎖釋放,其餘事務纔可以執行與該鎖衝突的操做。對應於上文分析中的「併發請求搶鎖」行爲。
所謂樂觀鎖,它假設多用戶併發的事務在處理時不會彼此互相影響,各事務可以在不產生鎖的狀況下處理各自影響的那部分數據。在提交數據更新以前,每一個事務會先檢查在該事務讀取數據後,有沒有其餘事務又修改了該數據。若是其餘事務有更新的話,正在提交的事務會進行回滾。
商品「秒殺」系統中,樂觀鎖的具體應用方法,是在DB的「庫存」記錄中維護一個版本號。在更新「庫存」的操做進行前,先去DB獲取當前版本號。在更新庫存的事務提交時,檢查該版本號是否已被其餘事務修改。若是版本沒被修改,則提交事務,且版本號加1;若是版本號已經被其餘事務修改,則回滾事務,並給上層報錯。
這個方案解決了「併發請求搶鎖」的問題,能夠提升DB的併發處理能力。
可是若是應用於微信紅包系統,則會存在下面三個問題:
若是拆紅包採用樂觀鎖,那麼在併發搶到相同版本號的拆紅包請求中,只有一個能拆紅包成功,其餘的請求將事務回滾並返回失敗,給用戶報錯,用戶體驗徹底不可接受。
若是採用樂觀鎖,將會致使第一時間同時拆紅包的用戶有一部分直接返回失敗,反而那些「手慢」的用戶,有可能由於併發減少後拆紅包成功,這會帶來用戶體驗上的負面影響。
若是採用樂觀鎖的方式,會帶來大數量的無效更新請求、事務回滾,給DB形成沒必要要的額外壓力。
基於以上緣由,微信紅包系統不能採用樂觀鎖的方式解決併發搶鎖問題。
綜合上面的分析,微信紅包系統針對相應的技術難點,採用了下面幾個方案,解決高併發問題。
1.系統垂直SET化,分而治之。
微信紅包用戶發一個紅包時,微信紅包系統生成一個ID做爲這個紅包的惟一標識。接下來這個紅包的全部發紅包、搶紅包、拆紅包、查詢紅包詳情等操做,都根據這個ID關聯。
紅包系統根據這個紅包ID,按必定的規則(如按ID尾號取模等),垂直上下切分。切分後,一個垂直鏈條上的邏輯Server服務器、DB統稱爲一個SET。
各個SET之間相互獨立,互相解耦。而且同一個紅包ID的全部請求,包括髮紅包、搶紅包、拆紅包、查詳情詳情等,垂直stick到同一個SET內處理,高度內聚。經過這樣的方式,系統將全部紅包請求這個巨大的洪流分散爲多股小流,互不影響,分而治之,以下圖所示。
這個方案解決了同時存在海量事務級操做的問題,將海量化爲小量。
2.邏輯Server層將請求排隊,解決DB併發問題。
紅包系統是資金交易系統,DB操做的事務性沒法避免,因此會存在「併發搶鎖」問題。可是若是到達DB的事務操做(也即拆紅包行爲)不是併發的,而是串行的,就不會存在「併發搶鎖」的問題了。
按這個思路,爲了使拆紅包的事務操做串行地進入DB,只須要將請求在Server層以FIFO(先進先出)的方式排隊,就能夠達到這個效果。從而問題就集中到Server的FIFO隊列設計上。
微信紅包系統設計了分佈式的、輕巧的、靈活的FIFO隊列方案。其具體實現以下:
首先,將同一個紅包ID的全部請求stick到同一臺Server。
上面SET化方案已經介紹,同個紅包ID的全部請求,按紅包ID stick到同個SET中。不過在同個SET中,會存在多臺Server服務器同時鏈接同一臺DB(基於容災、性能考慮,須要多臺Server互備、均衡壓力)。
爲了使同一個紅包ID的全部請求,stick到同一臺Server服務器上,在SET化的設計以外,微信紅包系統添加了一層基於紅包ID hash值的分流,以下圖所示。
其次,設計單機請求排隊方案。
將stick到同一臺Server上的全部請求在被接收進程接收後,按紅包ID進行排隊。而後串行地進入worker進程(執行業務邏輯)進行處理,從而達到排隊的效果,以下圖所示。
最後,增長memcached控制併發。
爲了防止Server中的請求隊列過載致使隊列被降級,從而全部請求擁進DB,系統增長了與Server服務器同機部署的memcached,用於控制拆同一個紅包的請求併發數。
具體來講,利用memcached的CAS原子累增操做,控制同時進入DB執行拆紅包事務的請求數,超過預先設定數值則直接拒絕服務。用於DB負載升高時的降級體驗。
經過以上三個措施,系統有效地控制了DB的「併發搶鎖」狀況。
3.雙維度庫表設計,保障系統性能穩定
紅包系統的分庫表規則,初期是根據紅包ID的hash值分爲多庫多表。隨着紅包數據量逐漸增大,單表數據量也逐漸增長。而DB的性能與單表數據量有必定相關性。當單表數據量達到必定程度時,DB性能會有大幅度降低,影響系統性能穩定性。採用冷熱分離,將歷史冷數據與當前熱數據分開存儲,能夠解決這個問題。
處理微信紅包數據的冷熱分離時,系統在以紅包ID維度分庫表的基礎上,增長了以循環天分表的維度,造成了雙維度分庫表的特點。
具體來講,就是分庫表規則像db_xx.t_y_dd設計,其中,xx/y是紅包ID的hash值後三位,dd的取值範圍在01~31,表明一個月天數最多31天。
經過這種雙維度分庫表方式,解決了DB單表數據量膨脹致使性能降低的問題,保障了系統性能的穩定性。同時,在熱冷分離的問題上,又使得數據搬遷變得簡單而優雅。
綜上所述,微信紅包系統在解決高併發問題上的設計,主要採用了SET化分治、請求排隊、雙維度分庫表等方案,使得單組DB的併發性能提高了8倍左右,取得了很好的效果。
微信紅包系統是一個高併發的資金交易系統,最大的技術挑戰是保障併發性能與資金安全。這種全新的技術挑戰,傳統的「秒殺」系統設計方案已不能徹底解決。在分析了業界「秒殺」系統解決方案的基礎上,微信紅包採用了SET化、請求排隊串行化、雙維度分庫表等設計,造成了獨特的高併發、資金安全系統解決方案,並在平時節假日、2015和2016春節實踐中充分證實了可行性,取得了顯著的效果。在剛剛過去的2017雞年除夕夜,微信紅包收發峯值達到76萬每秒,收發微信紅包142億個,微信紅包系統的表現穩定,實現了除夕夜系統零故障。