Uber的API生命週期管理平臺邊緣閘道器(Edge Gateway)的設計實踐

peida發表於2020-09-16

設計邊緣閘道器(Edge Gateway),一個高可用和高可擴充套件的自助服務閘道器,用於配置、管理和監控 Uber 每個業務領域的 API。

Uber 的 API 閘道器的演進

2014 年 10 月,優步開始了規模之旅,最終將成為該公司最令人印象深刻的增長階段之一。隨著時間的推移,我們每個月都在以非線性方式擴大我們的工程團隊,並在全球範圍內獲得數百萬使用者。

在本文中,我們將介紹 Uber 的 API 閘道器演進的不同階段,該閘道器為 Uber 產品提供動力。我們將回顧歷史,瞭解伴隨這一飛速發展階段而發生的建築模式的演變。我們將討論這三代閘道器係統的演變,探討它們的挑戰和責任。

第一代:有機進化

2014 年調查 Uber 的架構,會發現有兩個關鍵服務:排程和 API。排程服務負責連線騎手和司機,API 服務是我們使用者和行程的持久化儲存。除此以外,還有一個個位數的微服務,支援我們消費者應用上的關鍵流程。

騎手應用和司機應用都使用一個託管在'/'的端點連線到排程服務。端點的主體有一個名為 "messageType "的特殊欄位,它決定了呼叫特定處理程式的 RPC 命令。處理程式用 JSON 有效載荷來響應。

 

 

 

圖 1:簡化的高層說明

在這組 RPC 命令中,有 15 個命令是為關鍵的實時操作保留的,比如允許司機夥伴開始接受車次、拒絕車次和乘客請求車次。一個特殊的訊息型別被命名為 "ApiCommand",它將所有請求代理到 api 服務,並附加一些來自排程服務的上下文。

在 API 閘道器的上下文中,它看起來像'ApiCommand'是我們進入 Uber 的閘道器。第一代是一個單一的單體服務有機演化的結果,它開始服務於真正的使用者,並找到了用額外的微服務來擴充套件的方法。排程服務作為一個面向公眾的 API 的移動介面--但包括一個具有匹配邏輯的排程系統和一個代理,將所有其他流量路由到 Uber 內部的其他微服務。

這個第一代系統的光輝歲月在那之後並沒有持續多久,因為它已經在前幾年投入生產。到 2015 年 1 月,一個全新的 API 閘道器(可以說是第一個真正的閘道器)的藍圖被引匯出來,第一個允許 Uber 騎手應用搜尋目的地位置的語義 RESTful API 被部署出來,每秒有幾千次查詢(QPS)。這是向正確方向邁出的一步。

第二代:全能門戶

Uber 在早期採用了微服務架構。這個架構上的決定最終導致了 2200 多個微服務的增長,這些服務在 2019 年為 Ubers 的所有產品提供動力。

 

 

 

圖 2:RTAPIs 服務作為整個公司堆疊的一部分的高階說明

API 閘道器層被命名為 RTAPI,是實時 API 的縮寫。它在 2015 年初從一個單一的 RestfulAPI 開始,後來發展成為一個閘道器,許多面向公眾的 API 為 20 多個不斷增長的移動和 Web 客戶端組合提供支援。該服務是一個單獨的儲存庫,隨著它繼續以指數級的速度增長,它被分成多個專門的部署組。

這個 API 閘道器是 Uber 最大的 NodeJS 應用程式之一,有一些令人印象深刻的資料:

  • 跨 110 個邏輯端點分組的多個端點
  • 40%的工程師已經將程式碼提交給了這一層
  • 峰值 800000 個請求/秒
  • 有120萬個翻譯服務用於為客戶端本地化資料
  • 在 5 分鐘內對每個 diff 執行 50000 個整合測試
  • 在最長的一段時間裡,幾乎每天都有部署
  • 約 100 萬行程式碼處理最關鍵的使用者流
  • 大約 20%的 mobile build 是根據該層中定義的模式生成的程式碼
  • 與 Uber 100 多個團隊擁有的約 400 多個下游服務進行溝通

第二代的目標

公司內部的每一個基礎設施都有一個預定的目標來滿足。有些目標是在最初的設計階段開始的,有些是在設計的過程中實現的。

解耦

100 多個團隊在並行構建特性。提供由後端團隊開發的基礎功能的微服務的數量呈爆炸式增長。前端和移動團隊正在以同樣快的速度建立產品體驗。Gateway 提供了所需的去耦功能,並允許我們的應用程式繼續依賴於一個穩定的 API 閘道器及其提供的契約。

協議轉換

所有移動到伺服器的通訊都主要使用 HTTP/JSON。在內部,Uber 還推出了一種新的內部協議,該協議旨在提供多路複用的雙向傳輸協議。優步的每一項新服務都會採用這一新協議。這使得後端系統在兩個協議之間的服務變得支離破碎。這些服務的一些子集也允許我們只通過對等網路來處理它們。當時的網路堆疊還處於非常早期的階段,閘道器保護我們的產品團隊不受底層網路變化的影響。

橫切關注點

公司使用的所有 API 都需要一定的功能集,這些功能應該保持通用性和健壯性。我們專注於身份驗證、監控(延遲、錯誤、有效負載大小)、資料驗證、安全審計日誌記錄、按需除錯日誌記錄、基線警報、SLA 測量、資料中心粘性、CORS 配置、本地化、快取、速率限制、負載削減和欄位混淆。

流式有效載荷

在此期間,許多應用程式功能都採用了將資料從伺服器推送到移動應用程式的功能。這些有效負載被建模為 API 和上面討論的相同的“橫切關注點”。應用程式的最終推送是由我們的流媒體基礎設施管理的。

減少往返

在過去的十年裡,網際網路已經在不斷髮展,以解決 HTTP 協議棧的各種缺點。減少 HTTP 上的往返是前端應用程式使用的一種眾所周知的技術(記住影像精靈、用於資產下載的多個域等)。在微服務架構中,減少訪問微服務功能的往返次數在閘道器層結合在一起,閘道器層“分散收集”來自各種下游服務的資料,以減少應用程式與後端之間的往返。這對於我們在拉坦、印度和其他國家的蜂窩網路上的低頻寬網路的使用者特別重要。

前端的後端 BFF(Backend for the frontend)

開發速度是任何成功產品的重要特徵。在整個 2016 年,我們的新硬體基礎設施沒有對接,提供新服務很容易,但硬體配置稍顯複雜。閘道器為團隊提供了一個很好的地方,可以在一天內啟動和完成他們的功能。這是因為這是我們的應用程式呼叫的一個系統,該服務有一個靈活的開發空間來編寫程式碼,並且可以訪問公司內數百個微服務客戶端。第一代 Uber Eats 完全是在 Gateway 內部開發的。隨著產品的成熟,產品被移出了閘道器。Uber 有許多功能完全是在閘道器層使用其他現有微服務的現有功能構建的。

我們的方法面臨的挑戰

技術挑戰

我們對閘道器的最初目標主要是 io 繫結,並且有一個團隊致力於支援節點.js. 經過一輪又一輪的審查,節點.js 成為這個門戶的首選語言。隨著時間的推移,擁有這樣一種動態語言並在 Uber 體系結構的關鍵層為 1500 名工程師提供自由格式的編碼空間,面臨著越來越大的挑戰。

從某種意義上說,每次新的 API/程式碼更改都會執行 50000 個測試,因此,使用一些動態載入機制可靠地建立基於依賴關係的增量測試框架非常複雜。隨著 Uber 的其他部分轉向 Golang 和 Java 作為主要支援的語言,新的後端工程師加入到閘道器及其非同步上節點.js 模式減慢了我們工程師的速度。

大門變得相當大。它採用了 monorepo 的標籤(閘道器被部署為 40 多個獨立服務),並將 2500 個 npm 庫升級到更新版本的節點.js 繼續以指數級的速度增加努力。這意味著我們不能採用許多庫的最新版本。此時,Uber 開始採用 gRPC 作為首選協議。我們的版本節點.js 在這方面沒有幫助。

在程式碼檢查和影子流量期間,經常會出現無法阻止的空指標異常(NPE)的情況,從而導致關鍵閘道器部署暫停幾天,直到 NPE 在一些不相關的新的未使用的 api 上得到修復。這進一步降低了我們的工程速度。

閘道器中程式碼的複雜性偏離了 IObound。由幾個 api 引入的效能迴歸可能導致閘道器速度減慢。

非技術性挑戰

閘道器的兩個具體目標給這個系統帶來了很大的壓力。“減少往返”和“前端的後端BFF”是導致大量業務邏輯程式碼洩漏到閘道器中的祕訣。有時這種洩漏是出於自願,有時是毫無理由的。由於有超過一百萬行的程式碼,很難區分“減少往返”和繁重的業務邏輯。

由於閘道器是保持客戶繼續移動和進食的關鍵基礎設施,閘道器團隊開始成為優步產品開發的瓶頸。我們通過 API 分片部署和分散審查緩解了這一點,但瓶頸問題並沒有得到令人滿意的解決。

這使我們不得不重新考慮下一代 API 閘道器的策略。

第三代:自助式、分散和分層式

到 2018 年初,Uber 已經擁有了全新的業務線,並有了眾多新的應用。業務線的數量只會繼續增加--貨運、ATG、Elevate、雜貨等。在每條業務線中,團隊管理著他們的後端系統和應用。我們需要系統垂直獨立,以便快速開發產品。閘道器必須提供一套正確的功能,能夠真正加速他們的發展,並避免上述的技術和非技術挑戰。

我們第三代產品的目標

公司與上次我們設計第二代閘道器時有很大不同。回顧了所有的技術和非技術挑戰,我們帶著新的目標開始設計第三代產品。

分離關注點

這種新的架構鼓勵公司遵循分層的產品開發方式。

邊緣層:真正的閘道器係統,除了 "為前端提供後臺 "和 "減少往返 "之外,提供我們第二代系統閘道器部分目標中描述的所有功能。"

展示層:專門標記了微服務,為其功能&產品提供前端的後端。該方法的結果是,產品團隊管理自己的展現和編排服務,以滿足消費應用所需的 API。這些服務中的程式碼是針對檢視的生成和來自許多下游服務的資料的聚合。有單獨的 API 來修改迎合特定消費者的響應。例如,與標準的 Uber 騎手應用相比,Uber Lite 應用可能需要更少的與接送地圖相關的資訊。每一個都可能涉及不同數量的下游呼叫,以計算所需的響應有效載荷與一些檢視邏輯。

產品層:這些微服務被專門標記為提供描述其產品/功能的功能性、可重用的 API。這些可能會被其他團隊重用,以組成和構建新的產品體驗。

領域層:包含了微服務,這些微服務是為產品團隊提供單一精細功能的葉子節點。

 

 

 

圖 3:分層結構

減少我們邊緣層的目標

造成複雜度的關鍵因素之一是第二代中由檢視生成和業務邏輯組成的臨時程式碼。在新的架構下,這兩個功能已經被移出到其他微服務中,由獨立團隊在標準的 Uber 庫和框架上擁有和運營。邊緣層是作為一個純邊緣層執行的,沒有定製程式碼。

需要注意的是,一些起步的團隊可以擁有一個服務,滿足展示層、產品層和服務層的職責。隨著功能的發展,可以將其分解到不同的層中。

這種架構提供了極大的靈活性,可以從小做起,並在我們所有的產品團隊中達成一致的北極星架構。

技術構件

在我們向新設想的架構轉移的過程中,我們需要關鍵的技術元件到位。

邊緣閘道器(Edge Gateway)

 

 

 

端到端使用者流

原本由我們第二代閘道器係統服務的邊緣層,被一個獨立的 Golang 服務與 UI 搭配取代。"邊緣閘道器 "作為 API 生命週期管理層,由內部開發。現在所有的 Uber 工程師都可以訪問 UI 來配置、建立和修改我們面向產品的 API。該 UI 既能進行簡單的配置,如身份驗證,也能進行高階配置,如請求轉換和頭傳播。

服務框架

鑑於所有的產品團隊都要維護和管理一套微服務(可能在這個架構的每一層為他們的功能/產品服務),邊緣層團隊與語言平臺團隊合作,商定了一個名為 "Glue "的標準化服務框架,在整個 Uber 中使用。Glue 框架提供了一個建立在 Fx 依賴注入框架之上的 MVCS 框架。

服務庫

閘道器中屬於 "減少往返 "和 "前臺的後臺BFF "這兩個屬性的程式碼類別,需要在 Golang 中建立一個輕量級的 DAG 執行系統。我們在 Golang 中構建了一個名為控制流框架(CFF)的內部系統,允許工程師在服務處理程式中開發複雜的無狀態工作流來進行業務邏輯協調。

 

 

圖4:一個 CFF 任務流

組織一致性

將一家過去幾年以特定方式運營的公司轉移到一個新的技術系統中,始終是一個挑戰。這個挑戰特別大,因為它影響了 40%的 Uber 工程的常規運作方式。進行這樣的努力,最好的方式是建立共識和對目標的認識。有幾個維度需要關注。

建立信任

中心化團隊將一些高規模的 API 和關鍵端點遷移到新的技術棧中,以驗證儘可能多的用例,並驗證我們可以開始讓外部團隊遷移他們的節點和邏輯。

確定所有者

由於有許多 API,我們必須明確識別所有權。這並不簡單,因為大量的 API 都是跨團隊擁有的邏輯。對於明確對映到某一產品/功能的 API,我們自動分配給他們,但對於複雜的 API,我們逐個進行協商,確定所有權。

團隊承諾

分解成團隊後,我們將終端團隊分成了很多組(通常是按照公司較大的組織結構,比如騎手、司機、支付、安全等),並聯絡了工程部的領導,希望找到一個工程部和專案部的 POC 來帶領他們的團隊,貫穿整個 2019 年。

人員培訓

中介軟體團隊對工程和專案負責人進行了培訓,讓他們瞭解 "如何 "遷移,"需要注意什麼","何時 "遷移。我們建立了支援渠道,其他團隊的開發人員可以在遷移過程中尋求問題和幫助。我們建立了一個自動化的集中跟蹤系統,以保證團隊的責任,提供進度的可視性,並向領導層彙報。

迭代策略

在遷移過程中,我們遇到了一些邊緣情況,對假設提出了質疑。有好幾次,我們引入了新的功能,而在其他時候,我們選擇不使用與某層無關的功能來汙染新的架構。

在遷移過程中,我們的團隊繼續思考未來和技術組織的發展方向,並確保在這一年中對技術指導進行調整。

最終,我們能夠有效地執行我們的承諾,並順利地朝著自助式 API 閘道器和分層服務架構的方向發展。

結論

在 Uber 花時間開發和管理了三代閘道器係統之後,以下是一些關於 API 閘道器的高層次洞察。

如果有選擇的話,請為你的移動應用和內部服務堅持使用單一協議。採用多種協議和序列化格式最終會導致閘道器係統的巨大開銷。擁有一個單一協議為你提供了選擇,你的閘道器層可以有多豐富的功能。它可以是簡單的代理層,也可以是極其複雜和功能豐富的閘道器,可以使用自定義 DSL 實現 graphQL。如果有多個協議需要翻譯,那麼閘道器層就不得不變得複雜,以實現將 http 請求路由到另一個協議的服務的最簡單過程。

設計你的閘道器係統以橫向擴充套件是極其關鍵的。尤其是對於像我們第二代和第三代這樣複雜的閘道器係統來說,更是如此。為 API 組構建獨立二進位制的能力是使我們的第二代閘道器能夠橫向擴充套件的一個關鍵功能。單一的二進位制將過於龐大,無法執行 1600 個複雜的 API。

基於 UI 的 API 配置對於現有 API 的增量變化是非常好的,但是建立一個新的 API 通常是一個多步驟的過程。作為工程師,有時 UI 可能會覺得比直接在檢查出來的程式碼庫上工作更慢。

我們從二代到三代的開發和遷移時間線長達 2 年。當工程師在專案中過渡和退出時,持續的投入是至關重要的。保持可持續發展的勢頭對這種以北極為目標的長期專案的成功極為關鍵。

最後,每個新系統不需要支援舊系統的所有技術債務的功能。在放棄支援的問題上做出有意識的選擇,對於長期的可持續發展至關重要。

回顧我們閘道器的發展歷程,人們會想,我們是否可以跳過一代人,到達目前的架構。任何一個還沒有開始這個歷程的公司,可能也會想是否應該從自助式 API 閘道器開始。這是一個很難做出的決定,因為這些進化不是獨立的決定。很多事情都取決於整個公司的支援系統的進化,比如基礎設施、語言平臺、產品團隊、增長、產品規模等等。

在 Uber,我們已經找到了最新架構成功的有力指標。我們在第三代系統中每天的 API 變化已經超過了第二代的數字。這直接關係到更快節奏的產品開發生命週期。轉移到基於 golang 的系統後,我們的資源利用率和請求/核心指標得到了顯著改善。我們大多數 API 的延遲資料已經顯著降低。隨著架構的成熟和舊系統在自然重寫週期內被重寫到新的分層架構中,還有很長的路要走。

作者

Madan Thangavelu

Madan Thangavelu 是 Uber 的高階工程經理。在過去的 6 年裡,他見證了 Uber 激動人心的超速發展階段,併為之做出了貢獻。他在 Uber 領導閘道器平臺團隊 4 年。目前,他是 Uber 履約平臺的工程負責人,該平臺為實時的全球規模購物和物流系統提供動力。

Uday Kiran Medisetty

Uday Kiran Medisetty 是 Uber 的高階員工工程師。他在第二代 API 閘道器暴露出<10 個 API 時加入團隊,幫助擴充套件到 1600 個 API,幫助制定第三代 API 的策略,還領導了基於伺服器到客戶端的推送訊息的開發,以實現實時移動體驗。在過去的幾年裡,他正在領導 Uber 核心履約平臺的重新架構。

Pavel Astakhov

Pavel Astakhov 是 Uber 的高階技術專案經理。在過去的 2 年裡,他領導了整個公司的多個大型跨功能基礎設施專案,並加入了 Madan & Uday,為從第二代邊緣層過渡到第三代邊緣層制定和領導執行策略/遷移。

原文

https://eng.uber.com/gatewayuberapi/

相關文章