電商非同步訊息系統的實踐

錢曙光發表於2016-08-04

宣告:本文為《程式設計師》7月期原創投稿文章,未經許可禁止任何形式的轉載。
作者:王曉宇,小米網平臺研發部軟體研發工程師。2015年入職小米,主要負責電商後端倉儲物流相關的業務系統開發。曾在西門子中國研究院,從事軟體研發工作,擁有兩年以上的軟體開發相關經驗。曾使用過的程式語言主要有Java與PHP,擁有多年的伺服器開發經驗以及MySQL優化經驗,對電商相關業務與系統架構具有一定的瞭解以及自己的見解。
責編:錢曙光,關注架構和演算法領域,尋求報導或者投稿請發郵件qianshg@csdn.net,另有「CSDN 高階架構師群」,內有諸多知名網際網路公司的大牛架構師,歡迎架構師加微信qshuguang2008申請入群,備註姓名+公司+職位。


摘要:本文首先介紹小米網系統架構的發展變化,然後介紹Notify系統的設計,最後介紹Notify系統的演化與升級變遷。希望能給各位的工作帶來一些啟發與指導。


為了適應業務的高速發展,小米網的系統架構經歷了很多次變更。在此過程中,為了給各個子系統解耦合,同時保證最終一致性原則的實現,我們建立了自己的非同步訊息系統——
Notify非同步訊息系統。

小米網架構發展

小米網的發展大致可以分為三個階段:初創階段、發展階段、完善階段。

1. 初創階段

當小米推出自己的第一部手機時,為了減少渠道成本,我們開始推行電商直銷的商業模式,與此同時,開始建設小米電商網站。最開始,小米的業務特點是:

  • SKU(商品品類)單一;
  • 訂單量巨大;
  • 瞬時訪問量巨大。

後兩點是在最初設計系統時完全沒有想到,因為當前我們並沒有預料到小米手機會如此受歡迎和供不應求。

在這個階段,快速上線是第一目標,因為團隊需要快速配合公司的手機銷售計劃。所以一開始小米網的架構設計比較簡單,並沒有考慮高併發和大資料的情況。當時系統從立項到上線僅兩個多月時間,並且只由三名工程師開發完成。

圖片描述

圖1 小米電商初期的系統架構

從圖中可以看出,系統架構只有兩個Web伺服器與一個DB伺服器,兩臺Web伺服器互為主備,所有的業務功能整合在一個系統中。當時的架構設計僅能支援簡單的電商功能,我們預測每年的手機銷量能到30萬就已經很好了。但是計劃永遠趕不上變化,很快小米電商就遇到了第一個大問題:系統耦合度很高,導致當搶購活動開始時,其他業務都會受到影響。

2. 發展階段

為了解決上面的問題,需要對小米網的架構進行修改,把各種業務系統拆成獨立的子系統。這一時期小米電商的系統架構發展的特點是:

  • 業務系統的拆分,小米負責處理搶購請求的大秒系統就是在這一階段誕生的,將搶購業務帶來的系統壓力完全隔離開來,確保在搶購活動時小米的其他業務可以不受影響;
  • 小米的系統結構SOA(面向服務的軟體結構)化,小米各系統之間的通訊採用介面方式來實現,甚至我們開發了一套通訊協議,叫做X5協議,來規範介面的開發與呼叫。

這一階段小米網的架構如下圖所示:

圖片描述

圖2 小米電商發展階段的系統架構

從圖2可以看出,前端與後端系統完全獨立出來,當前端進行搶購活動時,後端的客服、售後、物流三大服務系統不會受到任何影響。圖2中所列舉的子系統只是小米網中的一小部分較為主要的系統,還有很多業務沒有列舉出來。

這種系統架構可以確保系統之間不會受到影響,但是介面的穩定性就成了一個至關重要的問題。這種系統架構幾乎所有的介面都是同步介面,意思就是說一個業務呼叫這些介面如果不成功,業務也無法成功。如果出現網路問題或者介面BUG,就會導致大量業務失敗。但事實上,並非所有的介面都必須做成同步性的,有些業務比如訂單系統把訂單資訊發給倉儲系統生產,就對同步性的要求不是很高,可以考慮使用非同步方式來解決。為了解決這個問題,小米網架構下一步的演化就是建立一個非同步訊息佇列系統。

3. 完善階段

經過非同步訊息系統的引入,小米網的系統架構最終發生了變化,如圖3所示:

圖片描述

圖3 小米網的系統架構

從圖3可以看出,非同步訊息佇列成了一箇中心節點,幾乎所有的子系統都與它有單向或者雙向的互動。當一個業務需要呼叫一個介面時,如果他對實時性的要求不是特別高,就可以把訊息發到我們的非同步訊息佇列系統,然後他就可以完成這項業務,而之後的訊息投遞過程就完全交給訊息佇列系統來實現。經過這次調整之後,小米網基本實現了主要業務的非同步化,大大增加了系統的容錯性,因為所有投遞不成功的訊息都可以儲存在訊息佇列系統中等待下次投遞。

Notify訊息系統的設計

基於對上面業務變化的分析,小米網內部開始計劃建立自己的非同步訊息系統。我們對比了市面的幾款MQ軟體,最後決定以Redis佇列為基礎,開發自己的非同步訊息佇列系統,取名叫做Notify非同步訊息系統。

Notify訊息系統的設計需要解決以下幾個問題:

  • 如何接收訊息;
  • 如何儲存訊息;
  • 如何投遞訊息;
  • 對訊息的統計與監控。

我們採用介面的方式來接收業務系統的訊息,採用MySQL來儲存訊息,在訊息傳送時使用Redis佇列來儲存。

為了實現以上主要功能,為Notify系統設計了以下的資料結構。下面為五個最主要的資料表,以及重要的欄位:

  1. biz - 業務(生產者);

  2. receive - 接收者(消費者);

  3. biz_receive - 訂閱關係;
    狀態欄位,表示訂閱關係的執行狀態,分為正常、暫停(接收訊息,但不傳送)、廢棄(不接收訊息)三種。
    介面地址欄位。

  4. biz_msg - 業務訊息;
    訊息體欄位。

  5. receive_msg - 投遞訊息;
    傳送狀態欄位,分為四種狀態,待處理、待投遞、已投遞、丟棄。
    傳送次數字段。

這裡需要提一下Notify系統的訊息分裂機制。考慮到有可能在一項業務執行過程中需要把訊息發給多個介面,Notify訊息在設計的時候引入了一個訊息分裂的概念。

圖片描述

圖4 Notify訊息設計中的三種訊息分裂情況

圖4分別列舉了訊息分裂的三種情況。首先第一種,在沒有訊息佇列時直接呼叫介面的情況,一個業務執行時如果要將一個訊息傳遞給不同的系統時,就需要呼叫不同的介面,並且這些介面還必須都返回成功,才能算這個業務執行成功。第二種情況是引入訊息佇列來處理這個問題的話,如是不進行分裂處理的話,S1需要把同一個訊息塞到不同的訊息佇列裡去。也就是要多次將訊息傳送給Notify系統。這樣設計雖然可以確保業務執行成功,但是卻不具備擴充套件性。假設我們新建立一個系統,也需要同樣的訊息,那麼就不得不回過頭來修改程式碼,關閉一個系統也是同理。所以第三種情況中我們設立了一個訊息分裂與訂閱的機制,業務執行時只需要把訊息投遞到Notify系統一次,而其他系統如果需要這個訊息,就可以在我們的Notify系統中設定訂閱關係,同樣的訊息就被複製成多個副本,然後被塞到多個不同的訊息佇列來投遞。這樣做既可以進一步提高業務執行的成功性,又使得業務具備可擴充套件性與可配置性。

圖片描述

圖5 Notify的架構設計

基於上面的一系列設計思想,最終形成了圖5中的結構設計圖,即Notify系統的最初設計圖。我們通過Api.notify介面,來收集業務系統的訊息,並存放在DB中。傳送訊息時,我們設立一個Maker元件,這個元件採用多程式執行方式,對每一個訂閱關係開啟一個程式,把訊息複製一個副本並放到對應的RedisMQ中。MQ的名稱就以biz-receive的對應id組合而成,方便查詢。然後,我們設立了一個Sender元件,Sender主要完成兩樣工作:一是把訊息傳送給對應的業務系統;二是把訊息放到Marker Queue中,來回寫訊息的狀態。如果訊息傳送成功了,就把訊息的狀態回寫成已投遞,如果傳送失敗,就把訊息狀態重新回寫成待處理,以便下一個週期再次投遞。然後我們又設立了一個Marker程式,來非同步的讀取Marker Queue裡的訊息來回寫狀態。這樣就完成了一個投遞週期,整個Notify系統就是通過這種方式源源不斷的將訊息投遞給各業務系統。

除了上面的主要結構外,在實現Notify時,還引入了以下幾個特性:

  1. 訊息分裂,如上文介紹過的一樣。

  2. 冷庫備份功能。隨著業務的擴充套件,DB中的訊息數也增長的很快,如果不對DB中的訊息做備份,會影響Notify本身的效能,以及統計功能的可用性。對於已經投遞成功的訊息來說,大部分情況下不會被用到,所以需要定期對訊息做遷移冷庫的操作。

  3. 為了保證傳送訊息的時效性,對Maker與Sender進行了多程式程式設計,每個程式負責一個訂閱關係的處理,可以獨佔一個MQ的控制權,我們通過這種方式來提高訊息傳送的時效性,確保關鍵業務訊息不會被阻塞。

  4. 訊息重發功能。如果訊息傳送失敗,會被Marker重新標記成待處理狀態,以進入下一次的投遞週期,同時訊息的發次數會加1。每次投遞都間隔一定的時間,當投遞次數超過一個閾值時,就不再投遞了。因為這個時候可能是由於業務系統的介面出了什麼問題,再嘗試投遞沒有任何意義,還會造成網路流量的浪費,影響其他系統的業務,所以停止繼續投遞。待介面問題修復後,我們再手動批量重推訊息。

  5. 採用非同步方式,在投遞週期中,也用到了非同步思想,這樣做也是為了加快訊息投遞的時效。

  6. 訊息可查詢。Notify為小米網的其他工程師提供了一個可以查詢訊息體及返回的地方,方便我們工程師除錯系統,定位bug。

Notify訊息系統的升級變遷

第一版Notify訊息系統設計的時候還存在著很多不足與考慮不周之處。隨著業務的發展,逐漸暴露出很多問題。小米網每年有兩大促銷活動,一個是天貓的雙十一活動,另外一個是小米自己的米粉節。這兩個節日,小米網的訂單量是呈爆發式的增長,而且每年的訂單量峰值都會有所增加。這對業務系統,尤其是作為中心節點的Notify系統來說,是一次巨大的衝擊。所以Notify訊息系統在最初設計的基礎經過了很多修改,並且於去年完成了一次大的重構,才達到了現在的處理能力,以目前的效能來看,小米網可以輕鬆經受住米粉節或雙十一的訂單量。

目前Notify系統主要的不足有以下幾點:

  1. 業務直接通過介面方式投遞訊息。如果網路出現不可靠的情況,直接投遞還是會有引起業務失敗的風險;
  2. 系統全部使用PHP來實現。做為一種指令碼語言,PHP還是有很多不足的地方,比如無法進行高效的運算;
  3. 採用單一MySQL例項。在資料量過大時會影響效能。

針對上面的這些不足,我們對Notify進行了一些重構。首先,對於接收訊息的功能,我們改為採用Agent代理的方式,來收集訊息。如圖6所示:

圖片描述

圖6 Notify採用Agent代理的方式

業務系統由原來的直接將訊息傳送給Notify,改為將訊息存在本地資料庫,然後由常駐記憶體的Agent代理來收集訊息,並將訊息傳送給Notify系統。如此以來,大大增加了業務的成功率,因為DB操作的可靠性遠大於網路操作。

對於PHP問題,我們則是採用Golang將關鍵的模組進行了重構。經過這次重構,主要模組的效能都有了很大的提升。與老版本對比,Api.Notify系統每臺Web伺服器接收訊息的能力提升了21倍,Marker服務處理能力提升了4倍,Sender服務處理能力提升了4倍。最終投遞週期中的各環節效能達到了圖7中所示:

圖片描述

圖7

針對單一MySQL例項問題,我們引入了MyCAT,MyCAT是阿里開發並維護的一款開源資料庫中介軟體,實現了MySQL的分散式儲存,提升了資料庫效能,並且MySQL例項數量動態可擴充套件,最重要的是MyCAT可執行MySQL語句,因此與MySQL幾乎無縫切換,開發成本小。

總結

本文主要介紹了小米網在實現非同步訊息佇列系統時所進行的實踐與探索,介紹了非同步訊息系統的設計經驗,為其他公司的實踐提供保貴經驗。


編輯推薦:架構技術實踐系列文章(部分):

2016年8月12日-13日,由CSDN重磅打造的網際網路應用架構實戰峰會運維技術與實戰峰會將在成都舉行,目前18位講師和議題已全部確認。兩場峰會大牛講師來自阿里、騰訊、百度、京東、小米、樂視、聚美優品、YY互娛、華為、360等知名網際網路公司,一線深度的實踐,共同探討高可用/高併發/高效能系統架構設計、電商架構、分散式架構、運維工具研發與實踐、運維自動化系統的構建、DevOps、雲上的運維案例分析、虛擬化技術、應用效能檢測與管理、遊戲行業的運維實踐等,將和與會嘉賓共同探討「構建更安全、更高效能、更穩定的架構和運維體系」等領域的話題與技術。【八折優惠中,點選這裡搶票,欲購從速。】

相關文章