餓了么高穩(wěn)定、高性能、高可用、高容錯API架構(gòu)實踐!
今天的分享和 API 架構(gòu)相關(guān),餓了么 API Everything 框架建設(shè)是一個不斷演進的過程。
什么是 API Everything?
先簡單介紹一下 API,就是相當于前端比如 Web 訪問到后端的服務(wù)接口,這中間有一個隔離,適配給外部各端進行訪問,隔離是起到安全性的考慮,還有一個協(xié)議轉(zhuǎn)換的考慮。
當然,基于這一塊我們還有很多其他的考慮。在餓了么初期發(fā)展階段,我們的很多 Web API 層都是手寫的,即多數(shù)應(yīng)用服務(wù)后端,都自己寫 Web API,單獨部署,提供給前端 HTTP API 調(diào)用。
當時業(yè)務(wù)高速發(fā)展,為了快速應(yīng)對,有一些業(yè)務(wù)邏輯會放在 Web API 層,甚至在 Web API 層也會訪問數(shù)據(jù)庫,進行數(shù)據(jù)庫操作。
在 API 層直接訪問數(shù)據(jù)庫會導(dǎo)致安全性的一些問題,這個是不允許的。前端訪問后端,這個 HTTP API 接口是什么風格?
有 Restful ,還有 JSON-RPC ,需要統(tǒng)一考慮,不然對前端開發(fā)的體驗不一致。
另外 HTTP API 文檔存在過時,不能反映代碼實現(xiàn)的變化,比如代碼改了,文檔沒有改。
最后,前端同學(xué)和后端同學(xué)同時開發(fā),但開發(fā)的節(jié)奏可能不太一樣。比如前端在進行開發(fā),但后端可能會被插入更加緊急的項目,就不能及時完成當前項目的 API。
這樣可能會延誤前端的開發(fā),產(chǎn)生前后端開發(fā)不同步,導(dǎo)致前端資源和后端資源有個相互等待,會導(dǎo)致開發(fā)體驗的不順暢,效率也不高。
需求調(diào)研-API Everything
基于這些情況,公司考慮是否可以統(tǒng)一形成一個 API 框架,于是我們調(diào)研了各個部門,發(fā)現(xiàn)他們都有這個需求,希望有一個統(tǒng)一的 API 框架開發(fā),目前也有一些獨立寫的 Web API 層。
如下圖,是調(diào)研的一些需求:
從圖中,我們能看到一個很重要的功能就是 HTTP 到 SOA 的服務(wù)映射,還有認證與鑒權(quán),比如公司的 SSO,包括用戶、餓了么的一些鑒權(quán),API 部署與運行。
API Orchestration,這個相當于是 API 的拼接和剪裁的概念,可以調(diào)用多個 API,取各個 API 返回結(jié)果的一部分,重新組合成新的結(jié)果,返回給前端。
這個應(yīng)用在一個前端 API 調(diào)用,可以實際調(diào)用多個后端 API,組裝一個返回結(jié)果給前端,減少前端調(diào)用后端多次,提高前端用戶的體驗。
另外,因為后端已經(jīng)有了很多基礎(chǔ)服務(wù)的接口,新業(yè)務(wù)開發(fā)不需要后端再提供接口,只需要在這些接口上進行組合、裁剪就可以了。
此外還有 API 文檔、API 測試、Mock API、限流、反爬(就是說接口暴露在公網(wǎng),存在爬蟲爬取這些接口的情況,我們怎么保護接口等等)以及灰度。
我們的 API Everything 做這些事情時,把后端的 SOA 服務(wù)通過 API 的方式,安全可靠地提供給各種端(Web/APP)來使用。
產(chǎn)品技術(shù)方案原則
調(diào)研出來以后,需要考慮以什么原則去考慮產(chǎn)品和系統(tǒng)設(shè)計。
API Everything 是一個基礎(chǔ)框架,我們首先考慮到基礎(chǔ)框架的穩(wěn)定性是第一位。
哪怕你不能滿足所有功能需求,但你很穩(wěn)定,接入 API Everything 框架的各個應(yīng)用系統(tǒng)就不會歇菜,不會出問題,服務(wù)才能在穩(wěn)定的基礎(chǔ)上提升,才有可能增加新的功能和特性,也就是說穩(wěn)定壓倒一切。
然后是性能,包括吞吐量(響應(yīng)速度)、性能高了,就會節(jié)省硬件資源;高可用性,避免出現(xiàn)單點故障。還有容錯性,對外各種依賴,要考慮外部依賴歇菜怎么辦,怎么降級。
還有,API Everything 接了后端的應(yīng)用系統(tǒng),外部流量進來,不能沖擊到后端應(yīng)用系統(tǒng)。如何讓這個系統(tǒng)更健壯,怎么保護自己,怎么保護接入的應(yīng)用系統(tǒng)等等。
其次,在這個基礎(chǔ)之上考慮 DevOps 怎么弄,提供接入方自助 DevOps,還有各種指標查看、監(jiān)控告警、排錯手段、查看 log/trace/exception、自助擴容等。
最終希望這一塊做到對接入方是透明的,可以自動擴容。API Everything 框架引起的問題,由我們解決,接入應(yīng)用方出現(xiàn)的問題,由接入方解決,要有非常清晰的邊界界定。
一般問題可以自動解決,現(xiàn)場日志自動保留,盡可能自愈,并且接入方知道發(fā)生了什么情況。
另外,怎么讓我們的研發(fā)或者應(yīng)用開發(fā)端更“懶”?就是把經(jīng)常要做的事情自動化。
比如接入一個應(yīng)用方,要進行各種配置,比較繁瑣也容易出錯,那我們是不是可以進行自動化接入、自動化配置呢?
剛才也談到代碼和文檔不同步怎么辦,所以我們不寫文檔,在代碼里面寫文檔。
比如:在 Java 代碼里寫 Java doc 注釋,我們就把這些注釋抽出來,作為 API 文檔的一部分;另外,我們也提供一些標注,幫助完成文檔。
用戶體驗也是考慮的一大因素,因為技術(shù)產(chǎn)品基本上由工程師直接進行開發(fā),追求完成功能,多數(shù)沒有考慮用戶體驗,導(dǎo)致用起來操作別扭。
所以在這方面要充分吸取教訓(xùn),把使用者的體驗考慮起來,能點一下就不用點兩下,不能把技術(shù)復(fù)雜性暴露給用戶去理解、去操作,讓用戶用起來很爽,簡單不去操作是一個目標。
這個框架涉及到很多配置,散放在不同系統(tǒng),我們的想法是在一個配置里面全把它搞定,不要讓用戶理解這個是 API Everything 框架的哪一部分管理的,要到哪個系統(tǒng)去操作。
另外就是滿足不同的功能需求,比如接入不同的協(xié)議等,這是我們對整個產(chǎn)品方案在原則上的一些考慮。
生命周期
從 API 這邊出發(fā),可以看到 API 的生命周期是從 API 開發(fā)開始的,這個過程中會有文檔、Mock,開發(fā)完了是管理,也就是授權(quán)誰能訪問,有些是不是可以灰度。
API 管理之后是 API 網(wǎng)關(guān)服務(wù),就是運行態(tài)服務(wù),對 API 進行協(xié)議轉(zhuǎn)換,比如將 HTTP 協(xié)議轉(zhuǎn)換成 SOA,調(diào)用后臺的 SOA 服務(wù),最后進入 API 運維,就是對 API 進行監(jiān)控管理和部署擴容。
產(chǎn)品規(guī)劃
根據(jù)產(chǎn)品系統(tǒng)設(shè)計原則,結(jié)合 API 的生命周期,我們規(guī)劃了下面幾個產(chǎn)品,如下圖:
比如說開發(fā)支持這一塊,就是 API Portal,運行支持這塊就是 Stargate Cluster。還有質(zhì)量保證,就是 API Robot,通過自動化回歸測試來保證 API 的質(zhì)量。
另外要考慮到在開發(fā)的過程中,怎么讓前后端同步,這里面很重要的一點是怎么樣 Mock API 的數(shù)據(jù),讓前后端分離開發(fā)?這就產(chǎn)生了 Mock Server,于是根據(jù)規(guī)劃形成了這四個產(chǎn)品。
但這四個產(chǎn)品之間是什么樣的關(guān)系呢?這里從系統(tǒng)上的交互來考慮。
如上圖,我們從底下看,前端的應(yīng)用(比如到達圈前端),通過 HTTP 訪問,到達 Nginx Cluster,然后轉(zhuǎn)向 SOA 服務(wù)。
灰色的路徑就是到達圈管理后臺應(yīng)用,灰色再上去就到達圈服務(wù),另外還有紅色這條路徑,前端 URL 可以通過加入 query string 訪問 Mock Server。
Stargate Cluster 收到這個 querystring,就不會發(fā)給后端 SOA 服務(wù),會路由到指定的 Mock Server。前端會通過 Mock 的方式完成前端的開發(fā)。
API Portal 負責 API 文檔這塊,文檔對應(yīng)的是部署到哪個環(huán)境,在 API Portal 里有顯示。
在餓了么有如下幾個環(huán)境:
- 開發(fā)的 Alpha 測試環(huán)境,這個是提供給開發(fā)使用的。
- Beta 環(huán)境,這個是提供給測試來驗證是否可以上線的環(huán)境,只有通過了 Beta 測試,才能上線。這個 Beta 環(huán)境也用來和其他團隊進行聯(lián)調(diào)。
- Prod 環(huán)境,用于線上生產(chǎn)。在 API Portal 上就能知道當前部署的應(yīng)用、對外提供的 API 文檔具體是什么。這是 API Portal 通過訪問 Stargate Cluster 部署信息獲得的。
API Robot 從 API Portal 中獲取 API 定義,通過定義發(fā)測試請求給 Stargate Cluster。
餓了么內(nèi)部服務(wù)是 SOA 架構(gòu),服務(wù)間有相互依賴的情況,需要進行測試、聯(lián)調(diào)。
有時候發(fā)現(xiàn)我們 SOA 服務(wù)依賴對方 SOA 服務(wù),對方還沒有開發(fā)完成,我們想測試自己開發(fā) 的 SOA 服務(wù)怎么辦?
這時我們就可以用 Mock Server,Mock 對方的 SOA 服務(wù),寫一下 Mock Case,完成我們自己的開發(fā)測試。
剛才說代碼即文檔,所以可能要規(guī)范一下怎樣寫代碼,注釋和標注寫完了,就可以自動化從這些注釋和標注中抽出文檔,形成 API 文檔。
Web API 這一塊不需要手工寫,目前我們自動生成對應(yīng) Web API 的代碼,然后自動再部署。
部署就是監(jiān)聽 SOA 服務(wù)部署消息,收到了部署消息,就自動生成的 Web API 代碼并且自動部署。
而 Mock,我們也是自動生成的,創(chuàng)建 Mock Case 的時候,就自動生成相應(yīng)的數(shù)據(jù),這些數(shù)據(jù)可以自己改。還有 API 的監(jiān)控告警,每個應(yīng)用接入,自動進行全鏈路監(jiān)控。
Stargate Cluster 技術(shù)架構(gòu)
如上圖,這是其中一個產(chǎn)品 Stargate Cluster 的技術(shù)架構(gòu)。
從上面看,ELESS 是我們構(gòu)建系統(tǒng),我們會監(jiān)聽它的構(gòu)建消息,當有構(gòu)建過來的時候,調(diào)用 base.stargate_core 服務(wù)。
該服務(wù)實現(xiàn)非常簡單,就是把 Stargate Cluster 需要的信息保存到 MaxQ 里面,MaxQ 是餓了么自己開發(fā)的一個 MQ 產(chǎn)品,目前在大量應(yīng)用。
最后 Stargate Cluster 運營管理服務(wù)是從 MaxQ 取消息進行處理。
為什么會有這樣的考慮?因為之前我們在 Stargate Cluster 運營管理服務(wù)里經(jīng)常有迭代開發(fā),經(jīng)常增加一些功能(比如異地多活),這些功能不斷地迭代、增加,需要經(jīng)常部署,處于一個不太穩(wěn)定的情況。
我們想任何這種構(gòu)建和部署消息不能丟,于是就有了 stargate_core 這個服務(wù),這個服務(wù)非常簡單,不進行功能上的迭代,保持不變,這樣就比較可靠,作用就是把構(gòu)建和部署消息放在 MaxQ 里頭。
而 stargate 運營管理不斷開發(fā)、然后重啟,這個過程中至少 MaxQ 里的數(shù)據(jù)不會丟,重啟完了也可以消費,繼續(xù)進行部署。我們是基于變化和不變化的隔離去考慮系統(tǒng)可靠性的結(jié)果。
Stargate Cluster 基于 Docker 部署
談?wù)劄槭裁茨茏詣硬渴穑?/span>其實挺有意思的,我們這邊用的是 Docker 環(huán)境。
從底下開始看,可以看到,比如 SOA 服務(wù)部署了,通過 ELESS(餓了么發(fā)布系統(tǒng) ),我們獲得部署消息,放到 MaxQ 里。
然后我們從 MaxQ 拿出消息,調(diào)用 AppOS(餓了么自研的 Docker 平臺),啟動相應(yīng)的 Docker 實例。
實例上運行著這個 SOA 服務(wù)對應(yīng)的 Web API 代碼,Docker 實例啟動時,調(diào)用 Navigator(餓了么自研的 Nginx 管理平臺),將該 Docker 實例的 IP 注冊到 Nginx 上,這樣外部流量就打到這些 Docker 上了。
在新 Docker 成功啟動之后,Stargate Cluster 就會調(diào)用 AppOS 將之前版本的 Docker 實例給銷毀掉,通過 Navigator 將對應(yīng)在 Nginx 上的 IP 也刪除掉,這就是完成自動部署。
這是我們自動部署的一些信息,從圖中可以看到左上角基本上是 SOA 服務(wù)部署的 Push Seq,下面是 PushSeq Used by Client。
這兩個 Push Seq 一致,就說明 SOA 服務(wù)部署時,Stargate Cluster 將其對應(yīng)的 Web API 端也自動部署了,兩邊用的版本(Push Seq)是一致的。同時,包括部署的一些版本信息我們也都會保存下來。
API Portal – 自動化文檔
講完 Stargate Cluster,我們再來看看 API Portal 這一塊。
API Portal 在系統(tǒng)部署時,獲取部署的源文件,自動解析這些代碼,然后根據(jù)代碼里面的注釋和標注自動生成 API 文檔,這個文檔以 Swagger 方式存儲。
剛開始我們以 Swagger 的原生界面去展示這個界面,前端開發(fā)不太習慣這個界面,于是我們就用前端喜歡的,即作為各種表格進行展示來給他們使用。上面的表格展示方式,就是因為這個開發(fā)出來的。
那 Swagger 原生的界面還需要嗎?API Portal 上有一個功能, Try it Out(就是試一試),具體就是后端開發(fā)采用這個功能看看后端 API 吐的數(shù)據(jù)長得怎么樣,不對就修改后端 API,直到滿意為止。
前端也使用這個功能,看看沒有實際數(shù)據(jù)又是怎么樣的。Try it Out 功能就采用了 Swagger 原生的界面,后端反而比較喜歡這個頁面,因此保留下來。這樣前后端的用戶體驗,也都能滿足,大家各取所需。
下面是一個 Swagger 文檔界面:
Mock Server 流程
Mock Server 是什么?看看圖中這個場景,SOA Client 要對 Service Provider 進行測試。
Service Provider (就是被測試的對象 Server Under Test )依賴外部的服務(wù),比如 Server Cluster 1,但是外部的服務(wù)因為其他情況不能用,我們就用 Mock Server 來模擬 Server Cluster 1。
這樣依賴問題解決了,SOA Client 對 Service Provider 就能正常進行測試了。
使用 Mock Server 還可以解決,依賴服務(wù)需要返回特定的場景,但又不好操作,這樣通過 Mock Server,寫不同的 Mock Case 進行返回,就比較方便。
實際情況下 SOA 環(huán)境里面有很多依賴關(guān)系,但對方的接口沒有確認或者環(huán)境不好怎么辦?
我們通過 Mock Server 把這些服務(wù)全部屏蔽掉,這樣只需要測試我們要測試的服務(wù)就好,這對 SOA 環(huán)境解決依賴問題是挺有效的。
Mock Server—自動解析
餓了么有 Maven 私服,各個 SOA 服務(wù)之間通過 Maven 私服上的 API 進行調(diào)用。
上圖是我們的 Mock Server 界面,從圖中可以看到左邊是輸入依賴服務(wù)的接口 Maven 依賴,基本上操作就是延續(xù)之前 SOA 調(diào)用的一些流程,把外部依賴填到上面的框里頭,在 Mock Server 指定依賴的接口。
于是 Mock Server 就能從 Maven 私服去拉這些依賴,自動分析它到底有哪些類,分析好這個類,包括這個類里面有哪些方法都全部顯示出來。
上圖右邊的方法就是自動分析,黃色標識的那個方法,就是想進行 Mock,點擊下右邊的加號,就創(chuàng)建了 Mock Case。這個 Mock Case 名字就是測試任意參數(shù)。
自動生成 Mock Case
Mock Case 是說當 Mock Server 接收的數(shù)據(jù),即請求的參數(shù)和 Mock Case 里設(shè)定的 Input 匹配,那么這個 Mock Case 里設(shè)定的 Output 就作為響應(yīng)返回。
剛才創(chuàng)建了一個 Mock Case 叫做測試任意參數(shù), Mock Case 的值是根據(jù)分析的 Model(數(shù)據(jù)定義),自動生成。
比如 Input "type" : Integer,我們沒有加入 enum[234567] 的時候,就意味著只要是請求里包含任何整數(shù),該 Mock Case 就會被命中,返回 Output 的內(nèi)容。
如果加入了 enum [234567],那么只有請求參數(shù)里是 234567,該 Mock Case 才會命中。
Output 支持函數(shù),上面 Preview 就可以看到執(zhí)行 Output 的表達式之后是什么結(jié)果,當該 Mock Case 被命中時,這個 Preview 的結(jié)果就作為響應(yīng)返回。
前后端開發(fā)分離
后端開發(fā)根據(jù) PRD,通過在代碼接口里添加標注和注釋,就完成了 API 定義。
把代碼 check in,構(gòu)建系統(tǒng)知道這個變化后,然后把這個變化通知到 API Portal,就自動生成了 API 文檔,后端在 API Portal 上通知前端,雙方通過API Portal 討論確認 API 文檔。
前端根據(jù)這個文檔,通過 API Portal 上提供的 Mock,完成前端的開發(fā),更復(fù)雜的交互需要構(gòu)造后端 API 產(chǎn)生不同的數(shù)據(jù)。
前端通過構(gòu)造不同的 Mock Case,完成這些復(fù)雜的開發(fā)。開發(fā)完成之后,就去進行其他事情,在后端完成 API 開發(fā)之后,通知前端一起進行聯(lián)調(diào)。
在以前,前后端不分離的話就會相互等待,前端開發(fā)等后端實現(xiàn) API,吐出數(shù)據(jù)之后再進行,對前端開發(fā)體驗不好,現(xiàn)在這樣前端就可以一氣呵成把它開發(fā)完成,不用等候后端了。
現(xiàn)在,后端這邊也開發(fā)完了,說一個時間大家聯(lián)調(diào)一下就 OK 了,這就是整個前后端開發(fā)分離的流程。
應(yīng)用實踐
這個流程在我們配送范圍的項目里已經(jīng)應(yīng)用起來了,這里我們給出了一個迭代的統(tǒng)計,這個迭代可以在開發(fā)的過程中就看到使用的情況。
比如原來估計工時 5 天,現(xiàn)在 3 天就做完了,后端也節(jié)省一些時間,以前他們說,不然就等前端調(diào)用后告訴他們什么樣子,或者他自己寫一些測試腳本去看是什么樣的數(shù)據(jù)。
這方面他就寫了 API,通過 API Portal 就能看到后端 API 吐的數(shù)據(jù)是否正確,這樣的話后端開發(fā)也減少了開發(fā)時間。
還有,后端以前要寫 Web API 代碼,還要進行部署,現(xiàn)在也不用寫了,也不用部署了。
以前聯(lián)調(diào)時間都比較長,是兩天時間,因為那時聯(lián)調(diào)是包括開發(fā)時間,他不確定數(shù)據(jù)對不對。
所以有些處理還沒有寫,等他看到數(shù)據(jù)以后再去開發(fā)出來,時間就稍微長一些。
聯(lián)調(diào)的時間,只需半個小時,看這個事情通不通就好了,整個開發(fā)體驗還是不錯的,總體來看,整個開發(fā)時間減少了 50% 左右。
問題真的解決了嗎?
有了這些產(chǎn)品,我們看最早提到的那些問題是不是都解決了。假如今天寫業(yè)務(wù),我們是通過統(tǒng)一的方式介入,把它下沉到 SOA,因此業(yè)務(wù)也不會分散到各個地方。
我們在定義 API 時,也給了一個缺省的 API 生成方式,就是使用 Json RPC,自動將方法簽名等作為 URL。
另外也提供了 Mock 服務(wù),幫助前后端開發(fā)分離,還有就是代碼即是文檔這一塊。然而這些問題都解決了,真的解決了嗎?
其實還遠遠沒有,還有更多事情要解決:
- 全鏈路環(huán)節(jié)比較多,出現(xiàn)問題時如何快速定位?
- 故障發(fā)生時:能夠自動把現(xiàn)場保留下來嗎?能夠執(zhí)行基本分析,把分析的結(jié)果保存下來嗎?能夠自愈嗎?
- 能夠快照后臺服務(wù)及數(shù)據(jù),通過 Docker 環(huán)境,通過之前記錄的 traffic,自動化完成回歸測試嗎?
- 采用 Async Web,提高性能?采用 GO?
- 當有一些業(yè)務(wù)需求:現(xiàn)有 API 相互組合就可以完成這個需求,還是需要開發(fā)?需要智能分析所有 API 業(yè)務(wù)屬性嗎?需要面向業(yè)務(wù)開發(fā)提供搜索和推薦?
關(guān)于團隊思考
我們一直在想,面向開發(fā)、測試、運維角色,如何提供一個更好的服務(wù)?也就是說怎么樣更懶、更自動化。
比如在 API 的運維上,我們能不能做到對于接入用戶方完全無感知?還有就是我們的 API Everything 團隊,面對多個接入方同時進行,我們應(yīng)怎樣處理,自動化?
其實我們不希望太多重復(fù)的事情需要重復(fù)去做,而是考慮怎么通過工具把我們自己給解放出來,去做更多自動化的事情,首先解放自己,才能拯救用戶。
最后我們也希望從根上去思考問題,去解決問題。
梁向東,餓了么北京技術(shù)中心技術(shù)創(chuàng)新部、研發(fā)總監(jiān),目前負責餓了么 API Everything(API 接入 SOA 系統(tǒng)的框架)開發(fā)、有菜業(yè)務(wù)(餐廳采購食材和供應(yīng)鏈管理)系統(tǒng)研發(fā)及新零售物流配送系統(tǒng)研發(fā);曾在亞馬遜從事物流配送系統(tǒng)研發(fā),曾和清華校友創(chuàng)立游戲公司,進行 MMORPG 游戲的研發(fā)和運營;曾在美國優(yōu)利系統(tǒng)中國有限公司從事金融,交通運輸行業(yè)的系統(tǒng)研發(fā)。深刻了解業(yè)務(wù),善于從業(yè)務(wù)角度,通過技術(shù)去解決問題。從業(yè)務(wù)需求到產(chǎn)品設(shè)計,從技術(shù)解決方案到實現(xiàn),從系統(tǒng)上線到推廣使用,具有豐富的經(jīng)驗。