高并發(fā)外賣系統(tǒng)的演進

外賣訂單業(yè)務

外賣訂單業(yè)務是一個需要即時送的業(yè)務,對實時性要求很高。從用戶訂餐到最終送達用戶,一般在1小時內。如果最終送達用戶時間變長,會帶來槽糕的用戶體驗。在1小時內,訂單會快速經過多個階段,直到最終送達用戶。各個階段需要緊密配合,確保訂單順利完成。

下圖是一個用戶視角的訂單流程圖。

從普通用戶的角度來看,一個外賣訂單從下單后,會經歷支付、商家接單、配送、用戶收貨、售后及訂單完成多個階段。以技術的視角來分解的話,每個階段依賴于多個子服務來共同完成,比如下單會依賴于購物車、訂單預覽、確認訂單服務,這些子服務又會依賴于底層基礎系統(tǒng)來完成其功能。

外賣業(yè)務另一個重要特征是一天內訂單量會規(guī)律變化,訂單會集中在中午、晚上兩個“飯點”附近,而其它時間的訂單量較少。這樣,飯點附近系統(tǒng)壓力會相對較大。

下圖是一天內的外賣訂單量分布圖

總結而言,外賣業(yè)務具有如下特征:

流程較長且實時性要求高;

訂單量高且集中。

下面將按時間脈絡為大家講解訂單系統(tǒng)經歷的各個階段、各階段業(yè)務特征、挑戰(zhàn)以及應對之道。

訂單系統(tǒng)雛型

外賣業(yè)務發(fā)展早期,第一目標是要能夠快速驗證業(yè)務的可行性。技術上,我們需要保證架構足夠靈活、快速迭代從而滿足業(yè)務快速試錯的需求。

在這個階段,我們將訂單相關功能組織成模塊,與其它模塊(門店模塊等)一起形成公用jar包,然后各個系統(tǒng)通過引入jar包來使用訂單功能。

早期系統(tǒng)的整體架構圖如下所示:

早期,外賣整體架構簡單、靈活,公共業(yè)務邏輯通過jar包實現(xiàn)后集成到各端應用,應用開發(fā)部署相對簡單。比較適合業(yè)務早期邏輯簡單、業(yè)務量較小、需要快速迭代的情況。但是,隨著業(yè)務邏輯的復雜、業(yè)務量的增長,單應用架構的弊端逐步暴露出來。系統(tǒng)復雜后,大家共用一個大項目進行開發(fā)部署,協(xié)調的成本變高;業(yè)務之間相互影響的問題也逐漸增多。

早期業(yè)務處于不斷試錯、快速變化、快速迭代階段,通過上述架構,我們能緊跟業(yè)務,快速滿足業(yè)務需求。隨著業(yè)務的發(fā)展以及業(yè)務的逐步成熟,我們對系統(tǒng)進行逐步升級,從而更好地支持業(yè)務。

獨立的訂單系統(tǒng)

2014年4月,外賣訂單量達到了10萬單/日,而且訂單量還在持續(xù)增長。這時候,業(yè)務大框架基本成型,業(yè)務在大框架基礎上快速迭代。大家共用一個大項目進行開發(fā)部署,相互影響,協(xié)調成本變高;多個業(yè)務部署于同一VM,相互影響的情況也在增多。

為解決開發(fā)、部署、運行時相互影響的問題。我們將訂單系統(tǒng)進行獨立拆分,從而獨立開發(fā)、部署、運行,避免受其它業(yè)務影響。

系統(tǒng)拆分主要有如下幾個原則:

相關業(yè)務拆分獨立系統(tǒng);

優(yōu)先級一致的業(yè)務拆分獨立系統(tǒng);

拆分系統(tǒng)包括業(yè)務服務和數(shù)據。

基于以上原則,我們將訂單系統(tǒng)進行獨立拆分,所有訂單服務通過RPC接口提供給外部使用。訂單系統(tǒng)內部,我們將功能按優(yōu)先級拆分為不同子系統(tǒng),避免相互影響。訂單系統(tǒng)通過MQ(隊列)消息,通知外部訂單狀態(tài)變更。

獨立拆分后的訂單系統(tǒng)架構如下所示:

其中,最底層是數(shù)據存儲層,訂單相關數(shù)據獨立存儲。訂單服務層,我們按照優(yōu)先級將訂單服務劃分為三個系統(tǒng),分別為交易系統(tǒng)、查詢系統(tǒng)、異步處理系統(tǒng)。

獨立拆分后,可以避免業(yè)務間的相互影響。快速支持業(yè)務迭代需求的同時,保障系統(tǒng)穩(wěn)定性。

高性能、高可用、高穩(wěn)定的訂單系統(tǒng)

訂單系統(tǒng)經過上述獨立拆分后,有效地避免了業(yè)務間的相互干擾,保障迭代速度的同時,保證了系統(tǒng)穩(wěn)定性。這時,我們的訂單量突破百萬,而且還在持續(xù)增長。之前的一些小問題,在訂單量增加后,被放大,進而影響用戶體驗。比如,用戶支付成功后,極端情況下(比如網絡、數(shù)據庫問題)會導致支付成功消息處理失敗,用戶支付成功后依然顯示未支付。訂單量變大后,問題訂單相應增多。我們需要提高系統(tǒng)的可靠性,保證訂單功能穩(wěn)定可用。

另外,隨著訂單量的增長、訂單業(yè)務的復雜,對訂單系統(tǒng)的性能、穩(wěn)定性、可用性等提出了更高的要求。

為了提供更加穩(wěn)定、可靠的訂單服務,我們對拆分后的訂單系統(tǒng)進行進一步升級。下面將分別介紹升級涉及的主要內容。

性能優(yōu)化

系統(tǒng)獨立拆分后,可以方便地對訂單系統(tǒng)進行優(yōu)化升級。我們對獨立拆分后的訂單系統(tǒng)進行了很多的性能優(yōu)化工作,提升服務整體性能,優(yōu)化工作主要涉及如下幾個方面。

異步化

服務所需要處理的工作越少,其性能自然越高。可以通過將部分操作異步化來減少需要同步進行的操作,進而提升服務的性能。異步化有兩種方案。

線程或線程池:將異步操作放在單獨線程中處理,避免阻塞服務線程;

消息異步:異步操作通過接收消息完成。

異步化帶來一個隱患,如何保障異步操作的執(zhí)行。這個場景主要發(fā)生在應用重啟時,對于通過線程或線程池進行的異步化,JVM重啟時,后臺執(zhí)行的異步操作可能尚未完成。這時,需要通過JVM優(yōu)雅關閉來保證異步操作進行完成后,JVM再關閉。通過消息來進行的,消息本身已提供持久化,不受應用重啟影響。

具體到訂單系統(tǒng),我們通過將部分不必同步進行的操作異步化,來提升對外服務接口的性能。不需要立即生效的操作即可以異步進行,比如發(fā)放紅包、PUSH推送、統(tǒng)計等。

以訂單配送PUSH推送為例,將PUSH推送異步化后的處理流程變更如下所示:

PUSH異步化后,線程#1在更新訂單狀態(tài)、發(fā)送消息后立即返回,而不用同步等待PUSH推送完成。而PUSH推送異步在線程#2中完成。

并行化

操作并行化也是提升性能的一大利器,并行化將原本串行的工作并行執(zhí)行,降低整體處理時間。我們對所有訂單服務進行分析,將其中非相互依賴的操作并行化,從而提升整體的響應時間。

以用戶下單為例,第一步是從各個依賴服務獲取信息,包括門店、菜品、用戶信息等。獲取這些信息并不需要相互依賴,故可以將其并行化,并行后的處理流程變更如下所示:

通過將獲取信息并行化,可有效縮短下單時間,提升下單接口性能。

緩存

通過將統(tǒng)計信息進行提前計算后緩存,避免獲取數(shù)據時進行實時計算,從而提升獲取統(tǒng)計數(shù)據的服務性能。比如對于首單、用戶已減免配送費等,通過提前計算后緩存,可以簡化實時獲取數(shù)據邏輯,節(jié)約時間。

以用戶已減免配送費為例,如果需要實時計算,則需要取到用戶所有訂單后,再進行計算,這樣實時計算成本較高。我們通過提前計算,緩存用戶已減免配送費。需要取用戶已減免配送費時,從緩存中取即可,不必實時計算。具體來說,包括如下幾點:

通過緩存保存用戶已減免配送費;

用戶下單時,如果訂單有減免配送費,增加緩存中用戶減免配送費金額(異步進行);

訂單取消時,如果訂單有減免配送費,減少緩存中用戶減免配送費金額(異步進行);

一致性優(yōu)化

訂單系統(tǒng)涉及交易,需要保證數(shù)據的一致性。否則,一旦出現(xiàn)問題,可能會導致訂單不能及時配送、交易金額不對等。

交易一個很重要的特征是其操作具有事務性,訂單系統(tǒng)是一個復雜的分布式系統(tǒng),比如支付涉及訂單系統(tǒng)、支付平臺、支付寶/網銀等第三方。僅通過傳統(tǒng)的數(shù)據庫事務來保障不太可行。對于訂單交易系統(tǒng)的事務性,并不要求嚴格滿足傳統(tǒng)數(shù)據庫事務的ACID性質,只需要最終結果一致即可。針對訂單系統(tǒng)的特征,我們通過如下種方式來保障最終結果的一致性。

重試/冪等

通過延時重試,保證操作最終會最執(zhí)行。比如退款操作,如退款時遇到網絡或支付平臺故障等問題,會延時進行重試,保證退款最終會被完成。重試又會帶來另一個問題,即部分操作重復進行,需要對操作進行冪等處理,保證重試的正確性。

以退款操作為例,加入重試/冪等后的處理流程如下所示:

退款操作首先會檢查是否已經退款,如果已經退款,直接返回。否則,向支付平臺發(fā)起退款,從而保證操作冪等,避免重復操作帶來問題。如果發(fā)起退款失敗(比如網絡或支付平臺故障),會將任務放入延時隊列,稍后重試。否則,直接返回。

通過重試+冪等,可以保證退款操作最終一定會完成。

2PC

2PC是指分布式事務的兩階段提交,通過2PC來保證多個系統(tǒng)的數(shù)據一致性。比如下單過程中,涉及庫存、優(yōu)惠資格等多個資源,下單時會首先預占資源(對應2PC的第一階段),下單失敗后會釋放資源(對應2PC的回滾階段),成功后會使用資源(對應2PC的提交階段)。對于2PC,網上有大量的說明,這里不再繼續(xù)展開。

高可用

分布式系統(tǒng)的可用性由其各個組件的可用性共同決定,要提升分布式系統(tǒng)的可用性,需要綜合提升組成分布式系統(tǒng)的各個組件的可用性。

針對訂單系統(tǒng)而言,其主要組成組件包括三類:存儲層、中間件層、服務層。下面將分層說明訂單系統(tǒng)的可用性。

存儲層

存儲層的組件如MySQL、ES等本身已經實現(xiàn)了高可用,比如MySQL通過主從集群、ES通過分片復制來實現(xiàn)高可用。存儲層的高可用依賴各個存儲組件即可。

中間件層

分布式系統(tǒng)會大量用到各類中間件,比如服務調用框架等,這類中間件一般使用開源產品或由公司基礎平臺提供,本身已具備高可用。

服務層

在分布式系統(tǒng)中,服務間通過相互調用來完成業(yè)務功能,一旦某個服務出現(xiàn)問題,會級聯(lián)影響調用方服務,進而導致系統(tǒng)崩潰。分布式系統(tǒng)中的依賴容災是影響服務高可用的一個重要方面。

依賴容災主要有如下幾個思路:

依賴超時設置;

依賴災備;

依賴降級;

限制依賴使用資源;

訂單系統(tǒng)會依賴多個其它服務,也存在這個問題。當前訂單系統(tǒng)通過同時采用上述四種方法,來避免底層服務出現(xiàn)問題時,影響整體服務。具體實現(xiàn)上,我們采用Hystrix框架來完成依賴容災功能。Hystrix框架采用上述四種方法,有效實現(xiàn)依賴容災。訂單系統(tǒng)依賴容災示意圖如下所示

通過為每個依賴服務設置獨立的線程池、合理的超時時間及出錯時回退方法,有效避免服務出現(xiàn)問題時,級聯(lián)影響,導致整體服務不可用,從而實現(xiàn)服務高可用。

另外,訂單系統(tǒng)服務層都是無狀態(tài)服務,通過集群+多機房部署,可以避免單點問題及機房故障,實現(xiàn)高可用。

小結

上面都是通過架構、技術實現(xiàn)層面來保障訂單系統(tǒng)的性能、穩(wěn)定性、可用性。實際中,有很多的事故是人為原因導致的,除了好的架構、技術實現(xiàn)外,通過規(guī)范、制度來規(guī)避人為事故也是保障性能、穩(wěn)定性、可用性的重要方面。訂單系統(tǒng)通過完善需求review、方案評審、代碼review、測試上線、后續(xù)跟進流程來避免人為因素影響訂單系統(tǒng)穩(wěn)定性。

通過以上措施,我們將訂單系統(tǒng)建設成了一個高性能、高穩(wěn)定、高可用的分布式系統(tǒng)。其中,交易系統(tǒng)tp99為150ms、查詢系統(tǒng)tp99時間為40ms。整體系統(tǒng)可用性為6個9。

可擴展的訂單系統(tǒng)

訂單系統(tǒng)經過上面介紹的整體升級后,已經是一個高性能、高穩(wěn)定、高可用的分布式系統(tǒng)。但是系統(tǒng)的的可擴展性還存在一定問題,部分服務只能通過垂直擴展(增加服務器配置)而不能通過水平擴展(加機器)來進行擴容。但是,服務器配置有上限,導致服務整體容量受到限制。

到2015年5月的時候,這個問題就比較突出了。當時,數(shù)據庫服務器寫接近單機上限。業(yè)務預期還會繼續(xù)快速增長。為保障業(yè)務的快速增長,我們對訂單系統(tǒng)開始進行第二次升級。目標是保證系統(tǒng)有足夠的擴展性,從而支撐業(yè)務的快速發(fā)展。

分布式系統(tǒng)的擴展性依賴于分布式系統(tǒng)中各個組件的可擴展性,針對訂單系統(tǒng)而言,其主要組成組件包括三類:存儲層、中間件層、服務層。下面將分層說明如何提高各層的可擴展性。

存儲層

訂單系統(tǒng)存儲層主要依賴于MySQL持久化、tair/redis cluster緩存。tair/redis cluster緩存本身即提供了很好的擴展性。MySQL可以通過增加從庫來解決讀擴展問題。但是,對于寫MySQL存在單機容量的限制。另外,數(shù)據庫的整體容量受限于單機硬盤的限制。

存儲層的可擴展性改造主要是對MySQL擴展性改造。

分庫分表

寫容量限制是受限于MySQL數(shù)據庫單機處理能力限制。如果能將數(shù)據拆為多份,不同數(shù)據放在不同機器上,就可以方便對容量進行擴展。

對數(shù)據進行拆分一般分為兩步,第一步是分庫,即將不同表放不同庫不同機器上。經過第一步分庫后,容量得到一定提升。但是,分庫并不能解決單表容量超過單機限制的問題,隨著業(yè)務的發(fā)展,訂單系統(tǒng)中的訂單表即遇到了這個問題。

針對訂單表超過單庫容量的問題,需要進行分表操作,即將訂單表數(shù)據進行拆分。單表數(shù)據拆分后,解決了寫的問題,但是如果查詢數(shù)據不在同一個分片,會帶來查詢效率的問題(需要聚合多張表)。由于外賣在線業(yè)務對實時性、性能要求較高。我們針對每個主要的查詢維度均保存一份數(shù)據(每份數(shù)據按查詢維度進行分片),方便查詢。

具體來說,外賣主要涉及三個查詢維度:訂單ID、用戶ID、門店ID。對訂單表分表時,對于一個訂單,我們存三份,分別按照訂單ID、用戶ID、 門店ID以一定規(guī)則存儲在每個維度不同分片中。這樣,可以分散寫壓力,同時,按照訂單ID、用戶ID、門店ID三個維度查詢時,數(shù)據均在一個分片,保證較高的查詢效率。

訂單表分表后,訂單表的存儲架構如下所示:

可以看到,分表后,每個維度共有100張表,分別放在4個庫上面。對于同一個訂單,冗余存儲了三份。未來,隨著業(yè)務發(fā)展,還可以繼續(xù)通過將表分到不同機器上來持續(xù)獲得容量的提升。

分庫分表后,訂單數(shù)據存儲到多個庫多個表中,為應用層查詢帶來一定麻煩,解決分庫分表后的查詢主要有三種方案:

MySQL服務器端支持:目前不支持。

中間件。

應用層。

由于MySQL服務器端不能支持,我們只剩下中間件和應用層兩個方案。中間件方案對應用透明,但是開發(fā)難度相對較大,當時這塊沒有資源去支持。于是,我們采用應用層方案來快速支持。結合應用開發(fā)框架(SPRING+MYBATIS),我們實現(xiàn)了一個輕量級的分庫分表訪問插件,避免將分庫分表邏輯嵌入到業(yè)務代碼。分庫分表插件的實現(xiàn)包括如下幾個要點。

配置文件管理分庫分表配置信息;

JAVA注解說明SQL語句分庫分表信息;

JAVA AOP解析注解+查詢配置文件,獲取數(shù)據源及表名;

MYBATIS動態(tài)替換表名;

SPRING動態(tài)替換數(shù)據源。

通過分庫分表,解決了寫容量擴展問題。但是分表后,會給查詢帶來一定的限制,只能支持主要維度的查詢,其它維度的查詢效率存在問題。

ES搜索

訂單表分表之后,對于ID、用戶ID、門店ID外的查詢(比如按照手機號前綴查詢)存在效率問題。這部分通常是復雜查詢,可以通過全文搜索來支持。在訂單系統(tǒng)中,我們通過ES來解決分表后非分表維度的復雜查詢效率問題。具體來說,使用ES,主要涉及如下幾點。

通過databus將訂單數(shù)據同步到ES。

同步數(shù)據時,通過批量寫入來降低ES寫入壓力。

通過ES的分片機制來支持擴展性。

小結

通過對存儲層的可擴展性改造,使得訂單系統(tǒng)存儲層具有較好的可擴展性。對于中間層的可擴展性與上面提到的中間層可用性一樣,中間層本身已提供解決方案,直接復用即可。對于服務層,訂單系統(tǒng)服務層提供的都是無狀態(tài)服務,對于無狀態(tài)服務,通過增加機器,即可獲得更高的容量,完成擴容。

通過對訂單系統(tǒng)各層可擴展性改造,使得訂單系統(tǒng)具備了較好的可擴展性,能夠支持業(yè)務的持續(xù)發(fā)展,當前,訂單系統(tǒng)已具體千萬單/日的容量。

上面幾部分都是在介紹如何通過架構、技術實現(xiàn)等手段來搭建一個可靠、完善的訂單系統(tǒng)。但是,要保障系統(tǒng)的持續(xù)健康運行,光搭建系統(tǒng)還不夠,運維也是很重要的一環(huán)。

智能運維的訂單系統(tǒng)

早期,對系統(tǒng)及業(yè)務的運維主要是采用人肉的方式,即外部反饋問題,RD通過排查日志等來定位問題。隨著系統(tǒng)的復雜、業(yè)務的增長,問題排查難度不斷加大,同時反饋問題的數(shù)量也在逐步增多。通過人肉方式效率偏低,并不能很好的滿足業(yè)務的需求。

為提升運維效率、降低人力成本,我們對系統(tǒng)及業(yè)務運維進行自動化、智能化改進,改進包括事前、事中、事后措施。

事前措施

事前措施的目的是為提前發(fā)現(xiàn)隱患,提前解決,避免問題惡化。

在事前措施這塊,我們主要采取如下幾個手段:

定期線上壓測:通過線上壓測,準確評估系統(tǒng)容量,提前發(fā)現(xiàn)系統(tǒng)隱患;

周期性系統(tǒng)健康體檢:通過周期檢測CPU利用率、內存利用率、接口QPS、接口TP95、異常數(shù),取消訂單數(shù)等指標是否異常,可以提前發(fā)現(xiàn)提前發(fā)現(xiàn)潛在問題、提前解決;

全鏈路關鍵日志:通過記錄全鏈路關鍵日志,根據日志,自動分析反饋訂單問題原因,給出處理結果,有效提高反饋處理效率。

事中措施

事中措施的目的是為及時發(fā)現(xiàn)問題、快速解決問題。

事中這塊,我們采取的手段包括:

訂單監(jiān)控大盤:實時監(jiān)控訂單業(yè)務指標,異常時報警;

系統(tǒng)監(jiān)控大盤:實時監(jiān)控訂單系統(tǒng)指標,異常時報警;

完善的SOP:報警后,通過標準流程,快速定位問題、解決問題。

事后措施

事后措施是指問題發(fā)生后,分析問題原因,徹底解決。并將相關經驗教訓反哺給事前、事中措施,不斷加強事先、事中措施,爭取盡量提前發(fā)現(xiàn)問題,將問題扼殺在萌芽階段。

通過將之前人肉進行的運維操作自動化、智能化,提升了處理效率、減少了運維的人力投入。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容