Uber三代API 生命週期管理平臺實現

由 Uber 開發的邊緣網關是一個高可用、可擴展的自助式網關,用於配置、管理和監視 Uber 的每個業務域API。

本文最初發表於Uber官方博客網站,經授權,由InfoQ中文站翻譯併發布。

Uber API 網關演變史

自 2014 年 10 月起,Uber 走上了規模化擴張之旅,這段旅程最終成爲公司最令人印象深刻的增長階段之一。隨着時間的推移,我們每個月都在非線性擴大工程團隊的規模,並在全球獲得了數以百萬計的用戶。

在本文中,我們將爲讀者介紹 Uber API 網關演變的不同階段,這個網關爲 Uber 產品提供了支持。我們將通過回顧歷史來了解架構的演變史,這些演變是伴隨着高速增長階段而發生的。我們將闡述這三代網關係統的演變史,探討它們的挑戰和責任。

第一代 API 網關:有機演變

如果你在 2014 年調查 Uber 架構的話,就會發現有兩個關鍵服務:調度和 API。調度服務負責連接乘客(Rider)和司機(Driver),API 服務是我們用戶和行程的長期存儲庫。除此之外,還有不到 10 個微服務,用來支持我們客戶應用程序上的關鍵流程。

乘客應用程序和司機應用程序都使用位於“/”的單一終結點連接調度服務。端點的主體有一個名爲“messageType”的特殊字段,該字段決定了調用特定處理程序的 RPC 命令。該處理程序以 JSON 有效負荷進行響應。

圖 1:簡化的高級圖示

在這組 RPC 命令中,有 15 個命令是爲關鍵的實時操作保留的,比如,允許司機夥伴開始接受行程、拒絕行程和乘客請求行程。一個特殊的messageType被命名爲 “ApiCommand”,它將所有請求代理到 API 服務,並提供一些來自調度服務的附加上下文。

在 API 網關的上下文中,看起來ApiCommand是我們進入 Uber 的門戶。第一代網關是單一的單體服務有機演變的結果,它開始服務於真實的用戶,並找到了利用附加的微服務進行擴展的方法。調度服務作爲面向公衆的 API 移動接口,包括一個具有匹配邏輯的調度系統和代理,用於將所有其他流量路由到 Uber 內的其他微服務。

但是,第一代系統的輝煌日子並沒有持續很長時間,因爲它在前幾年就已經投產了。到 2015 年 1 月,一個全新的 API 網關(可以說是第一個真正的網關)藍圖已經啓動,第一個語義 RESTful API 被部署,允許 Uber 乘客應用程序搜索目的地位置,每秒查詢數(Queries per second,QPS)可達幾千次,這是朝着正確方向邁出的第一步。

第二代 API 網關:無所不包的網關

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

圖 2:RTAPI 服務作爲整個公司技術棧的一部分的高級圖示

API 網關曾被命名爲 RTAPI,是 Real-Time-API 的縮寫。它在 2015 年初以一個單一的 RESTful API 開始,並發展成爲擁有許多面向公共的 API 網關,併爲 20 多個不斷增長的移動和 Web 客戶端組合提供支持。該服務是單一的存儲庫,隨着它繼續以指數級的速度增長,被分解爲多個專門的部署組。

這個 API 網關是 Uber 最大的 NodeJS 應用程序之一,有一些令人印象深刻的統計數據:

  • 跨 110 個邏輯端點分組的多個端點;
  • 40% 的工程師已將代碼提交到這一層;
  • 峯值時高達 800000 個請求/每秒;
  • 爲客戶提供 120 萬次翻譯,以實現數據本地化;
  • 在 5 分鐘內對每個差異執行 50000 次集成測試;
  • 有很長一段時間,幾乎每天都會進行一次部署;
  • 大約有 100 萬行代碼處理最關鍵的用戶流;
  • 大約 20% 的移動構建是由這一層定義的模式生成的代碼;
  • 與 Uber 100 多個團隊擁有的約 400 多個下游服務進行通信;

第二代網關的目標

公司內部的每一個基礎設施都有一組預定的目標要滿足。有的目標是最初設計時就開始的,有些則是在設計過程中逐步實現的。

解耦

100 多個團隊並行構建功能。由後端團隊開發的提供基礎功能的微服務數量呈爆炸式增長。前端團隊和移動團隊正在以同樣快的速度構建產品體驗。網關提供了所需的解耦,並允許我們的應用程序繼續依賴穩定的 API 網關和它提供的合約。

協議轉換

所有移動客戶端到服務器的通信主要使用 HTTP/JSON。在內部,Uber 還推出了一種新的內部協議,旨在提供多路複用雙向傳輸協議。曾經有一段時間,Uber 的每個新服務都採用了這個新協議。這就使得後臺系統的服務在這兩種協議之間變得支離破碎。這些服務的某些子集也允許我們只能通過對等網絡來解決這些問題。當時的網絡協議棧也處於非常早期的階段,網關保護我們的產品團隊不受底層網絡變化的影響。

橫切關注點

公司使用的所有 API 都需要一定的功能集,這些功能應該保持通用且穩定。我們關注的重點是身份驗證、監控(延遲、錯誤、有效負荷大小)、數據驗證、安全審覈日誌、按需調試日誌、基線警報、SLA 測量、數據中心粘性、CORS 配置、本地化、緩存、速率限制、負荷消減和字段模糊處理。

流式有效負荷

在此期間,許多應用功能採用了從服務器向移動應用推送數據的功能。這些有效負荷被建模爲 API 和上面討論的相同的“橫向關注點”。最後推送到應用程序的過程是由我們的流媒體基礎設施管理的。

減少往返

過去十年,互聯網在解決 HTTP 協議棧的各種缺點方面取得了進展。減少 HTTP 上的往返是前端應用程序使用的一項衆所周知的技術(請記住圖像精靈、用於資產下載的多個域等)在微服務架構中,減少訪問微服務功能的往返次數在網關層結合在一起,網關層從各種下游服務中“分散收集”數據,以減少我們的應用和後端之間的往返。這對於我們在拉美、印度等國家的低帶寬蜂窩網絡上的用戶來說尤爲重要。

開發速度

對任何成功的產品而言,開發速度都是非常關鍵的特徵。在整個 2016 年,我們的新硬件基礎設施並未實現 Docker 化,雖然提供新服務很容易,但硬件配置稍顯複雜。網關爲團隊提供了一個這樣奇妙的地方,讓團隊可以在一天之內開始並完成他們的功能。這是因爲它是我們的應用程序調用的系統,服務有一個靈活的開發空間來編寫代碼,並且可以訪問公司內部的數百個微服務客戶端。Uber Eats 的第一代產品完全是在網關內開發的。隨着產品的成熟,部分產品被移出了網關。在 Uber,有很多功能完全是利用其它現有微服務的現有功能在網關層構建出來的。

面臨的挑戰

技術挑戰

我們最初的網關目標主要是受 I/O 限制的,並且有一個團隊致力於支持 Node.js。經過一輪又一輪的評審,Node.js 成了這個網關的首選語言。隨着時間的推移,擁有這樣一種動態的語言,以及爲 Uber 架構中如此關鍵的層中 1500 名工程師提供自由編碼空間,都面臨着越來越大的挑戰。

在某些時候,每一個新的 API/代碼變更都要運行 5 萬個測試,因此,要可靠地創建一個增量測試框架是很複雜的,因爲這個框架基於依賴項,且具有一些動態加載機制。當 Uber 的其他部分轉向 Golang 和 Java 作爲主要支持的語言時,隨着新的後端工程師加入網關及其異步 Node.js 模式,工程師的速度慢了下來。

網關變得相當大了。它被貼上了 monorepo(單體式代碼倉庫)的標籤(該網關被部署爲 40 多個獨立服務),並將 2500 個 npm 庫升級到較新版本的 Node.js,繼續以指數級方式增加工作量。這就意味着我們無法採用衆多庫的最新版本。這時,Uber 開始採用gRPC作爲首選協議,而我們的 Node.js 版本在這方面卻毫無建樹。

在代碼審查和影子流量期間,指針異常(Null Point exception,NPE)是無法避免的,這會導致關鍵網關部署停滯幾天,直到 NPE 在一些不相關的、新的、未使用的 API 上得到修復。這樣一來,就進一步拖慢了我們的工程速度。

網關中代碼的複雜性與受 I/O 限制背道而馳。由一些 API 引入的性能迴歸可能會導致網關變慢。

非技術性挑戰

網關的兩個特定目標。“減少往返”和“開發速度”,給這個系統帶來了很大的壓力。這兩個目標是導致大量業務邏輯代碼泄漏到網關的原因。有時,這種泄露是故意的,有時是無意的。由於代碼超過一百萬行,因此“減少往返”和大量的業務邏輯之間是相當難以辨別的。

隨着網關成爲保證客戶持續移動的關鍵基礎設施,網關團隊開始成爲 Uber 產品開發的瓶頸。我們通過 API 分片部署和分散審查緩解了這一問題,但成爲瓶頸的問題沒有解決到令人滿意的程度。

這時,我們不得不重新考慮下一代 API 網關的策略。

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

到 2018 年初,Uber 已經擁有全新的業務線,並有了衆多新的應用。業務線的數量再次增加:Freight(優步貨運)、ATG(先進技術團隊)、Elevate(優步航空)、Groceries(雜貨配送)等。在每條業務線中,團隊管理者的後端系統和應用需要互相獨立,以便快速開發產品。網關必須能夠提供正確的功能集,這些功能實際上可以對它們進行加速,並避免上面提到的技術性和非技術性挑戰。

第三代網關的目標

第三代網關與第二代網關的設計有很大不同。在回顧所有技術性和非技術性的挑戰後,我們開始設計第三代網關,並制定了一套新目標。

關注點分離

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

邊緣層:真正的網關係統,它提供了第二代系統的網關部分目標所描述的所有功能,除了“開發速度”和“減少往返”。

表示層:專門爲前端的功能和產品提供後端標記的微服務。這種方法導致產品團隊管理自己的表示和編排服務,這些服務可以滿足消費應用程序所需的 API。這些服務中的代碼是針對視圖生成和來自許多下游服務數據的聚合。有單獨的 API 來修改以適應特定消費者的響應。例如,與標準的 Uber 乘客應用程序相比,Uber Lite 應用程序可能需要更少的與接送地圖相關的信息。其中每一個都可能涉及不同數量的下游調用,以使用某些視圖邏輯計算所需的響應有效負荷。

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

域層:包含作爲葉節點的微服務,爲產品團隊提供單一的優化功能。

圖 3:分層架構

減少邊緣層的目標

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

關鍵是要注意,一些剛起步的團隊可以擁有一個單一服務來滿足表示層、產品層和服務層的職責。隨着功能的發展,它可以被解構到不同的層。

這種架構提供了極大的靈活性,可以從小規模開始,最終形成一個貫穿我們所有產品團隊的北極星架構。

技術構建塊

在我們努力轉移到新設想的架構時,我們需要關鍵的技術組件就位。

邊緣網關

端到端用戶流

最初由我們的第二代網關係統服務的邊緣層被一個單獨的 Golang 服務和一個用戶界面所取代。“邊緣網關”是內部開發的 API 生命週期管理層。所有 Uber 工程師現在都可以訪問其用戶界面來配置、創建和修改面向產品的 API。用戶界面既可以進行簡單的配置(如身份驗證),也可以進行高級配置(如請求轉換和標頭傳播)。

服務框架

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

服務庫

網關中的代碼類別屬於“減少往返”和“用於前端的後端”這兩個範疇,需要在 Golang 建立一個輕量級 DAG 執行系統。我們在 Golang 建立了一個內部系統,稱爲控制流框架(Control Flow Framework,CFF),它允許工程師在服務處理程序中爲業務邏輯編排開發複雜的無狀態工作流。

圖 4:CCF 任務流

組織一致性

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

建立信任

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

查找所有者

由於有許多 API,我們必須清楚地確定所有權。要做成這件事並不簡單,因爲大量 API 具有跨團隊擁有的邏輯。對於清晰映射到某個產品/特性的 API,我們會自動分配它們,但對於複雜的 API,我們逐個處理並協商所有權。

承諾

在將其分成團隊之後,我們將端點團隊分成許多組(通常按較大的公司組織結構,例如乘客、司機、支付、安全等),並聯系工程領導,以期在 2019 年全年找到一個工程部和項目部的 POC 來帶領他們的團隊。

培訓

集中式團隊對工程和項目負責人進行了培訓,讓他們瞭解“如何”遷移、“需要注意什麼”、“何時”遷移等。我們建立了支持渠道,來自其他團隊的開發人員可以在遷移過程中尋求問題和幫助。爲了保證團隊承擔責任,提供進度的可視性,並更新領導能力,我們還部署了一個自動化的集中跟蹤系統。

迭代策略

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

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

最終,我們能夠有效地執行我們的承諾,並朝着自助式 API 網關和分層服務架構的方向邁進。

結論

在 Uber 耗費時間開發和管理了三代網關係統之後,下面是一些關於 API 網關的高層次觀察。

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

設計你的網關係統,使其能夠橫向擴展是非常關鍵的。對於像我們第二代和第三代這樣複雜的網關係統來說,更是如此。爲 API 組構建獨立二進制的能力是我們第二代網關能夠橫向擴展的一個關鍵特性。單一的二進制文件過於龐大,無法運行 1600 個複雜的 API。

基於用戶界面的 API 配置對於現有 API 中的增量更改非常有用,但是創建新的 API 通常是一個多步驟的過程。作爲工程師,有時用戶界面可能會感覺比直接在檢查過的的代碼庫上工作更慢。

我們從第二代到第三代的開發和遷移時間長達 2 年。隨着工程師在項目內外的過渡,持續投資至關重要。最後,每一個新系統不需要支持來自舊系統的所有技術債務功能。有意識地選擇放棄支持對長期可持續性至關重要。

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

在 Uber,我們發現了這一最新架構成功的有力指標。我們在第三代系統中每天的 API 變化已經超過了第二代的數字,這直接關係到節奏更快的產品開發生命週期。轉移到基於 Golang 的系統後,我們的資源利用率和請求/核心度量已經顯著地提高了。我們大多數 API 上的延遲數都已經顯著地減少了。隨着新架構的成熟和舊系統在其自然重寫週期中被重寫到較新的分層架構中,還有很長的一段路要走。

作者介紹:

Madan Thangavelu是 Uber 高級工程經理。在過去的 6 年裏,他見證了 Uber 令人興奮的高速增長階段,併爲此做出了貢獻。他花了 4 年的時間領導 Uber 的網關平臺團隊。目前,他擔任 Uber 實現平臺的工程主管,該平臺爲全球實時購物和物流系統提供支持。

Uday Kiran Medisetty是 Uber 的高級工程師。。他在第二代 API Gateway 發佈不到 10 個 API 時加入了團隊,幫助擴展到 1600 個 API,並協助制定了第三代網關的策略,還領導了基於服務器到客戶端的實時移動體驗推送消息的開發。在過去的幾年裏,他正在領導 Uber 核心實現平臺的重新架構。

Pavel Astakhov是 Uber 的高級技術項目經理。在過去的兩年裏,他領導了整個公司的多個大型跨功能基礎設施項目,並與 Madan 和 Uday 一起開發和領導了從邊緣層的第二代到第三代過渡的執行策略/遷移。

原文鏈接:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章