1. 程式人生 > >【餓了麽】業務井噴時,訂單系統架構這樣演進

【餓了麽】業務井噴時,訂單系統架構這樣演進

深圳 左右 內容 時間 組件 不知道 要求 技術分享 追溯

本文根據石佳寧在InfoQ舉辦的2016ArchSummit全球架構師(深圳)峰會上的演講整理而成。

老司機簡介

石佳寧,餓了麽後臺支撐研發部負責人,目前任職於餓了麽,現任平臺研發中心-後臺支撐部門負責人,主要負責餓了麽外賣訂單、統一客服系統、BD銷售以及管理工 具、代理商管理平臺等系統的設計和研發工作。

先自我介紹一下,我於2014年加入餓了麽,那時正是餓了麽飛速發展的起始點。我一直從事後臺領域的研發,比如BD系統、客服系統和訂單系統,現在專註交易架構相關的工作。

今天要講的內容主要分為兩大部分。第一部分是在高速增長和愈加復雜的交易場景下,餓了麽訂單的服務架構是如何演進的,究竟是什麽支撐我們的發展。

快速增長下的業務場景

技術分享

脫離具體的場景所有架構演進沒有任何意義。上面這兩個圖表不是餓了麽的數據,是第三方分析整個外賣市場的數據圖。左邊的圖表是從2011年開始,整個O2O市場以及外賣的份額逐年增加。2013年和2014年的時候發生了比較大的飛躍,餓了麽也是在這個時間段訂單量開始猛增。右邊的圖表是用戶註重外賣平臺的因素分布。

從圖中可以看到,用戶很在意配送速度,在意交易的時效性。對於O2O或者餓了麽訂單,交易的要求比傳統電商的高,因為交易一般一兩個小時就結束了。在2014年初,餓了麽訂單量只有日均10萬單,到2014年底超過百萬,這是一個質的飛躍,10萬訂單的量級和百萬訂單的量級的要求非常不一樣。在2015年突破了日均300萬,到今年5月單日峰值突破500萬。

快速發展涉及很多問題。我們是一家創業公司,業務發展非常快,可能準備不是很充分,比如說監控、日誌、告警、框架、消息、數據庫,很多基礎設施還在建設之中。在這個過程中出現一些問題是在所難免的,對系統的要求不是不能掛、不能出問題,而是出了問題要第一時間能恢復。這是整個系統架構的前提。

服務架構的演進

技術分享

圖中所示是訂單的早期架構圖,比較簡單。這個架構在2014年的時候支撐了日均10萬的訂單,是一套很不錯的架構,依然在很多系統中完美運行。但是對於後來發展的場景,它已經曝露問題了,比如業務邏輯嚴重耦合、代碼管理很困難,因為數據庫都在一起,操作變更很難追溯。

更進一步的是,性能的瓶頸只能是靠服務器去硬抗,從物理架構到邏輯架構,都已經成為業務發展的掣肘了。於是,為了業務的發展,我們做了一些演進的工作。

演進工作的核心就是一個字“拆”,跟“拆”對等的就是分治的思想。怎麽拆分呢?面向服務有很多拆分原則。我將拆分過程中最具幫助和指導性的點羅列了以下幾條。

技術分享

  • 第一是明確的定義。之前也確實犯了一些錯誤,為了拆而拆。其實我們需要更明確,什麽才算是一個服務?服務一定具有非常獨立的技術能力或者業務能力,而且一定意義上能夠很抽象。

  • 第二是松耦合。最基本的松耦合就是Customer的消費不依賴於Provider的某一個特定實現,這樣服務器的內部變更不會影響外部消費,消費者可以切換到其他服務能力的提供方,這是最基本的松耦合。還有時間上的松耦合或者位置上的松耦合,我們希望的松耦合是消費方和服務方是可以分離的。

  • 第三是基於領域的認知,這對於整個產品起到非常大的作用。因為當時整個餓了麽所有系統是在一起的,基於領域的認知,在面向用戶的維度和面向商戶的維度做了切分,還有基於交易鏈路做了切分。

  • 第四是單一職責和關註分離。簡單說,我們希望一個服務或者一個模塊擁有單一的能力,而不是承擔過多的職責,否則責任不清晰,導致能力也不清晰。

  • 最後一點是可被驗證的結果。在訂單拆分的過程中我們犯了一些錯誤,當時認為這樣拆分是沒有問題的,但是過一、兩個月,並沒有帶來效率和能力的提升,反而是跨團隊的要求越來越多,能力要求也越來越多。這時候可能是拆錯了。如果是一個好的拆分一定有利於發展,拆分之後的發展是更迅速的。

技術分享

基於這幾條原則,我們對餓了麽的整體服務做拆分之後,如上圖所示,架構就有了一些變化,看起來跟剛才架構區別不大。把Order Service做了分離。當時拆分雖然比較垂直,但是用戶、商戶、金融、訂單等還是有一些橫向交互。

一個接口有一個非常明確的Owner,一個表、一個庫也能保證僅有單一的操作方,讓我感受比較直接的是,為服務的治理奠定了基礎,以後可以針對某項特定業務做一些降級、熔斷,以及單獨的監控。拆分實際上是讓各自模塊的掌控力變得更強了,對業務起到更好的支撐作用。

技術分享

這時每個部門或者每個團隊都負責自己獨立的領域,代碼和數據都拆分完畢是不是就可以了?但是後來發現好像還不對。因為雖然大的領域上確實已經幹凈了,但是在小的領域上依然問題很多,訂單並不僅僅只有一張表,一個單一的模塊,其實還有很多復雜的內容。

在一些技術工作上,這些問題曝露得並不是那麽明顯,那時候大家對於一些領域認知或者業務邊界的認識還是模糊的,沒有人界定這些。但是當更進一步地去發展一個領域的時候,還是會有職責不清晰或者能力模糊的地方。我們思考,還要基於業務進行更細膩的規劃。

於是我們把訂單本身做了一些業務層次的拆分,拆分之前首先要確認訂單到底在整個系統中,尤其是交易系統、O2O系統中承擔什麽角色,擔負什麽職責。

在這個思考過程中,我們的認知大概是以下四點。

第一,訂單是整個交易鏈路的核心,圍繞了一些相關服務,比如金額計算服務、催單服務、售中異常服務等,我們希望這些服務之間有明確的區別。

第二,訂單實時處理是整個鏈路的中心,我們將這個過程定義得盡量簡潔。一筆交易中,訂單被推進得越復雜,說明系統設計得越復雜,出問題的概率也會越高。所以我們希望訂單核心流程非常簡單、輕薄,把復雜的東西剝離出來,把簡單和復雜明確成兩個部分。

第三,考慮到交易的時效性和異常場景越來越復雜,將交易分成正向交易流程和逆向交易流程兩個部分。正向交易流程,99%的訂單會根據這個流程走完生命周期;逆向交易流程,比如說退單要求時效性比較低,處理會牽扯多方業務可能很復雜,所以通過一個逆向的交易流程來解決。

第四,能夠在功能和業務上獨立的部分,盡可能抽象為單獨的模塊或服務。簡單來說,比如催單的服務,它其實對交易鏈路無法起到推進作用,它只是一個動作或者附帶服務,我們把它單獨抽象出來,為後面的發展做出鋪墊。

基於這些之後,我們對訂單進行完整的認知,對訂單的服務架構和業務架構做成圖中的樣子,大概是三層。下面一層是基本數據;中間層是正向逆向的流程、最核心的狀態和最關聯的交易鏈上耦合的服務;上層是用戶服務、商戶服務,包括跟交易鏈相關的,比如餓了麽最近推出的“準時達”的服務。

技術分享

我們同時對其他服務模塊也做了演進。一些是之前設計的不合理,如圖所示是當時緩存服務的邏輯架構,節點比較多。簡單解釋一下最初的做法: 提交訂單的時候清除緩存,獲取訂單的時候如果沒有緩存的話,會通過消息機制來更新緩存。中間還有一個Replicator,起到重復合並的作用。

後來我們發現,本來可以輕量級實現的內容,但是用了相對復雜的實現,鏈路長,組件多,受網絡影響非常大。一旦一個節點緩存數據不一致,感知會比較困難,尤其是業務體量大的時候。

業務體量小的時候同時處理的量並不多,問題曝露並不明顯,但是體量變大的時候,這個設計立刻帶來很多困擾。所以我們對緩存做了簡化,就是把不必要的內容砍掉,做一個最基本的緩存服務。

這是一個最基本的緩存的套路,在數據庫更精準的情況下更新緩存,如果從DB獲取不到就從緩存獲取。這個架構雖然簡單了,但是效率比之前高很多,之前數據庫和緩存之間延遲在200毫秒左右,而這個簡單實現延遲控制在10毫秒以內。

技術分享

之前訂單最大的瓶頸是在數據庫,我們主要做了DAL中間層組件。圖中這個中間件對我們影響非常大,日均300萬單的時候數據庫量比較大,引入DAL中間件做什麽呢?有幾個作用:數據庫管理和負載均衡以及讀寫分離,水平分表對用戶和商戶兩個維度做評估,為用戶存儲至少半年以上的數據。解決了數據庫的瓶頸,系統整體負載能力提升了很多。

技術分享

這張圖說明了訂單具體改造的時候DAL中間件起的作用,有讀寫分離端口、綁定主庫端口、水平分表、限流削峰以及負載均衡等功能。

技術分享

監控和告警的峰值非常明顯,午間和晚間兩個高峰,其他時間流量相對平緩。下面主要講三個部分。

第一,對於訂單而言,吞吐量是最需要重點關註的指標。一開始對業務指標的感知並不是特別清晰,就在某一個接口耗費了很多時間。後來發現一些很小BD的問題不太容易從小接口感知到,但是從業務方面感知就比較明顯,所以就更多關註業務指標的控制。

第二,通常我們重視系統指標,而容易忽視業務指標,其實業務指標更能反映出隱晦的問題。

第三,目前我們致力於基於監控和數據學習的過載保護和業務自動降級。雖然現在還沒有完全做好,但是已經能感覺到一些效果。如果商戶長時間不接單,用戶會自動取消訂單,自動取消功能的開關目前是人工控制的,我們更希望是系統來控制。

比如說有大量訂單取消了,有可能是接單功能出了問題,就需要臨時關閉這個功能,如果還是依靠人來做,往往已經來不及,這時候就應該基於數據的學習,讓系統自動降級這個功能。

技術分享

當做完這一切,訂單的架構就變成了上面這個樣子。我們把整個Service集群做了分組,有面向用戶的、面向商戶的,還有物流和其他方面的。

Design for failure

技術分享

就訂單系統而言主要有以下四個內容。第一是消息廣播補償,第二是主流程補償,第三是災備,第四是隨機故障測試系統。

技術分享

首先是消息廣播補償。對於訂單來說,MQ是非常核心的基礎組件,如果它出現問題,一些訂單處理就會受影響。為了避免這種情況發生,我們做了一個補償的內容,這個補償其實很簡單,就是在訂單狀態發生消息變化的時候,我們會同時落一份消息數據,目前會存儲最近一小時的消息。

如果MQ系統或者集群當前有問題或者抖動,消息廣播補償可以起到一個備線的作用。消息廣播補償不能應付所有問題,但是對於訂單系統的穩定和健壯而言還是非常有用的。

第二是主流程的補償。什麽是主流程?就是交易的正向流程。99%的交易都會是正向的,就是下單、付款,順利吃飯。在這個過程中,只要用戶有通過餓了麽吃飯的意向,就盡全力一定讓他完成最終的交易,不要因為系統的原因影響到他。

主流程主要是針對鏈路本身出問題的情況,以最大程度保證交易的進行,也是對主要鏈路的保護。

比如有一次出現這個問題:用戶已經支付過了,但訂單沒有感受到這個結果,訂單顯示還在待支付,當時支付服務本應該把結果推送過來,訂單就可以繼續往前走,但是系統在那裏卡住了,這對用戶就是比較差的體驗。

所以後來我們做了主流程的補償,以確保交易的信息鏈路一直完整。我們的原則是,對訂單的各個狀態變更進行推送或拉取,保證最終的一致性,鏈路和介質要獨立於原流程。我們從兩個方面來解決這個問題。

在部署方面,把提供補償功能的服務和主服務分開部署,依賴的服務也需要使用獨立實例,以保證高可用性。在效果方面,用戶支付成功前的所有信息都應該盡量入庫,可以對支付、待接單、接單等一系列環節都可以做補償。

技術分享

這是主流程補償的圖,最大的關聯方就是支付和商戶,支付就是代表用戶。商戶有推送訂單信息,支付也有推送訂單信息,如果出現問題,補償服務可以拉取結果,訂單甚至可以自動接單。這個補償經過多次的演變,目前依然在運作,對於一些比較特殊的情況還是很有用的,可以在第一時間處理問題,保證交易的完成。

技術分享

第三是災備。目前訂單系統做了一個比較簡單的災備,就是兩個機房的切換。切換的時候是全流量切換的,我們會把流量從A機房切到B機房。訂單的主要操作是在切換的過程中要進行修復數據。

比如,一些訂單開始是在A機房,被切換到B機房去操作,這就可能會造成兩個機群數據不一致的情況,所以會專門對信息做補全,當一筆交易切換到另一個機房後,我們要確保短時間內將數據對比並修復完成,當然主要還是確保數據最終一致。

技術分享

第四是隨機故障測試系統。左圖是Netflix的猴子家族,右圖是我們做的Kennel系統,一個是猴子窩,一個是狗窩。大家對猴子家族了解嗎?Netflix現在幾乎把所有內容都部署在雲上,對系統和架構的要求很高,他們可以隨時破壞一些節點,以測試是否能依然為用戶提供服務。

我們也參考他們的做法,有很大的啟發,避免失敗最好的辦法就是經常失敗。餓了麽的發展速度比非常快,技術還不完善,設計也會有缺口。我個人覺得,一個好的系統或者好的設計不是一開始被大牛設計出來的,一定是隨著發展和演進逐漸被叠代出來的。

參考了Netflix的猴子家族,我們研發了自己的Kennel系統。猴子家族主要是針對節點的攻擊,我們的Kennel主要是對網絡、內存等做了調整,還結合自己的服務,對應用和接口也做了一些攻擊。攻擊分兩部分。

第一部分是物理層面的,我們可以對指定節點IO做攻擊,或者把CPU打到很高;對於服務和接口而言,可以把某個接口固定增加500毫秒或者更要的響應延遲,這樣做的目的是什麽?在整個鏈路中,我們希望架構設計或者節點都是高可用的,高可用就需要被測試,通過大量的測試人為攻擊節點或者服務,來看預先設計好的那些措施或者補償的能力是不是真的有用。

整個Kennel的設計是,首先會有一個控制中心來做總的調度,配置模塊可以配置各種計劃,可以控制CPU或者網絡丟包等,可以設置在每周六8-10am的某個時間點攻擊系統十五分鐘。它還有一些操作模塊,比如執行計劃模塊、任務執行模塊、節點管理模塊、執行記錄模塊等。

技術分享

Kennel有四個主要的作用。

首先,幫助我們發現鏈路中隱蔽的缺陷,將小概率事件放大。比如說緩存不一致的問題,之前極少出現,一旦出現之後,處理手段比較缺乏,那就可以通過Kennel來模擬。網絡的抖動是很隨機的,那麽Kennel可以在某個時間段專門進行模擬,把小概率事件放大。如果懷疑某個地方出了問題,可以通過它來測試是不是真的能查出問題。

第二,重大功能可以在發布之前通過其進行測試,迫使你更深入地設計和編碼。通過模擬流量或者線上流量回放,來檢驗系統運行是否如你設計那樣工作,比如監控的曲線或者告警以及相關服務之間的依賴等。

第三,我們做了很多失敗的準備和設計,要看到底會不會起作用、起多大作用。可以通過Kennel進行校驗,在某個時間通過隨機手段攻擊相關服務,服務方不知道具體的攻擊內容,這時原本設定的監控告警,降級熔斷等措施有沒有及時起作用就是一個很好的校驗。同時還可以檢驗之前準備的容錯或者補償措施是否能按照預期工作。

第四,需要驗證FailOver的設計,只有驗證通過才可以依靠。所有的設計都是經歷了一次一次的失敗,一些設計原以為有用,但是真實問題發生時並沒有起到作用。真正有意義的FailOver設計一定是經過驗證的。

【餓了麽】業務井噴時,訂單系統架構這樣演進