蘑菇街電商交易平臺服務架構及改造優化歷程
蘑菇街導購時期 業務結構
蘑菇街是做導購起家的,當時所有的業務都是基於使用者和內容這兩大核心展開。那個時候前臺業務主要做的是社交導購,後臺業務主要做的是內容管理。一句話總結就是小而美的狀態,業務相對來也不是很複雜。
當時的技術架構是典型的創業型公司技術架構。網站整體是用 PHP 搭建的,系統做了簡單的分層,基礎設施以現成的開源產品為主。2013 年時蘑菇街做了轉型,主要原因那段時間很大一批導購網站遭到了封殺,於是就轉型做社會化電商平臺。
社會化電商平臺分兩部分,一部分社會化,我們之前做導購時積累了一些經驗。電商是我們之前沒有接觸過的,這塊基本上是從零開始一手建立起來。要做電商平臺,首先就要搭建一個交易平臺。起初比較簡單,我們重寫了一套系統,系統結構和之前相比並沒有本質上的變化,所有業務都寫在一個巨大的工程裡面, 中間通過一套代理層和我們的基礎設施進行互動。
電商轉型面臨的問題
-
業務高速發展,每年保持 3 倍以上的增長(2015 年使用者過億,PV 超過 10億)
-
使用者購買鏈路大促峰值是日常的百倍(2015 年初最高只支援 400 單/秒)
-
業務異常複雜,業務形態快速膨脹
-
歷史包袱沉重,系統耦合非常嚴
蘑菇街轉型到電商平臺以後,業務基本上每年以三倍以上的速度增長,這個時候問題開始暴露了。電商平臺在發展過程中尤其在發展中期遇到的一些的問題,不僅僅是蘑菇街的,其它平臺可能也會遇到。如系統程式碼臃腫、模組耦合程度高,依賴複雜,業務擴充套件能力差等。
蘑菇街那個時候主要面臨了幾個問題:
一個是我們業務在高速增長,系統容量跟不上
另一個是電商業務形態變的特別快,業務支撐不夠靈活,不夠快。
此外還有歷史包袱,系統耦合非常嚴重。
解決這一系列問題的關鍵就一個字:“拆”。
系統拆分歷程
-
DB 垂直拆分
-
業務系統垂直拆分(購物車,下單,資金…)
-
資料 & 業務模型統一,服務介面設計邏輯清晰,粒度合適
-
基礎業務邏輯下沉到服務,Web 層專注表示邏輯和編排
-
服務治理
系統拆分——交易購物車為例
以交易購物車的例子來說明下我們的改造過程,以前我們就一個工程,所有的程式碼都寫在這,不同的終端或業務都有一個不同的模組程式碼在維護。訪問資料也比較隨意,各自維護一套資料訪問的程式碼。因此就有兩個非常頭疼的問題:
一方面由於交易就一個庫,所有內容都是放在裡面,因此這些隨意散亂的 SQL 可能會冷不丁的給你來個慢查詢,別的業務程式碼帶來的不穩定會相互影響,還很難定位這種“野 SQL”是哪裡查過來的,導致我們的 DB 很不穩定,對後續改造非常不利。
另外一個是業務支撐方面,產品提一個需求過來,在各種端都要實現一遍,複用性很差,業務支撐非常不靈活,系統毫無擴充套件性,開發同學也是苦不堪言,經常加班加點搞,還經常搞出一堆 bug。
於是我們就去拆系統,到底怎麼拆?其實也是有些講究的。
系統拆分的優先順序
如果把 DB 比如成一個木桶,各類業務就可以比喻為往裡面倒的水,一開始往木桶裡面倒的水可能並不多,木桶裝的下沒問題。但是隨著業務增長,木桶總有一天是會裝不下的。
首先木桶需要足夠大,並且能很方便擴容,這樣才不會有後顧之憂。業務量有時候並不好預測,指不定什麼時候量就起來了,如果不把這個底層的木桶做得足夠強大,而優先去搞業務上的拆分優化,那量一旦起來整個系統就歇菜了。
因此 DB 是系統拆分的基礎,需要優先拆分。
DB 拆出來的同時還要關注穩定性,前面也提到,當時 SQL 是比較散亂,極易造成 DB 不穩定,所以資料訪問/模型統一也很關鍵,我們建了統一的資料訪問層。有了這一層之後,後面對
DB 的改造擴容都能夠比較有效的掌控。
基礎的東西都建好了,再來解決業務支撐困難這個問題。業務模型上需要統一抽象,能夠支援定製擴充套件。流程改造過程同時也孵化出了 SPI 業務框架、流程引擎、規則引擎等這些基礎業務框架。在業務支撐上做到了靈活可擴充套件。系統也做了比較合理的分層,每層只需要關心本層所需關注的能力即可。
系統拆分成果
交易系統在整體拆分完成以後,公司 SOA 化雛形也基本已經形成了,包括基礎服務化框架、訊息中介軟體、資料中介軟體、配置中心也都落地實施了,此外還孵化了一系列基礎設施工具,包括監控系統,排程系統,日誌蒐集,鏈路跟蹤系統等。
還有一個背景拆分過程中,公司戰略整體往 Java 語言轉,這個是公司綜合層面考慮,Java 的人才尤其是杭州相對比較多,技術體系也比較成熟,有大牛在能 hold 住問題,當時確實 PHP 資源比較少。
容量提升
做了系統拆分改造以後,接下來更多的會去關注應用本身的容量、效能以及穩定性方面的事情。我們也在這些方面分別作了一些改造和嘗試。
-
按業務對 DB 進行垂直拆分
-
讀寫分離,保證讀可以任意擴充套件
-
分庫分表,提升中心服務寫入容量
系統拆分時,已經按業務把 DB 垂直拆分出來了,並且 DB 也做了讀寫分離(基於 MySQL)。
下面重點介紹一下分庫分表上的改造,當時目的主要是為了提升中心服務的寫入容量,因為當時 DB 讀寫分離是單 Master 結構,會有一個寫入瓶頸。
以交易建立為例來說明我們分庫分表的歷程,交易建立應該算是交易裡面最為複雜的業務場景之一。建立一筆訂單的時,會同同時寫入其他很多的資料,當時系統容量大約是每秒能夠處理一千單,DB 單點存在寫入瓶頸,並且寫入過多會造成主從延遲嚴重。另外 DB 磁碟空間也已經突破了 80%,不穩定性非常高,有可能隨時會崩掉。
所以我們就決定去做拆分,當時的背景是中介軟體還沒建立起來,沒有分庫分表相關的元件,於是就決定內部先搞起來。
當時對比了業內比較流行的一些方案,比如阿里的 TDDL,Cobar,谷歌的 Vitess 等,比較下來發現這些元件都比較重,接入和使用成本都相對比較高。我們的原則是符合我們業務場景下,選一種接入和使用成本都相對簡單的元件。於是我們採取的是最後一種方式,通過 MyBatis Plugin 位元組碼增強的方式實現分庫分表功能,該元件目前已開源:https://github.com/baihui212/tsharding
分庫分表業界方案對比如下圖:
自研分庫分表元件 TSharding,完成分庫分表
-
足夠簡單,投入較少資源
-
支援分庫分表
-
支援資料來源路由
-
支援事務
-
支援結果集合並
-
支援讀寫分離
這個元件叫 TSharding,它的特點是足夠簡單,是符合我們預期的,支援分庫且分表,支援資料來源路由,支援事務,支援結果集合並,支援讀寫分離,滿足我們所有的要求。
效能優化
我們在效能優化上也做了一些嘗試,主要舉下面三個場景:
-
分散式事務處理
-
單機非同步並行
-
預處理 & 快取
分散式事務處理----交易建立為例
優化思路:非同步訊息解耦
-
交易建立流程中,訂單、券和庫存的狀態必須要保證一致性
-
營銷優惠券服務和庫存中心庫存服務,與下單服務是分開部署的
-
呼叫券/庫存服務超時/失敗,非同步發訊息通知回滾;複雜性可控
-
MQ 產生端傳送失敗重試 + 消費接受 ACK 機制保證做種一致
-
消除了二階段提交等分散式事務框架的侵入性影響
首先講分散式事務處理,這裡還是以交易建立為例,交易建立過程會跟多個服務進行互動,並且有些服務是強依賴,比如扣減庫存,鎖券服務,必須保持一致性。二階段/多階段協議這種方式很重,當時並沒有採納。
我們當時想了一個方法是,通過非同步訊息解耦的方式來解決,具體流程:
下單時先不要急著讓這個訂單暴露出來,我們先建立一筆不可見的訂單(或者可以認為是先預建立了一筆訂單),然後再去做減庫存,鎖券操作,當這些操作異常或者失敗時,下單系統就會發出一條廢單訊息,它的下游系統(如促銷,庫存系統)收到這個廢單訊息以後,就會幫我們做回滾的操作,用這樣的方式來解決我們分散式事務的問題。
分散式事務處理——支付回撥為例
-
支付回撥流程中,資金系統回撥交易後會促發訂單狀態更新,減庫存,發券等操作
-
資金作為發起方保證重試,訊息可達,交易以及下游做好等
-
失敗業務進任務重試表,做非同步補償重試
-
消除了二階段提交等分散式事務框架的侵入性影響
還有一個場景就是支付回撥,支付系統在訂單完成付款之後會通知交易系統,交易系統會進行一些列的操作如定單狀態更新,減庫存,發券等,也是一個分散式事務問題。
我們的策略是,當業務失敗的時候這個請求會進入我們的一個失敗補償表,通過不斷的做非同步補償重試(階梯式),保證最終一致性。
單機非同步並行——購物車為例
分析思路
-
購物車是典型 IO 密集型應用
-
程式碼序列執行,同步等待時間較長
-
CPU 利用率低
分析一下,購物車其實本身是一個典型的 IO 密集型的應用,有很多類似的應用都是這樣的,會存在大量的網路 IO 請求。還有一點是我們習慣上程式碼是序列寫的,所以存在很多同步等待時間。
既然每次購物車的查詢都會經過這麼多的節點,那如果兩個節點之間沒有依賴關係,我是不是就可以並行的去搞,分析一下其實每一次查詢都會對應一顆查詢依賴樹,同一層節點之間是沒有依賴關係的,在這一層我們其實是可以去做並行操作的,所以基於這個思路我們當時做了優化,並且效果還挺不錯。
具體的優化就是加入這個概念,去查的時候我們先等一下別的查詢,我們一塊兒去查,最終做一個彙總,查詢結果就出來了,就是這麼一個流程。然後做的效果也不錯,整個 RT 基本上能夠降到一半以上。
預處理 & 快取——營銷計價服務為例
-
使用快取降低 DB 讀壓力
-
儘可能地快取需要的資料
-
DB 資料有變化主動失效快取(非同步,低延遲),減少不一致問題
-
大促高峰前開啟本地快取;做快取預熱,有助於提高快取命中率
-
預處理達到計價介面部分耦合:報名大促活動商品的折扣同步到商品表
預處理跟快取化,其實也是一種比較通用的優化手段。我們採用了多級快取的策略,本地快取+分散式快取。優先讀取本地快取,本地快取沒有就去分散式快取取。分散式快取取不到才去 DB 裡面取。當資料發生變化的時候我們會有一套非同步重新整理快取的系統來及時更新快取裡面的資料。
服務 SLA 保障
SLA: Service Level Agreement,是對服務提供者的要求。SLA 體現在對容器(QPS)、效能(RT)、程度(分佈情況;可用性;出錯率)的約束。提高 SLA 的一些手段如下
-
基礎監控先行,把關鍵指標監控起來
-
依賴治理、邏輯優化:減少不必要的依賴
-
負載均衡;服務分組;限流
-
降級預案、容災、壓測、線上演練
這個是我們內部的一個監控系統,我們會監控每個應用的一些關鍵指標,觀察整個鏈路上的情況。
總結及下一步規劃
總結
-
服務化構架不是一成不變的,是隨著業務不斷髮展而演化
-
沒有最佳的方案,在合適場景下采用合適的方案
目前在做
-
服務治理、SLA 保障系統化
下一步
-
同城/異地雙活