Categories
程式開發

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


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

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

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

Uber API 網關演變史

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

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

第一代API 網關:有機演變

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

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

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

圖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 所有產品提供了動力。

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

圖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 來描述他們的產品/功能,這些可能會被其他團隊重用來組合和構建新的產品體驗。

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

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

圖3:分層架構

減少邊緣層的目標

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

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

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

技術構建塊

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

邊緣網關

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

端到端用戶流

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

服務框架

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

服務庫

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

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

圖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 上的延迟数都已经显著地减少了。随着新架构的成熟和旧系统在其自然重写周期中被重写到较新的分层架构中,还有很长的一段路要走。

作者介紹:

馬丹·唐加魯是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/