Uber:面向領域的微服務架構

前言

近來,一些關於面向服務架構的話題,特別是針對微服務架構的弊端這個話題上進行了大量的討論。雖然在幾年前,微服務架構受到很多人的青睞,因爲它們提供了許多好處,如獨立部署的靈活性、明確的所有權、系統穩定性的改善以及更好的分離問題等優點。但是不久,就開始有人吐槽微服務會大幅增加系統複雜性,有時甚至連一些簡單的功能都難以構建。

隨着 Uber 發展,我們目前擁有了大約 2200 個關鍵的微服務,並且也親身經歷了這些權衡。在過去的兩年裏,Uber 一直在試圖降低微服務的複雜性的同時仍然保持着微服務架構的優勢。我們希望通過這篇博文介紹我們對微服務架構的通用方法,我們稱之爲 “面向領域的微服務架構”(DOMA)。

由於這些缺點,近年來也有一些批評微服務架構的聲音,但是卻很少有人主張徹底拒絕微服務架構。運營收益太重要了,而且似乎沒有有效的替代方案。我們介紹 DOMA 的目的是爲了給那些希望降低整體系統複雜性,同時又保持微服務架構相關靈活性的組織提供一些經驗建議。

這篇文章主要解釋了什麼是 DOMA,以及 Uber 採用這種架構的原因,它對平臺和產品團隊帶來哪些好處。最後,給想要採用這種架構的團隊一些建議。

什麼是微服務

動機

在 Uber,我們也採用了微服務架構,因爲我們(大約在 2012-2013 年)主要有兩個單體服務,遇到了很多通過微服務來解決的運營問題。

隨着 Uber 從 10 多個工程師發展到 100 多個工程師,多個團隊擁有技術棧的碎片時,這種單一的架構將團隊的命運捆綁在一起,使得獨立運作變得困難。

因此,我們採用了微服務架構。最終,我們的系統變得更加靈活,這使得團隊能夠更加自主。

毫不誇張地說,如果沒有微服務架構,Uber 不可能達到今天所保持的規模和執行質量。

然而,隨着公司規模的進一步擴大,從 100 多名工程師到 1000 多名工程師,我們開始注意到一系列與系統複雜性大大增加的相關問題。在微服務架構下,人們用單一的整體代碼庫換取了黑盒,黑盒的功能隨時可能發生變化,很容易造成意外情況。

例如,工程師們不得不通過 12 個不同團隊大約 50 個服務來調查問題的根本原因。

理解服務之間的依賴關係可能會變得相當困難,因爲服務之間的調用可能會深入許多層。第 n 個依賴關係的延遲峯值可能會導致上游的一連串問題。如果沒有合適的工具,就不可能看到實際發生的情況,這讓調試變得困難。

Jaeger 於 2018 年中發佈的 Uber 微服務架構

爲了構建一個簡單的功能,工程師往往需要跨多個服務工作,所有這些服務都由不同的個人和團隊所擁有。這就需要跨部門跨團隊的合作,在會議、設計和代碼審查上花費時間。由於團隊在彼此的服務中構建代碼,修改彼此的數據模型,甚至代表服務所有者執行部署,早期對服務所有權的明確界限劃分受到了影響。網絡化的單體可能會形成,看似獨立的服務都必須一起部署才能安全地執行任何變更。

大約在 2018 年 Uber 的複雜流程示例,在 DOMA 之前需要 10 個接觸點才能進行簡單集成。

這樣所帶來的結果就是開發進度變慢、服務所屬不穩定、遷移更困難等。對於已經採用微服務架構的企業來說,已經沒有回頭路了。這就變成了 “有了它們不能活,沒有它們不能活”。

面向領域的微服務架構

Uber 的措施

Uber 域代表一個或多個與邏輯功能分組綁定的微服務的集合。設計域時常見的問題是 “域應該有多大?” 有些域可以包含數十個服務,有些域只能包含單個服務。重要的任務是仔細考慮每個集合的邏輯角色。例如,我們的地圖搜索服務構成一個域,票價服務是一個域,匹配平臺(匹配騎手和駕駛員)是一個域。這些也不總是遵循公司的組織結構。Uber Maps 組織本身分爲三個域,在三個不同的網關後面有 80 個微服務。

層設計

層設計回答了 “什麼服務可以調用其他什麼服務?” 的問題。在 Uber 的微服務架構中,我們可以將層設計視爲“規模化的關注點分離”,或者,我們可以將層設計視爲“規模化的依賴管理”。

層設計描述了一種機制,用於考慮 Uber 的故障影響範圍和跨服務依賴的產品特異性。當域從底層移到頂層時,它們在中斷的情況下會影響較少的服務,並代表更多特定的產品使用案例。相反,底層的功能具有更多的依存關係,因此趨向於具有更大的影響半徑,並代表了更通用的業務功能集。下圖說明了此概念。

可以將頂層視爲具體的用戶體驗(例如移動功能),將底層視爲通用的業務功能(例如帳戶管理或市場行程)。層僅取決於其下的層,這爲我們提供了一種有用的啓發式方法,可以思考影響範圍和區域集成等問題。

值得注意的是,功能經常會從這個圖表中 "向下" 移動,從具體到更普遍。可以想象,一個簡單的功能,隨着需求的變多,最終會變成越來越多的平臺。事實上,這種向下遷移是意料之中的,Uber 的許多核心業務平臺一開始都是針對騎手或司機的功能,隨着我們開發出更多的業務線,它們也有了更多的依賴性,就會變得越來越通用(比如 Uber Eats 或 Uber Freight)。

在 Uber 內部,我們建立了以下五個層次。

每層代表着越來越具體的功能分組,並且影響半徑越來越小(或者換句話說,更少的組件取決於該層中的功能)。

網關

在微服務架構中相信大家對 “API 網關” 這個術語並不陌生。而在 DOMA 中我們的定義的網關其實與大家所熟知的 “API 網關” 的概念相差無幾,只是我們傾向於將網關專門視爲進入基礎服務集合(稱爲域)的單個入口點。網關的成功取決於 API 設計的成功。

由於上游使用者僅在單一服務上運行,因此網關在遷移,服務發現以及整體系統複雜度方面提供了許多好處,上游服務僅需一個依賴項,而不是對域中可能存在的幾個下游服務的依賴。如果我們從面向對象設計的角度考慮網關,那麼它們就是接口定義,它使我們能夠根據底層 “實現”(在本例中爲底層微服務的集合)做我們想做的任何事情。

擴展

擴展表示一種擴展域的機制。擴展的基本定義是,它提供了一種擴展基礎服務功能的機制,而無需更改該服務的實際實現,也不會影響其整體可靠性。在 Uber,我們提供了兩種不同的擴展模型:邏輯擴展和數據擴展。擴展的概念使我們能夠將架構擴展到能夠獨立工作的多個團隊。

邏輯擴展

邏輯擴展提供了一種擴展服務的底層邏輯機制。對於邏輯擴展,我們使用提供程序或插件模式的變體,其接口是以服務爲基礎定義的。這樣一來使得擴展團隊可以在不修改底層平臺核心代碼的情況下,以接口驅動的方式實現擴展邏輯。

例如,一個驅動上線。通常,我們會進行各種檢查以確保允許驅動上線(安全檢查,合規性等)。這些都由一個單獨的團隊擁有。一種實現方法是讓每個團隊在同一端點編寫邏輯,但這可能會增加複雜性。每次檢查都需要自定義且完全不相關的邏輯。

對於邏輯擴展,“上線” 端點將定義一個接口,他們希望每個擴展都符合預定義的請求類型和響應。每個團隊都將註冊一個負責執行此邏輯的擴展。在這種情況下,他們可能簡單地獲取一些關於驅動程序的上下文況,然後返回布爾值,來判斷驅動程序是否可以上線。“上線” 端點將簡單地遍歷這些響應,並確定它們其中是否有問題。

這就將核心代碼與每個擴展解耦,並提供了擴展之間的隔離,它不知道其他邏輯在執行什麼。圍繞這一點,就能很容易建立更多的功能,比如可觀察性或者是特徵標誌等。

數據擴展

數據擴展提供了一種將任意數據附加到接口的機制,來避免核心平臺數據模型中的臃腫。對於數據擴展,我們利用 Protobuf 的 Any 功能,這樣團隊可以將任意數據添加到請求中。服務通常會存儲這些數據或將其傳遞給邏輯擴展,這樣核心平臺就永遠不會負責反序列化(從而 "知道")這個任意上下文。Protobuf 的任何實現都會有一些基礎設施開銷,以換取更強的類型化。爲了更簡單的實現,我們可以直接使用 JSON 字符串來表示任意數據。

自定義擴展

在邏輯和數據擴展之外,Uber 的很多團隊都推出了自己適合自己領域的擴展模式。例如,與我們的展示架構綁定的很多集成都使用了基於 DAG 的任務執行邏輯。

效益

Uber 幾乎每個主要領域都在一定程度上受到了 DOMA 的影響。在過去的一年裏,我們主要關注 Uber 的業務層,它爲我們的各個業務線提供了通用的邏輯。

DOMA 在 Uber 還很年輕,我們很高興能在未來分享更多的數據和我們架構的深入案例。不過,在簡化開發人員體驗和降低整體系統複雜度方面,早期的跡象是非常積極的。

產品與平臺

DOMA 是 Uber 整個產品和平臺團隊達成共識的結果。平臺支持成本往往下降了一個數量級。產品團隊從護欄和加速開發中獲益。

例如,我們擴展架構的一個早期平臺消費者通過採用擴展架構,減少了代碼審查、規劃和消費者學習曲線的時間,能夠將一個新功能的優先級和集成時間從三天下降到三個小時。

降低複雜度

以前產品團隊要利用一個領域,需要調用許多下游服務,現在只需要調用一個服務。通過減少入駐新功能的接觸點數量,平臺能夠將入駐時間縮短 25-50%。此外,我們能夠將 2200 個微服務劃分爲 70 個域。其中大約有 50% 已經實施,其中大部分有一些未來採用的計劃。

未來的遷移

在 Uber,我們計算過微服務的半衰期是 1.5 年,也就是說每 1.5 年我們就有 50% 的微服務流失。如果沒有網關,微服務架構很容易因爲這種流失而陷入 “遷移地獄”。不斷變化的微服務需要不斷進行上游遷移。網關使團隊能夠避免對底層領域服務的依賴性,這意味着這些服務可以在不強制進行上游遷移的情況下發生變化。

Uber 在去年最大的兩次平臺重寫都發生在網關背後。這些平臺有數百個依賴於它們的服務,這些服務將不得不遷移現有的平臺。在這些情況下,遷移的成本會非常高,使得的平臺重寫變得不可行。

新的業務和產品線

事實證明,使用 DOMA 設計的平臺可擴展性更強,也更容易維護。Uber 的大多數團隊之所以採用 DOMA,是因爲支持新業務線的成本太高。

一些建議

本節爲可能想採用 DOMA 的公司提供一些實用的建議。這裏的指導原則是,根據我們的經驗,一個成熟的、經過深思熟慮的微服務架構源於在正確的時間向正確的方向悄悄推敲。現實情況是,對於一個人的整個微服務架構來說,真正的 “重寫” 是永遠不可能的。

因此,我們認爲微服務架構的演進更像是 “修剪樹籬”,使其最終正確成長,而不是自上而下或一次性的架構(或重新架構)工作。這是一個動態和漸進的過程。

創業公司

驅動性的問題應該是 “我們應該在什麼時候採用微服務架構?” 和“它對我們的組織有意義嗎?”正如我們在上面所看到的那樣,雖然微服務爲擁有大量工程師的組織提供了運營上的好處,但這也換來了複雜性的增加,會使功能的構建更加困難。

在小型公司中,運營效益可能無法抵消架構複雜性的增加。此外,微服務架構通常需要專門的工程資源來支持,這對於早期階段的公司來說可能超出了預算,否則從優先級的角度來看是次優的。

考慮到這一點,在一段時間內完全暫緩採用微服務也不是沒有道理的。如果一個組織真的選擇採用微服務,就應該考慮 “微服務作爲大型分佈式應用” 的類比,以及想要構建的微服務之間的關注點分離。另外,要認識到,第一批微服務很可能是最重要的,也是持續時間最長的,因爲它們真正描述了業務的核心。

**中等公司
**

一旦公司的規模達到中等,有了多個團隊,不同的功能和平臺之間的關注點明確分離變得朦朧,微服務架構就會變得更加明顯有用。

在這個階段,人們可以開始考慮微服務之間的層次結構。依賴性管理可能會變得更加重要,因爲一些服務開始對業務運營變得更加明顯的關鍵,越來越多的團隊依賴這些服務。

早期對平臺化的投資可能會在未來的道路上得到回報。如果能夠創建完全產品不可知的業務平臺,避免核心平臺服務中任意的產品邏輯,這裏就有可能避免技術債務。此時採用擴展來實現這一目標可能是有意義的。

鑑於微服務的數量可能還相當少,將它們集中在一起可能沒有意義。不過,這裏值得注意的是,在 Uber 的 DOMA 實現背景下,一個領域可以包含一個服務,所以用 “面向領域” 的方式來思考可能還是有用的。

大型公司

規模較大的工程組織可能有數百名工程師和微服務以及多個依賴關係。這時 DOMA 就達到了它的全部作用。很可能會有明顯的微服務集羣,這些集羣可以很容易地歸爲域,在它們前面有一個網關。遺留服務往往開始需要重構或重寫,然後進行遷移,這意味着如果網關已經到位的話,很快就會開始在遷移的便利性方面提供價值。

明確的層次結構也將變得越來越重要,一些服務將作爲 “產品” 服務來運行,以實現特定的功能或功能分組,而其他服務將越來越多地支持多個產品,並被認爲是“平臺”。現階段關鍵是要保持任意產品邏輯與平臺的脫鉤,這樣才能避免給平臺團隊帶來沉重的運營負擔以及整個系統的不穩定。

最後的感想

隨着 Uber 越來越多的團隊來採用 DOMA,我們仍在積極地進化 DOMA。DOMA 的關鍵洞察力在於,微服務架構實際上只是一個大型的分佈式程序,你可以將同樣的原則應用於它的演進,就像你應用於任何軟件一樣。DOMA 只是一種在實踐中思考這些原則的方法。我們希望其他人覺得它有用,我們也期待着反饋。

原文鏈接:https://eng.uber.com/microservice-architecture/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/aB_4MbhiCzfTGNJIJCIW7Q