電商異步消息系統的實踐

crduq7605 8年前發布 | 45K 次閱讀 消息系統

作者:王曉宇,小米網平臺研發部軟件研發工程師。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幾乎無縫切換,開發成本小。

總結

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

來自:http://www.tuicool.com//articles/MZf2yei

 

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