餓了麼:業務井噴時訂單系統架構的演進

摘要:餓了麼是一家創業公司,業務發展很是快,可能準備不是很充分,好比說監控、日誌、告警、框架、消息、數據庫,不少基礎設施還在建設之中。在這個過程當中出現一些問題是在所不免的,對系統的要求不是不能掛、不能出問題,而是出了問題要第一時間能恢復。數據庫

  關於它的服務架構的演進:緩存

 

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

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

演進工做的核心就是一個字「拆」,跟「拆」對等的就是分治的思想。怎麼拆分呢?面向服務有不少拆分原則。我將拆分過程當中最具幫助和指導性的點羅列了如下幾條:架構

1 明確的定義  以前也確實犯了一些錯誤,爲了拆而拆。其實咱們須要更明確,什麼纔算是一個服務?服務必定具備很是獨立的技術能力或者業務能力,並且必定意義上可以很抽象負載均衡

2鬆耦合  最基本的鬆耦合就是Customer的消費不依賴於Provider的某一個特定實現,這樣服務器的內部變動不會影響外部消費,消費者能夠切換到其餘服務能力的提供方,這是最基本的鬆耦合。還有時間上的鬆耦合或者位置上的鬆耦合,咱們但願的鬆耦合是消費方和服務方是能夠分離的框架

3基於領域認知 這對於整個產品起到很是大的做用。由於當時整個餓了麼全部系統是在一塊兒的,基於領域的認知,在面向用戶的維度和麪向商戶的維度作了切分,還有基於交易鏈路作了切分ide

4單一職責與關注分離   簡單說,咱們但願一個服務或者一個模塊擁有單一的能力,而不是承擔過多的職責,不然責任不清晰,致使能力也不清晰。性能

5可被驗證的結果 在訂單拆分的過程當中咱們犯了一些錯誤,當時認爲這樣拆分是沒有問題的,可是過1、兩個月,並無帶來效率和能力的提高,反而是跨團隊的要求愈來愈多,能力要求也愈來愈多。這時候多是拆錯了。若是是一個好的拆分必定有利於發展,拆分以後的發展是更迅速的。學習

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

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

這時每一個部門或者每一個團隊都負責本身獨立的領域,代碼和數據都拆分完畢是否是就能夠了?可是後來發現好像還不對。由於雖然大的領域上確實已經乾淨了,可是在小的領域上依然問題不少,訂單並不只僅只有一張表,一個單一的模塊,其實還有不少複雜的內容。

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

因而咱們把訂單自己作了一些業務層次的拆分,拆分以前首先要確認訂單到底在整個系統中,尤爲是交易系統、O2O系統中承擔什麼角色,擔負什麼職責。

在這個思考過程當中,咱們的認知大概是如下四點

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

第二,訂單實時處理是整個鏈路的中心,咱們將這個過程定義得儘可能簡潔。一筆交易中,訂單被推動得越複雜,說明系統設計得越複雜,出問題的機率也會越高。因此咱們但願訂單核心流程很是簡單、輕薄,把複雜的東西剝離出來,把簡單和複雜明確成兩個部分。

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

第四,可以在功能和業務上獨立的部分,儘量抽象爲單獨的模塊或服務。簡單來講,好比催單的服務,它其實對交易鏈路沒法起到推動做用,它只是一個動做或者附帶服務,咱們把它單獨抽象出來,爲後面的發展作出鋪墊。

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

 

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

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

業務體量小的時候同時處理的量並很少,問題曝露並不明顯,可是體量變大的時候,這個設計馬上帶來不少困擾。因此咱們對緩存作了簡化,就是把沒必要要的內容砍掉,作一個最基本的緩存服務。

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

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

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

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

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

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

第三,目前咱們致力於基於監控和數據學習的過載保護和業務自動降級。雖然如今尚未徹底作好,可是已經能感受到一些效果。若是商戶長時間不接單,用戶會自動取消訂單,自動取消功能的開關目前是人工控制的,咱們更但願是系統來控制。

好比說有大量訂單取消了,有多是接單功能出了問題,就須要臨時關閉這個功能,若是仍是依靠人來作,每每已經來不及,這時候就應該基於數據的學習,讓系統自動降級這個功能。

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

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

首先是消息廣播補償。對於訂單來講,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設計必定是通過驗證的。

相關文章
相關標籤/搜索