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