蘑菇街電商交易平臺服務(wù)架構(gòu)及改造優(yōu)化歷程(含PPT)
導(dǎo)讀:高可用架構(gòu) 7 月 30 日在上海舉辦了『互聯(lián)網(wǎng)架構(gòu)的基石』專題沙龍,進行了閉門私董會研討及對外開放的四個專題的演講,期望能促進業(yè)界對互聯(lián)網(wǎng)基礎(chǔ)架構(gòu)的建設(shè)及發(fā)展,本文是潘福江分享蘑菇街電商交易系統(tǒng)架構(gòu)。
潘福江,蘑菇街高級研發(fā)工程師,2014 年之前在阿里,搞過電商垂直業(yè)務(wù)平臺建設(shè),也搞過中間件相關(guān)的研發(fā)工作,2015 年加入蘑菇街(現(xiàn)美麗聯(lián)合集團),負(fù)責(zé)蘑菇街交易資金,購物車等電商基礎(chǔ)平臺的服務(wù)化建設(shè)工作。
我來自蘑菇街,蘑菇街是一個主要面向女性用戶的電商平臺,男同胞們可能用的比較少。不過蘑菇街里有大量的模特妹子, ?而且顏值都比較高,建議大家可以下來用用,寫代碼累的時候,可以偷偷打開蘑菇街看看妹子,感覺還是很不錯的。
今天我的主題是蘑菇街交易平臺的服務(wù)架構(gòu),以及在服務(wù)化建設(shè)過程中,我們做的一些改造歷程分享。
蘑菇街導(dǎo)購時期 業(yè)務(wù)結(jié)構(gòu)
蘑菇街是做導(dǎo)購起家的,當(dāng)時所有的業(yè)務(wù)都是基于用戶和內(nèi)容這兩大核心展開。那個時候前臺業(yè)務(wù)主要做的是社交導(dǎo)購,后臺業(yè)務(wù)主要做的是內(nèi)容管理。一句話總結(jié)就是小而美的狀態(tài),業(yè)務(wù)相對來也不是很復(fù)雜。
當(dāng)時的技術(shù)架構(gòu)是典型的創(chuàng)業(yè)型公司技術(shù)架構(gòu)。網(wǎng)站整體是用 PHP 搭建的,系統(tǒng)做了簡單的分層,基礎(chǔ)設(shè)施以現(xiàn)成的開源產(chǎn)品為主。2013 年時蘑菇街做了轉(zhuǎn)型,主要原因那段時間很大一批導(dǎo)購網(wǎng)站遭到了封殺,于是就轉(zhuǎn)型做社會化電商平臺。
社會化電商平臺分兩部分,一部分社會化,我們之前做導(dǎo)購時積累了一些經(jīng)驗。電商是我們之前沒有接觸過的,這塊基本上是從零開始一手建立起來。要做電商平臺,首先就要搭建一個交易平臺。起初比較簡單,我們重寫了一套系統(tǒng),系統(tǒng)結(jié)構(gòu)和之前相比并沒有本質(zhì)上的變化,所有業(yè)務(wù)都寫在一個巨大的工程里面, 中間通過一套代理層和我們的基礎(chǔ)設(shè)施進行交互。
電商轉(zhuǎn)型面臨的問題
業(yè)務(wù)高速發(fā)展,每年保持 3 倍以上的增長(2015 年用戶過億,PV 超過 10億)
用戶購買鏈路大促峰值是日常的百倍(2015 年初最高只支持 400 單/秒)
業(yè)務(wù)異常復(fù)雜,業(yè)務(wù)形態(tài)快速膨脹
歷史包袱沉重,系統(tǒng)耦合非常嚴(yán)
蘑菇街轉(zhuǎn)型到電商平臺以后,業(yè)務(wù)基本上每年以三倍以上的速度增長,這個時候問題開始暴露了。電商平臺在發(fā)展過程中尤其在發(fā)展中期遇到的一些的問題,不僅僅是蘑菇街的,其它平臺可能也會遇到。如系統(tǒng)代碼臃腫、模塊耦合程度高,依賴復(fù)雜,業(yè)務(wù)擴展能力差等。
蘑菇街那個時候主要面臨了幾個問題:
一個是我們業(yè)務(wù)在高速增長,系統(tǒng)容量跟不上,當(dāng)時交易系統(tǒng)只能支持到每秒四百單的容量(大促的時候流量是平時的百倍以上)。
另一個是電商業(yè)務(wù)形態(tài)變的特別快,業(yè)務(wù)支撐不夠靈活,不夠快。
此外還有歷史包袱,系統(tǒng)耦合非常嚴(yán)重。
解決這一系列問題的關(guān)鍵就一個字:“拆”。
系統(tǒng)拆分歷程
DB 垂直拆分
業(yè)務(wù)系統(tǒng)垂直拆分(購物車,下單,資金…)
數(shù)據(jù) & 業(yè)務(wù)模型統(tǒng)一,服務(wù)接口設(shè)計邏輯清晰,粒度合適
基礎(chǔ)業(yè)務(wù)邏輯下沉到服務(wù),Web 層專注表示邏輯和編排
服務(wù)治理
系統(tǒng)拆分——交易購物車為例
以交易購物車的例子來說明下我們的改造過程,以前我們就一個工程,所有的代碼都寫在這,不同的終端或業(yè)務(wù)都有一個不同的模塊代碼在維護。訪問數(shù)據(jù)也比較隨意,各自維護一套數(shù)據(jù)訪問的代碼。因此就有兩個非常頭疼的問題:
一方面由于交易就一個庫,所有內(nèi)容都是放在里面,因此這些隨意散亂的 SQL 可能會冷不丁的給你來個慢查詢,別的業(yè)務(wù)代碼帶來的不穩(wěn)定會相互影響,還很難定位這種“野 SQL”是哪里查過來的,導(dǎo)致我們的 DB 很不穩(wěn)定,對后續(xù)改造非常不利。
另外一個是業(yè)務(wù)支撐方面,產(chǎn)品提一個需求過來,在各種端都要實現(xiàn)一遍,復(fù)用性很差,業(yè)務(wù)支撐非常不靈活,系統(tǒng)毫無擴展性,開發(fā)同學(xué)也是苦不堪言,經(jīng)常加班加點搞,還經(jīng)常搞出一堆 bug。
于是我們就去拆系統(tǒng),到底怎么拆?其實也是有些講究的。
系統(tǒng)拆分的優(yōu)先順序
如果把 DB 比如成一個木桶,各類業(yè)務(wù)就可以比喻為往里面倒的水,一開始往木桶里面倒的水可能并不多,木桶裝的下沒問題。但是隨著業(yè)務(wù)增長,木桶總有一天是會裝不下的。
首先木桶需要足夠大,并且能很方便擴容,這樣才不會有后顧之憂。業(yè)務(wù)量有時候并不好預(yù)測,指不定什么時候量就起來了,如果不把這個底層的木桶做得足夠強大,而優(yōu)先去搞業(yè)務(wù)上的拆分優(yōu)化,那量一旦起來整個系統(tǒng)就歇菜了。
因此DB 是系統(tǒng)拆分的基礎(chǔ),需要優(yōu)先拆分。
DB 拆出來的同時還要關(guān)注穩(wěn)定性,前面也提到,當(dāng)時 SQL 是比較散亂,極易造成 DB 不穩(wěn)定,所以數(shù)據(jù)訪問/模型統(tǒng)一也很關(guān)鍵,我們建了統(tǒng)一的數(shù)據(jù)訪問層。有了這一層之后,后面對 DB 的改造擴容都能夠比較有效的掌控。
基礎(chǔ)的東西都建好了,再來解決業(yè)務(wù)支撐困難這個問題。業(yè)務(wù)模型上需要統(tǒng)一抽象,能夠支持定制擴展。流程改造過程同時也孵化出了SPI 業(yè)務(wù)框架、流程引擎、規(guī)則引擎等這些基礎(chǔ)業(yè)務(wù)框架。在業(yè)務(wù)支撐上做到了靈活可擴展。系統(tǒng)也做了比較合理的分層,每層只需要關(guān)心本層所需關(guān)注的能力即可。
系統(tǒng)拆分成果
交易系統(tǒng)在整體拆分完成以后,公司 SOA 化雛形也基本已經(jīng)形成了,包括基礎(chǔ)服務(wù)化框架、消息中間件、數(shù)據(jù)中間件、配置中心也都落地實施了,此外還孵化了一系列基礎(chǔ)設(shè)施工具,包括監(jiān)控系統(tǒng),調(diào)度系統(tǒng),日志搜集,鏈路跟蹤系統(tǒng)等。
還有一個背景拆分過程中,公司戰(zhàn)略整體往 Java 語言轉(zhuǎn),這個是公司綜合層面考慮,Java的人才尤其是杭州相對比較多,技術(shù)體系也比較成熟,有大牛在能 hold 住問題,當(dāng)時確實 PHP 資源比較少。
容量提升
做了系統(tǒng)拆分改造以后,接下來更多的會去關(guān)注應(yīng)用本身的容量、性能以及穩(wěn)定性方面的事情。我們也在這些方面分別作了一些改造和嘗試。
按業(yè)務(wù)對 DB 進行垂直拆分
讀寫分離,保證讀可以任意擴展
分庫分表,提升中心服務(wù)寫入容量
系統(tǒng)拆分時,已經(jīng)按業(yè)務(wù)把 DB 垂直拆分出來了,并且 DB 也做了讀寫分離(基于 MySQL)。
下面重點介紹一下分庫分表上的改造,當(dāng)時目的主要是為了提升中心服務(wù)的寫入容量,因為當(dāng)時 DB 讀寫分離是單 Master 結(jié)構(gòu),會有一個寫入瓶頸。
以交易創(chuàng)建為例來說明我們分庫分表的歷程,交易創(chuàng)建應(yīng)該算是交易里面最為復(fù)雜的業(yè)務(wù)場景之一。創(chuàng)建一筆訂單的時,會同同時寫入其他很多的數(shù)據(jù),當(dāng)時系統(tǒng)容量大約是每秒能夠處理一千單,DB 單點存在寫入瓶頸,并且寫入過多會造成主從延遲嚴(yán)重。另外 DB 磁盤空間也已經(jīng)突破了 80%,不穩(wěn)定性非常高,有可能隨時會崩掉。
所以我們就決定去做拆分,當(dāng)時的背景是中間件還沒建立起來,沒有分庫分表相關(guān)的組件,于是就決定內(nèi)部先搞起來。
當(dāng)時對比了業(yè)內(nèi)比較流行的一些方案,比如阿里的 TDDL,Cobar,谷歌的 Vitess 等,比較下來發(fā)現(xiàn)這些組件都比較重,接入和使用成本都相對比較高。我們的原則是符合我們業(yè)務(wù)場景下,選一種接入和使用成本都相對簡單的組件。于是我們采取的是最后一種方式,通過 MyBatis Plugin 字節(jié)碼增強的方式實現(xiàn)分庫分表功能,該組件目前已開源:https://github.com/baihui212/tsharding
分庫分表業(yè)界方案對比如下圖:
自研分庫分表組件 TSharding,完成分庫分表
足夠簡單,投入較少資源
支持分庫分表
支持?jǐn)?shù)據(jù)源路由
支持事務(wù)
支持結(jié)果集合并
支持讀寫分離
這個組件叫 TSharding,它的特點是足夠簡單,是符合我們預(yù)期的,支持分庫且分表,支持?jǐn)?shù)據(jù)源路由,支持事務(wù),支持結(jié)果集合并,支持讀寫分離,滿足我們所有的要求。
性能優(yōu)化
我們在性能優(yōu)化上也做了一些嘗試,主要舉下面三個場景:
分布式事務(wù)處理
單機異步并行
預(yù)處理 & 緩存
分布式事務(wù)處理----交易創(chuàng)建為例
優(yōu)化思路:異步消息解耦
交易創(chuàng)建流程中,訂單、券和庫存的狀態(tài)必須要保證一致性
營銷優(yōu)惠券服務(wù)和庫存中心庫存服務(wù),與下單服務(wù)是分開部署的
調(diào)用券/庫存服務(wù)超時/失敗,異步發(fā)消息通知回滾;復(fù)雜性可控
MQ 產(chǎn)生端發(fā)送失敗重試 + 消費接受 ACK 機制保證做種一致
消除了二階段提交等分布式事務(wù)框架的侵入性影響
首先講分布式事務(wù)處理,這里還是以交易創(chuàng)建為例,交易創(chuàng)建過程會跟多個服務(wù)進行交互,并且有些服務(wù)是強依賴,比如扣減庫存,鎖券服務(wù),必須保持一致性。二階段/多階段協(xié)議這種方式很重,當(dāng)時并沒有采納。
我們當(dāng)時想了一個方法是,通過異步消息解耦的方式來解決,具體流程:
下單時先不要急著讓這個訂單暴露出來,我們先創(chuàng)建一筆不可見的訂單(或者可以認(rèn)為是先預(yù)創(chuàng)建了一筆訂單),然后再去做減庫存,鎖券操作,當(dāng)這些操作異常或者失敗時,下單系統(tǒng)就會發(fā)出一條廢單消息,它的下游系統(tǒng)(如促銷,庫存系統(tǒng))收到這個廢單消息以后,就會幫我們做回滾的操作,用這樣的方式來解決我們分布式事務(wù)的問題。
分布式事務(wù)處理——支付回調(diào)為例
支付回調(diào)流程中,資金系統(tǒng)回調(diào)交易后會促發(fā)訂單狀態(tài)更新,減庫存,發(fā)券等操作
資金作為發(fā)起方保證重試,消息可達(dá),交易以及下游做好等
失敗業(yè)務(wù)進任務(wù)重試表,做異步補償重試
消除了二階段提交等分布式事務(wù)框架的侵入性影響
還有一個場景就是支付回調(diào),支付系統(tǒng)在訂單完成付款之后會通知交易系統(tǒng),交易系統(tǒng)會進行一些列的操作如定單狀態(tài)更新,減庫存,發(fā)券等,也是一個分布式事務(wù)問題。
我們的策略是,當(dāng)業(yè)務(wù)失敗的時候這個請求會進入我們的一個失敗補償表,通過不斷的做異步補償重試(階梯式),保證最終一致性。
單機異步并行——購物車為例
分析思路
購物車是典型 IO 密集型應(yīng)用
代碼串行執(zhí)行,同步等待時間較長
CPU利用率低
分析一下,購物車其實本身是一個典型的 IO 密集型的應(yīng)用,有很多類似的應(yīng)用都是這樣的,會存在大量的網(wǎng)絡(luò) IO 請求。還有一點是我們習(xí)慣上代碼是串行寫的,所以存在很多同步等待時間。
既然每次購物車的查詢都會經(jīng)過這么多的節(jié)點,那如果兩個節(jié)點之間沒有依賴關(guān)系,我是不是就可以并行的去搞,分析一下其實每一次查詢都會對應(yīng)一顆查詢依賴樹,同一層節(jié)點之間是沒有依賴關(guān)系的,在這一層我們其實是可以去做并行操作的,所以基于這個思路我們當(dāng)時做了優(yōu)化,并且效果還挺不錯。
具體的優(yōu)化就是加入這個概念,去查的時候我們先等一下別的查詢,我們一塊兒去查,最終做一個匯總,查詢結(jié)果就出來了,就是這么一個流程。然后做的效果也不錯,整個 RT 基本上能夠降到一半以上。
預(yù)處理 & 緩存——營銷計價服務(wù)為例
使用緩存降低 DB 讀壓力
盡可能地緩存需要的數(shù)據(jù)
DB 數(shù)據(jù)有變化主動失效緩存(異步,低延遲),減少不一致問題
大促高峰前開啟本地緩存;做緩存預(yù)熱,有助于提高緩存命中率
預(yù)處理達(dá)到計價接口部分耦合:報名大促活動商品的折扣同步到商品表
預(yù)處理跟緩存化,其實也是一種比較通用的優(yōu)化手段。我們采用了多級緩存的策略,本地緩存+分布式緩存。優(yōu)先讀取本地緩存,本地緩存沒有就去分布式緩存取。分布式緩存取不到才去 DB 里面取。當(dāng)數(shù)據(jù)發(fā)生變化的時候我們會有一套異步刷新緩存的系統(tǒng)來及時更新緩存里面的數(shù)據(jù)。
服務(wù) SLA 保障
SLA: Service Level Agreement,是對服務(wù)提供者的要求。SLA 體現(xiàn)在對容器(QPS)、性能(RT)、程度(分布情況;可用性;出錯率)的約束。提高 SLA 的一些手段如下
基礎(chǔ)監(jiān)控先行,把關(guān)鍵指標(biāo)監(jiān)控起來
依賴治理、邏輯優(yōu)化:減少不必要的依賴
負(fù)載均衡;服務(wù)分組;限流
降級預(yù)案、容災(zāi)、壓測、在線演練
這個是我們內(nèi)部的一個監(jiān)控系統(tǒng),我們會監(jiān)控每個應(yīng)用的一些關(guān)鍵指標(biāo),觀察整個鏈路上的情況。
總結(jié)及下一步規(guī)劃
總結(jié)
服務(wù)化構(gòu)架不是一成不變的,是隨著業(yè)務(wù)不斷發(fā)展而演化
沒有最佳的方案,在合適場景下采用合適的方案
目前在做
服務(wù)治理、SLA 保障系統(tǒng)化
下一步
同城/異地雙活
Q&A
提問:如果消費者收到一條消息,這時候我就告訴系統(tǒng)可以把這個消息刪除了,那我后端在執(zhí)行這條消息的過程當(dāng)中,比如我做一些入庫操作或者其他的操作,但是這個服務(wù)死掉了,那么你們有沒有遇到類似這樣的情況,你們是怎么來處理的?
潘福江:目前沒有,因為其實這是一個需要合作的過程,我們需要下游系統(tǒng)去配合保證,保證業(yè)務(wù) OK 之后才去 ACK 消息,主要是幾個系統(tǒng)之間的協(xié)作問題。
提問:電商大部分分布式事務(wù)解決方案用消息隊列的機制,有沒有一種更通用的解決方式?我們開發(fā)一套分布式組件,比如兩階段協(xié)議,更高效解決這種分布式事務(wù)。
潘福江:分布式事務(wù)問題其實還是看場景的,支付寶(以前在支付寶)有類似的框架,但是比較重,也需要一定的接入成本,需要一定的配合。Case by Case 的分析問題會更好一些,比如有些場景他的一致性要求并沒有那么高,沒有必要采用兩階段協(xié)議這種來處理,主要還是看業(yè)務(wù)場景。
提問:數(shù)據(jù)庫遷移的時候,能做到平滑遷移嗎?因為我看到你之前用的中間件的時候切換過兩次,這個時候肯定遇到一些數(shù)據(jù)庫在線平滑遷移,你是怎么弄的?
潘福江:這個我們內(nèi)部會有一套數(shù)據(jù)同步工具,另外有套開關(guān)系統(tǒng)來完成灰度切換,你可以動態(tài)去推送一些值,到你的應(yīng)用里面,然后動態(tài)的可以把這個值改掉。此外我們這個數(shù)據(jù)同步工具是支持回溯的,遇到緊急情況可以迅速切回,并且把數(shù)據(jù)回溯回來。
提問:你做了一個分庫之后你要把老的庫保留到新的庫里面,因為上去之后你要分步發(fā)布,但你的老庫還在跑,這個時候有人在用你的老庫,但是因為你又在發(fā)布,一半流量已經(jīng)切到新的庫上去了,如果這個時候有人正在修改數(shù)據(jù)怎么辦?
潘福江:上文也提到,老庫和新庫是有建立一個通道的(數(shù)據(jù)同步通道),并且數(shù)據(jù)同步工具一直在工作。老庫的數(shù)據(jù)會實時的同步到新庫,并且我們的灰度是通過開關(guān)系統(tǒng)動態(tài)推送的,實時生效。
提問:在分庫分表的拆分之后,假如有表的關(guān)聯(lián)查詢你們怎么處理的?
潘福江:我們貌似沒有關(guān)聯(lián)查詢,也不推薦,關(guān)聯(lián)查詢對后續(xù)水平拆分十分不利,不利于 DB 的擴展。可以分開查,然后在應(yīng)用層把關(guān)聯(lián)的事情做掉。
提問:如果分開去查的話,性能上會不會受影響呢?
潘福江:性能上可能是會受到一定的影響,會多訪問幾次數(shù)據(jù)庫,但是 DB 擴展性能大大的提升,互聯(lián)網(wǎng)玩的是大數(shù)據(jù),比起這點,DB 擴展性更重要,應(yīng)用層可以有很多的方式去優(yōu)化(比如緩存),如果你用了 JOIN 再去做水平拆分是非常困難的。
提問:你們在拆庫之前,有沒有考慮什么好的方案回退?
潘福江:我們的數(shù)據(jù)同步工具是支持回溯的,出了問題通過開關(guān)系統(tǒng)可以立馬切回,并且能回溯數(shù)據(jù),把影響面降到可控范圍。
相關(guān)閱讀
(點擊標(biāo)題可直接閱讀)
Mercury:唯品會全鏈路應(yīng)用監(jiān)控系統(tǒng)解決方案詳解
同程旅游緩存系統(tǒng)設(shè)計:如何打造Redis時代的完美體系
保證分布式系統(tǒng)數(shù)據(jù)一致性的6種方案(含蘑菇街方案)
10個互聯(lián)網(wǎng)團隊?wèi)?yīng)對高壓的容量評估與高可用體系:私董會1期
本文及本次沙龍相關(guān) PPT 鏈接如下,也可點擊閱讀原文直接下載
http://pan.baidu.com/s/1nvnOEBf
想更多了解高可用架構(gòu)沙龍內(nèi)容,請關(guān)注「ArchNotes」微信公眾號以閱讀后續(xù)文章。關(guān)注公眾號并回復(fù)城市圈可以更及時了解后續(xù)活動信息。轉(zhuǎn)載請注明來自高可用架構(gòu)及包含以下二維碼。
高可用架構(gòu)
改變互聯(lián)網(wǎng)的構(gòu)建方式
長按二維碼 關(guān)注「高可用架構(gòu)」公眾號