Rest API 的那些事兒
一、前言
在軟體行業快速發展的今天,傳統的軟體授權已經不能足以滿足一個IT類的公司的發展。雖然在大部分公司裡,它還是現金池的直接源頭。但是在可遇見的未來,受摩爾根理論的失效、物聯網的發展等影響,應用的架構會越來越趨於簡單化,架構越來越傾向於分散式水平擴充套件,對外的服務提供也會越來越SaaS化。在這種大背景下,很多公司都開始提供所謂的開放平臺。
查閱各個大公司的開放平臺,我們不難發現,都是Rest API,都是HTTP請求,響應報文都是大同小異的XML或者是JSON等眾多雷同的特點。這是為什麼呢?讓我們嘮嘮API平臺的那些事。
二、定義
檢視歷史,我們驚訝地發現,其實Rest的概念早在2000年就被人提出。用一句話描述它,就是用固定的URI和可變的引數訪問某個服務,來完成一系列業務請求。
- 每一個URI代表一種資源;
- 客戶端和伺服器之間,傳遞這種資源的某種表現層;
- 客戶端通過幾個HTTP動詞,對伺服器端資源進行操作,實現"表現層狀態轉化"。
2.1 Rest API 格式
Rest API,無論它的名字多麼高大上,它本質還是一個HTTP請求,POST也好,GET也罷,都是不同的資料提交方式。所以,能夠決定一個Rest API的也就:URI、引數、請求方式、請求頭等。
我們一般用URI
來定義希望對外暴露的服務。結構基本類似 schema://yourCompanyDomain/rest/{version}/{application}/{someService}
。schema
可以是http
,也可以是https
,version
指的是你這個API的版本,application
一般會指向底層的某個子系統,someService
就是這個子系統對外提供的服務。當然,如果按照業務為邊界劃分,也可將業務維度相同但隸屬於底層不同的系統的服務定義為一個application
。
對於這種Rest請求,常見的響應結果就是XML或者是JSON形式,往往結果中會包含請求狀態,和時間戳,業務系統響應結果。
具體的格式約定,可以看底部的參考文獻。
2.1.1 API的版本概念
在 URI
的格式定義中,我們包含了version這個欄位,這在早期,其實被認為是不優雅的方式。阮一峰有一篇文章就專門抨擊這種設計,後面他又自己打臉說還是拼接version的好。(不知道是不是因為Github的設計緣故)
API
設計時常會考慮版本這個概念,無論是在URI還是在請求引數裡面,至少有一個地方得指明含版本,為什麼呢?
這很簡單,就和系統迭代一樣,API也是快速迭代開發的。也許初期,你的老大腦袋一熱,我們要上API,於是你們就加班加點做,設定了一版請求協議。當時你們想,寫完就能賺錢,必然帶來一堆問題,比如說,程式碼難以維護,功能單一。後面這個API
的第二版,你們要基於它去做一些新的變更,比如請求引數多了,返回內容多了,必然就有了第二版。但是又不能影響現有的業務。這個API
基本的URI
沒變,但是會同時存在兩個版本,老使用者繼續請求舊版,沒問題,你無需動舊版?有需求的新使用者,你要請求新版才能提供服務。這樣通過版本來區分了不同的服務,便於以後的升級和維護。(就像BAE那貨可以毫無壓力地廢棄掉2.0的API)
所以一開始設計API時,就要定義好版本。其次,版本化可以騙錢。我們可以滿懷惡意地猜測,1.0為了搶使用者,免費。2.0老牛逼了,你用的爽了,想更爽,付費。233333333
2.2 架構特點
API平臺的架構,其實和底層公司的業務系統架構有著密切的關係,基於一個好的系統架構寫一個Rest API平臺基本是水到渠成的。我們先從業務系統的架構變遷說起,再來分析上面的API平臺。
2.2.1 業務系統架構變遷
基本的軟體公司的業務系統,一開始都是單機,單節點,單庫
。慢慢隨著業務量的增加。這個系統越來越複雜,機器效能越來越不能滿足需求。於是,第一種可能,領導說,換機器。上一臺牛逼機器。但是機器效能有限,越牛逼的機器價格越貴,有時候都能買3~5臺現有配置機器。於是就有了方案二,三個臭皮匠賽過一個諸葛亮。單應用,多機器,多節點
。
但是慢慢過了幾年。你發現,這程式碼寫的越來越屎,越來越複雜。基本上新來的開發要熟悉好幾個月的業務。就程式碼因為快速上線。一堆坑,無法改。於是,大家現在都在做的事情,就是拆分。也就是現在常說的SOA
。拆分,也有拆的好的,拆的不好的。不好的,就是一個大的噁心系統,變成了一堆噁心的小系統,互相呼叫,成一團亂碼。小系統看似很好。但是某個不起眼的小系統。一掛,那麼全部的系統都癱了。這個時候這個萬年不維護的小系統還找不到負責人,他麼早就滾了。這就是拆分的技術欠債,你無法避免。
那麼,就談到拆分的架構設計。其實這塊分兩個架構,技術架構和業務架構
。技術架構,就是要分清技術系統和業務系統
。技術系統也可能是一個業務系統,但它一定是一個通用的服務元件。它提供的服務無任何定製需求,就是純簡單服務。比如,發簡訊發郵件發微信的通知系統,它就是通知。你業務有何特殊需求,就在上面自己實現一個XXX通知系統,那麼業務系統的拆分,才是最關鍵的。就是要劃清邊界
。
這個邊界問題很可怕,什麼你該做,什麼你不該做。每個系統的職責都要明確。不要你也實現一個他也實現一個,然後相互調。這種可怕性就是在兩個服務都癱瘓的時候,完全都無法啟用。最後你的系統架構變成了一個通用技術元件系統,完成各個基礎服務,每個產品線,業務端,基於你的技術系統包裝出業務定製化服務系統,然後最上層就是業務子系統。業務子系統組合在一起,就是一個大的業務系統,也叫服務化平臺。
這個時候,你需要做開放平臺。暴露一套Restful API,就是水到渠成了。
PS,實際的架構遠比這個複雜,截圖選自《大型網站系統與Java中介軟體實踐》。
2.2.2 基於不同系統架構的 API 平臺
1、演變
初期的API平臺往往是上圖左側那種,某個龐大的業務系統希望暴露一套API,於是大家就在這個系統上做,直接設計一套協議。但是,這樣子帶來的缺點十分明顯,第一,它與業務聯絡太重,理想的API平臺是通用的,不是隻給你設計一個。第二,它不好擴充套件,每次變更都得和業務系統一同上線,糟糕的情況下程式碼還會影響原有正常的業務。第三,效能問題,理論上會降低原有應用的效能。
這種情況下,如果應用部署了多臺機器,多個節點,我們就可以獨立出來。也就是右邊所示的API Gateway,它做的事情本質上就是反向代理,將外部的請求校驗完合法性之後反代至內部實際想要對外暴露服務的服務叢集上。
所以,這種場景下,API Gateway也就如名稱所說,就是一個入口。實際的Rest API的東西還是建立在各個業務子系統上,只是只需要提供最簡單的服務,無需考慮授權等東西。使用者管理,API註冊釋出,呼叫統計等,均由API Gateway實現處理。對於想要快速上線的開發人員而言,實在是一個不錯的福音。
然而,當系統應用拆分到了SOA化之後,API的架構由有了新的變革,我們有了註冊中心的概念。因為SOA化,所以每個業務子系統其實都有了對外的統一介面,有了ESB(註冊中心)。實際的內部系統間請求也有了較好的路由、熔斷等策略。
在這種大背景下,API平臺對外暴露的Rest API無需底層的業務專門開發了。直接使用現有的內部介面,選擇性暴露即可。問題點就在於,如何根據定義的Rest API請求,實際模擬內部的RPC協議請求。
某種程度上,這時候的API平臺,已經不僅僅是HTTP Rest請求了。我們完全可以實現相同RPC協議的透傳,比如你就是一個Hessian介面想對外暴露,我只需包上一層認證,直接註冊於API Gateway,外部Hessian請求直接透傳至內部子系統。
在這個基礎上的 Rest API 平臺,才是靈活的,可擴充套件的,易於維護的。然而有得必有失,Mock請求必然會有效能上的損耗,但是這個架構的公司,已經不在乎錢了,上10臺虛機,不夠加唄。
2、特點
- C/S結構
- 無狀態(API平臺無需儲存業務狀態,只做認證和轉發)
- 有快取,API會對指定URI的請求轉發做快取,保證併發性,業務系統也對同樣的請求針對性快取。
- 結構分層,每層間無法直接訪問。
API平臺的背後,就是龐大的各個業務子系統。每個API,就相當於一個業務子系統。API平臺要做的事,就非常清晰和簡單。就是業務子系統註冊釋出API,對外部請求校驗計費,模擬請求內部業務子系統,對子系統結果包裝序列化為JSON
返回。
2.3 互動流程
上面是一個簡單的互動,簡單顯示了外部系統和內部系統通過Rest API的互動過程:開發者(企業)註冊,申請APP_KEY,開通API。按照開發接入,請求籤名。轉發至後端呼叫返回結果,API平臺計費(預付費或者後收費),統計呼叫情況。
外部通訊本質上還是HTTP
,那麼必然存在了授權問題,生產的API平臺是直接暴露於公網的,如果認證授權策略出現紕漏,影響是可怕的。
比如,這個API是群發簡訊,你要是沒有好的授權體系,允許人隨意推送。某個人想搞你,呼叫釋出反共資訊,你整個公司都會跨。HTTP
協議本質上沒有這一塊的內容,所以我們必然要在這上面考慮安全策略的內容。
2.3.1 如何保證Rest API的安全性
如果單純考慮加解密,或者簽名方式來保證請求合法,其實是遠遠不夠的。事實上,一個安全的API平臺往往需要多方面一起考慮,保證請求安全合法。
1、是不是實際客戶端的請求?
- 設計專門的私有請求頭:定義獨有的Request headers,標明有此請求頭的請求合法。
- 請求包含請求時間:定義時間,防止中間攔截篡改,只對指定超時範圍內(如10秒)的請求予以響應。
- 請求URI是否合法:此URI是否在API平臺註冊?防止偽造URI攻擊
- 請求是否包含不允許的引數定義:請求此版本的這個URI是否允許某些欄位,防止注入工具。
- 部分競爭資源是否包含呼叫時版本(Etag):部分競爭資源,使用If-Match頭提供。如使用者資金賬戶查詢API,可以返回此時的賬戶版本,修改扣款時附加版本號(類似樂觀鎖設計)。
2、API平臺是否允許你呼叫(訪問控制)?
訪問控制,主要是授權呼叫部分。API都對外暴露,但是某些公共API可以直接請求,某些,需要授權請求。本質的目的,都是為了驗證發起使用者合法,且對使用者能標識統計計費。
以HMac Auth為例,我們簡單設計一個簽名演算法。開發者註冊時獲取App Key、App Secret,然後申請部分API的訪問許可權,發起請求時:
- 所有請求引數按第一個字元升序排序(先字母后數字),如第一個相同,則看第二個,依次順延。
- 按請求引數名及引數值相互連線組成一個字串。param1=value1¶m2=value2...(其中包含App Key引數)
- 將應用金鑰分別新增到以上請求引數串的頭部和尾部:secret + 請求引數字串 + secret。
- 對該字串進行 SHA1 運算,得到一個二進位制陣列。
- 將該二進位制陣列轉換為十六進位制的字串,該字串為此次請求的簽名。
- 該簽名值使用sign系統級引數一起和其它請求引數一起傳送給API平臺。
服務端先驗證是不是實際客戶端的請求
,然後按照App Key查詢對應App Secret,執行簽名演算法,比較簽名是否一致。簽名一致後檢視此App Key對應的使用者是否有訪問此API的許可權,有則放行。
執行成功後包裝返回指定格式的結果,進行統計計費。
三、需求與實現
3.1 系統需求
3.1.1 目標
- 支援rest類API介面動態釋出及運營,包括但不限於:
- 安全認證
- 會話管理
- 流量統計及限流
- 計費收費
- 熔斷
- 支援現有子系統RPC協議的API動態釋出及運營,外部請求透傳。
- 支援json、xml響應報文,可以請求時選取所需報文格式。
- 支援動態直接將後端SOA服務暴露為API。
- 支援動態將普通Web介面暴露為API。
- 支援動態將MQ服務暴露為API。
- 支援多個服務組合編排後暴露為API。
3.1.2 業務需求
1、API管理
所有API可後臺查詢管理,包括動態釋出、引數對映配置、後端服務介面配置、API禁用、啟用,多版本、分組、分級別等。
2、應用管理
後臺管理開放平臺接入的應用(第三方應用),包括查詢、禁用、啟用、稽核。
3、API鑑權&授權
- 應用申請稽核通過後生成公鑰,開放平臺需提供支援分散式系統的金鑰管理
- 服務可設定為兩個安全等級:需授權訪問和無需授權訪問(後者即任意客戶端都可以發起呼叫),預設所有API都需授權訪問。
- 非正常狀態(禁用、停用、黑名單等)的應用直接拋異常不允許訪問——熔斷機制
- 呼叫次數、呼叫頻率、併發數可執行時控制,避免某請求量過大影響其他應用的呼叫。
- 可對某個應用某個API設定強制熔斷,所有請求無視閥值直接丟擲異常。
- 易用性
- 與SOA整合,SOA服務一鍵釋出到API平臺。
- 支援後臺動態釋出API,而不是新上一個API就需上線一次。
4、計費統計
- API的呼叫統計,每筆請求時間,響應時間,響應狀態。
- API的計費計算,按照請求量和請求資源計費,實現多種計費模型。(預付費,後收費。按量,按時間週期。)
5、開發者平臺
- API開發者平臺,開發者註冊、訪問、申請API授權、計費統計、呼叫統計。
- API文件系統,詳細的API文件展示,SDK下載,使用者登入後還可專門生成不同程式語言請求,線上模擬請求結果等。
3.1.2 角色定義
1、外部使用者
使用者 | 做什麼 | 使用目的 |
---|---|---|
API平臺接入方 | 接入API平臺 | 使用XXXX提供的開放平臺服務 |
2、各個業務產品線
使用者 | 做什麼 | 使用目的 |
---|---|---|
各個業務產品線 | 作為外部應用接入API平臺 | 使用XXXX提供的開放平臺服務 |
各個業務產品線 | 提供服務 | 提供後端服務,釋出到API平臺供外部應用接入 |
公司後端應用 | 提供服務 | 提供後端服務,釋出到API平臺供外部應用接入 |
API平臺 | API治理 | 運營,管理API、第三方應用等 |
3.2 請求模型
API 的所有服務請求域名是相同的,區別在於Request Path等。請求引數分為系統級引數和業務級引數兩部分,系統級引數是所有 API 都擁有的引數,而業務級引數由具體服務 API 定義。
3.2.1 統一服務 URL
建立API Gateway接受所有請求,按照Request Path,Request Method,Request Head分發所有的請求。
1、 通用統一URL
格式:schema://<API Gateway URI>/DispatcherServlet?method=XXService.xxMethod?xxxObj.xxxParam=xxxValue。
說明:所有請求直接走DispatcherServlet
分發,所有內容均定義於URL引數中。method
為後端某個子系統的某個方法。xxxObj.xxxParam
為方法引數實體的某個屬性的值定義。
示例:
http://api.xxxxx.com/router?method=SMSService.sendSMS&user.phoneNumber=18888888888&sign=ds234324sdsad&date=20151229231232
2、Rest型別URL
格式:schema://<API Gateway URI>/rest/{version}/{service}/{method}/{params}
說明:請求按照Gateway定義的Rest地址匹配,動態對映至具體系統具體方法,模擬呼叫。請求中包含version
欄位。
示例:
http://api.xxxx.com/rest/v1/XXService/xxMethod/{xxParam}
http://api.xxxx.com/rest/v1/XXService/xxMethod?xxxParam=xxxValue
3.2.2 引數設計
1、系統級引數
系統級引數是由 API 平臺定義的一組引數,每個服務都擁有這些引數,用以傳送框架級的引數資訊。如我們前面提到的 method 就是一個系統級引數,使用該引數指定服務的名稱。
2、業務級引數
業務級引數,顧名思義是由業務邏輯需要自行定義的,每個服務 API 都可以定義若干個自己的業務級引數。API Getaway 根據引數名和請求屬性名相等的契約,將業務級引數具體的方法請求物件中。
3.3 常見框架
- Kong:https://github.com/Mashape/kong
- Zuul:https://github.com/Netflix/zuul
- ROP:https://github.com/itstamen/rop
四、優劣
4.1 好處
跨平臺,管你是
Java
,還是PHP
,還是Node.js
還是Go
,你丫都得支援HTTP
請求。我API
平臺只需要提供這個語言的SDK
,保證能按照訊息協議呼叫就好。將複雜的內部業務系統抽象為通用呼叫請求。包裝了複雜的業務邏輯,對外提供統一的,好管理的介面。並可以定製化設計,計費,授權一類的容易管理。
4.2 壞處
協議描述能力弱化,
Restful
的URI
無法完全對請求引數做強格式校驗。最後的方法引數繫結,模擬內部請求時往往容易出問題,尤其是以Java等強格式語言的系統。不能像WebService
一樣清晰描述請求報文。同樣的道理,響應結果為了是
JSON
、XML
。這當中,編碼,正反序列化,等操作,往往就會有效能瓶頸。而且,Java在這塊資源消耗極大。以Github的ROP
這個框架為例,當年測試時,它在併發請求過高的時候就會有一個記憶體洩漏問題。
附:參考資料
相關文章
- webpack的那些事兒Web
- Ubuntu的那些事兒Ubuntu
- babel那些事兒Babel
- PHP那些事兒PHP
- OAuth那些事兒OAuth
- Git那些事兒Git
- https的那些事兒HTTP
- 面試的那些事兒--01面試
- TCP 的那些事兒(下)TCP
- TCP 的那些事兒(上)TCP
- 聊聊viewport那些事兒View
- Java字串那些事兒Java字串
- 漏洞檢測的那些事兒
- 雲原生java的那些事兒Java
- HTTP 快取的那些事兒HTTP快取
- iOS 截圖的那些事兒iOS
- Android Drawable的那些事兒Android
- C語言那些事兒C語言
- MySQL優化那些事兒MySql優化
- PHP 閉包那些事兒PHP
- 字元編碼那些事兒字元
- 網路安全那些事兒
- 依賴注入那些事兒依賴注入
- IT專案管理那些事兒專案管理
- Hadoop搭建那些事兒Hadoop
- Filebeat 收集日誌的那些事兒
- [apue] 等待子程式的那些事兒
- 聊聊瀏覽器的那些事兒瀏覽器
- JavaScript 中 setTimeout 的那些事兒JavaScript
- 七牛與 Ueditor 的那些事兒
- Weex 事件傳遞的那些事兒事件
- 「前端那些事兒」④ 效能監控前端
- 程式碼重構那些事兒
- Node檔案操作那些事兒
- 物件導向 - Java那些事兒物件Java
- 檔案上傳那些事兒
- Java日誌框架那些事兒Java框架
- 技術部落格那些事兒