小米網技術架構變遷實踐.
- 標簽 :
文/ 張濤,小米網架構師
“先賣10000臺再說!”2011年8月9日,小米網負責人黎萬強在公司內部大會上這樣說道。時間回到4年多前,在小米“標配”產品發布日8月16日前幾天,小米公司100多號人沉浸在即將到來的第一款產品誕生的亢奮中,但是,沒有人能告訴我們:未來,我們將能走多遠\做多大。彼時,小米網僅有三位開發工程師,在經過兩個多月的緊張開發后,小米網將要第一次面對公眾在線銷售產品,接受大考。由于工程師資源極度緊張,我們甚至考慮過使用ECSHOP之類的開源系統搭建小米網,不過幸好我們很快放棄了這一想法。因為在3個月之后,我們就發現不得不對系統進行一輪重構了。如果使用了第三方開源系統,為適應原系統架構,我們將長期被迫“遷就”原架構而放棄很多更優的設計,并為學習這個系統而付出時間成本。
第一代的小米網架構非常簡單,如圖1所示。
圖1 第一代小米網架構
我們實現了一個最基本的電商網站基本組件:在線銷售系統、訂單處理系統、倉儲和物流系統,其中物流只對接了兄弟公司凡客的子公司如風達(現已獨立)。所有的業務系統共用一個數據庫。這樣運行了幾個月,發現網站訪問量越來越大,當有新品銷售時,面對突然激增的大量訪問,數據庫壓力陡增,造成后端業務系統幾乎無法使用。
2012年上半年,在小米網運行半年多后,我們決定將業務系統進行拆分,首先將銷售系統剝離,之后逐步將越多越多的子系統拆分。拆分后各業務系統相對獨立,各自使用自己的數據庫,這樣就完美解決了不同系統搶占數據庫資源的問題,也讓模塊更清晰,程序員也能專注于自己負責的業務系統的開發,如圖2所示。
圖2 拆分業務系統
這種結構隨著小米網子系統的增多,只運行了幾個月,我們就發現災難開始顯現了:我們需要維護的接口越來越多。系統間接口調用圖變成了圖3這樣。
圖3 系統間接口調用圖
這張網越來越復雜,從而使系統越來越難以維護,問題層出不窮。為了讓各子系統盡量解耦,我們開發了小米網異步消息服務系統(Notify),讓它作為所有子系統異步通信的中間人,所有子系統只需與中間人通信,接口標準化,將網狀結構變為星狀結構,大大降低了系統間通信成本,提高了開發效率,如圖4所示。
圖4 小米網異步消息服務系統(Notify)
此時,我們各子系統的網絡架構也進行了相應的升級,大體分三層:調度層、業務層、數據庫。在調度層,我們主要使用LVS、HAProxy做流量轉發和故障轉移;業務層則五花八門,不同語言,不同框架百花齊放;數據層主要使用MySQL、NoSQL存儲及緩存服務(Redis和Memcache)。
經過以上改造,我們從結構上,讓整個流程更清晰了。然而,流量在繼續增大,特別是小米網的爆品非常多,由于供應鏈及硬件產業特性,導致新品上市時供應量無法滿足用戶需求,用戶的熱情又遠超我們的期望。大量請求導致前端銷售系統的數據庫開始告急。我們急需采取一種方案,將峰值抹平。我們是一家電商網站,在交易時會有大量在線聯機事務處理,對數據一致性要求極高,所以經過討論,我們決定采用淘寶開源的數據庫中間件產品Cobar來實現數據庫的水平切分。一共部署了32個實例,按用戶ID實現數據的均勻讀寫,每個實例都做了MM雙主高可用實現,如圖5所示。
圖5 采用淘寶開源的數據庫中間件產品Cobar
這種架構保證了我們日常的在線銷售穩定運行無壓力,但是,每逢重大產品發布時,仍然要面對數百萬QPS的搶購并發壓力。不光是對數據庫,對前端的應用程序服務器一樣造成巨大的沖擊。非搶購時間和搶購時間的流量差距可達幾十倍到上百倍,如果我們按搶購峰值流量部署服務器,那將是一筆巨大的硬件資產浪費。在這個背景下,我們組織專門的人員開發了小米網的大型秒殺系統:BigTap。其實道理很簡單,大家都去銀行辦過業務,在銀行窗口排隊的多,但是我們幾乎很少看到有人在銀行的取號機面前排過隊。大秒系統其實就是充當銀行的取號機的角色。它的業務邏輯非常簡單:判定用戶是否合法,合法則給這個用戶購買資格,用戶搶購成功;不合法則拒絕用戶請求,搶購失敗,如圖6所示。
圖6 小米網的大型秒殺系統BigTap
由于平時不搶購,大秒系統沒有任何流量,所以,我們將大秒系統整體遷移至AWS云上,搶購前一天,將系統擴容,搶購后,將服務器再下架,實現完美伸縮,大大節約了成本。
除了大秒系統以外,我們還額外開發了眾多小米網特色服務以支撐自己的業務。其中之一就是基于Redis和Twitter開源的Twemproxy開發的小米通用緩存服務(內部代號MCC),集群中單節點達到14萬QPS,支持自動分片,熱加載,全Redis協議支持。由于MCC支撐了小米網全業務線的緩存服務,所以我們還將此服務設計成雙機房高可用架構,如圖7所示。
圖7 小米雙機房架構(圖中M表示主Redis實例,S表示從Redis實例)
常態下,雙機房同時工作,讀寫機房1的主和從實例。機房2的Mi-Twemproxy也讀寫機房1的主從實例。當機房1故障時,只需修改機房2的Mi-Twemproxy讀寫機房2的從實例,并將此實例提升為主實例。當機房2出現故障時,不需要做任何改動。不足之處是當機房1出現故障時,機房2短期內只有一個主實例工作,無冗余。
在搭建電商網站中,我們還要時刻考慮的一個業務問題是:如何盡快地將貨物售出,實現最快的庫存周轉,同時還要有好的購物體驗,在這個問題上,庫存系統的設計是一個很大的挑戰。我們嘗試考慮過很多電商的做法:按倉庫庫存賣商品。這種設計的好處是:倉與倉之間不用調撥,省去物流費用。缺點是,可能某個倉庫存過高賣不出,某個倉又缺貨,導致用戶無法下單購買。最終導致庫存周轉周期太長,降低了整體效率。小米網結合自身實際情況,設計了一套虛擬庫存分配系統,將各個倉作為庫存渠道,可以自由合并,拆分供給不同的銷售渠道,且可以自由調配。這種方法的缺點是訂單可能會跨倉發貨,增加物流成本,但是優點也顯而易見:大大提高了庫存周轉率,用戶也獲得了較好的購物體驗,在以用戶為中心的小米網,這是我們首先會考慮的問題,設計如圖8所示。
圖8 小米虛擬庫存分配系統
跨倉調撥既然無法避免,那我們能做的是盡量減少跨倉調撥頻次,在多個倉庫之間調撥時盡量合理規劃。小米網的跨倉調撥問題實際上是一個多目標線性規劃求最優解的問題,我們將各倉當前需求量,未來預測需求量,調撥線路和時間均考慮了進去。
在任何一家互聯網公司中,都必須要重視的一件事就是:監控。服務器、應用程序、我們的業務,都存在監控需求。然而當今好的監控方案幾乎是空白,每個公司業務不同,特點不同,通用的監控方案全都收效甚微。監控的意義在于出故障時,責任人應該第一時間知道并能采取措施。這要求監控系統做到一是及時,二是準確。當監控對象是一家大型公司時,還要求監控系統做到極其重要的一點是——有效。下面我會分析何為有效監控。小米網一路走過來,做過很多監控,當業務觸發異常時就開始通過短信、郵件等告警。就算監控點很少時也不足以構成威脅,一旦觸發告警,馬上處理。然而,業務量激增后,監控點非常多,責任人往往會在短時間內收到大量重復的告警郵件和短信,時間一長,人都會疲勞,此時極容易忽略重大告警。所以,之前監控系統設計的最大問題在于沒有區分異常和告警的關系,沒有設定異常的策略和告警的策略。如果一條告警送到了,但是沒有引起責任人的重視,那么這就是一條失敗的、無效的告警。遇到異常馬上觸發告警是不科學的設計。我們新設計的監控系統的目的是大大提高有效告警量,其核心思想如下。
異常判斷策略:
異常判定函數
- val,監控結果取值
- val(‘key’),結果若有多個字段,如Server各參數,取指定 key 的值
- count,結果若有多條,取結果集條數
- exist,監控結果是否存在
- empty,監控結果是否為空
異常判定表達式
- val>3 ,取值大于3時判定為異常
- count>10,結果集條數大于10時判定為異常
- exist,結果不為空即判定為異常
- empty,結果為空即判定為異常
告警判斷策略:
告警判定函數
- times,當前連續異常次數 percent(num),最近n次監控結果中異常的百分比
- limit(num),告警次數限制,num=0為不限制
- snooze(minutes),不限制告警次數時,異常周期內每隔minutes分鐘告警一次 告警
判定策表達式
- times>3,連續異常3次則告警
- percent(10)>0.8,最近10次監控結果中異常數大于80%則告警
-
times>3 && limit(0) && snooze(30),大于3次異常開始告警,異常周期內,每隔30分鐘告警一次
通過這兩個策略組合,能夠實現最大限度不打擾人的有效告警。
最后,我在此稍提一下小米網的服務化。這是目前我們正在進行的技術架構大升級,采用Thrift+ETCD+Go+PHP實現。小米SOA框架完全自行開發,框架本身由Go語言實現。非Go語言的項目,我們通過兩個小插件:服務發現助手和服務注冊助手幫助接入到我們的服務平臺。關于服務化的意義,網上有非常多的詳細的分析,在此不再贅述。對于越來越大的技術架構體系,服務化將是未來的趨勢。
上一篇:投資人說:聰明人為什么難以成功

