服務API版本控制設計與實踐
作者:vivo網際網路伺服器團隊-Song jie
一、前言
筆者曾負責vivo應用商店伺服器開發,有幸見證應用商店從百萬日活到幾千萬日活的發展歷程。應用商店客戶端經歷了大大小小上百個版本迭代後,服務端也在架構上完成了單體到服務叢集、微服務升級。
下面主要聊一聊在業務快速發展過程中,產品不斷迭代,服務端在相容不同版本客戶端的API遇到的問題的一些經驗和心得。一方面讓團隊內童鞋對已有的一些設計思想有一個更徹底的理解,另一方面也是希望能引起一些遇到類似場景同行的共鳴,提供解決思路。
二、通用解決方案
應用商店客戶端迭代非常頻繁,釋出新的APP版本的時候,勢必導致出現多版本,這樣服務端就會導致多個不同的客戶端請求。強制使用者升級APP,可能會導致使用者流失,因此採用多版本共存就是必須的。以下是業界討論過的的一些SOA服務API版本控制方法參考[1]。在實際開發中原則上離不開以下三個方案。
方案一:The Knot 無版本——即平臺的API永遠只有一個版本,所有的使用者都必須使用最新的API,任何API的修改都會影響到平臺所有的使用者。(如下圖1)
方案二:Point-to-Point——點對點,即平臺的API版本自帶版本號,使用者根據自己的需求選擇使用對應的API,需要使用新的API特性,使用者必須自己升級。(如下圖2)
方案三:Compatible Versioning——相容性版本控制,和The Knot一樣,平臺只有一個版本,但是最新版本需要相容以前版本的API行為。(如下圖3)
(引用自: The Costs of Versioning an API )
簡單分析,The Knot只維護最新版本,對服務端而言維護有一定簡化了,但是要求服務使用者及時適配最新的版本,這種做法不太適用使用者產品,目前內部服務比較適用。Point-Point針對不同客戶的版本提供獨立的服務,當隨著版本的增加開發和運維的維護成本會增加,這種在後面我們面對“協議升級”的時候有使用。
方案三應該是最常用的情況,服務端向後相容。後面案例也主要採用這種思想,具體的做法也是有很多種,會結合具體的業務場景使用不同策略,這個會是接下來討論的重點。
三、具體業務場景面臨的挑戰和探索
3.1 The Knot 無版本和Point-to-Point模式的應用場景
上圖是我們應用商店迭代變化的一個縮影,業務發展到一定階段面臨以下挑戰:
1)業務發展前期,作為服務提供方,服務端不僅要支撐多個版本應用商店客戶端,同時服務於軟體側的PC助手;
2)產品形態變化多樣,服務端介面變更和維護面臨多版本客戶端相容的挑戰;
3)架構邏輯上,服務端採用早期傳統架構,開發和維護成本比較高;服務端與客戶端進行互動的協議優化升級;以及服務拆分勢在必行。
所以服務端協議、框架升級以及公共服務拆分是首要解決的方向。改造經歷了兩個過程:
-
階段一新版本新的介面一律採用新的JSON協議;已有功能介面進行相容處理,根據客戶端版本進行區分,返回不同協議的格式內容。
-
階段二隨著業務迭代,新的版本商店依賴的所有介面都完成了協議升級後,為了提升服務的穩定性,舊的協議效能無法明顯提升,一方面升級後端架構和框架,提升開發效率和可維護性。同時拆分和獨立新的工程,實現歷史工程只提供給歷史版本使用。我們針對大流量高併發、以及基礎服務場景比如首頁、詳情、下載進行獨立服務獨立拆分。同時也提取一些公共的內部RPC服務,比如獲取應用詳情、過濾服務等。
經過改造,服務端架構如上圖所示。
1)至此Old-Service後續只用進行相應的維護工作即可,對應Point-to-Point版本。
2)內部的RPC服務由於只提供內部服務,服務端和客戶端可以隨時同步升級,只要維護最新的版本就可以,採用The Knot模式。這裡需要注意的是服務的升級需要注意保持向下相容,在擴充套件欄位或者修改欄位的時候需要特別小心,不然可能在服務升級的時候會引起客戶端呼叫的異常。
3.2 Compatible Versioning:相容性版本控制
相容性版本控制應該是最常見的版本控制方式,特別是在C/S架構當中,具體的相容性版本不同的策略總結有API版本、客戶端版本號、功能引數標誌等。
場景一:API版本號控制
隨著網際網路發展的,使用者體驗要求也是越來越高,產品形式也會隨之每年有不一樣的變化。除了避免審美疲勞外,也是在不斷探索如何提升屏效、點選率和轉化。就拿應用商店首頁列表舉例。
應用列表在形態上經歷過單一的 應用雙排 -> 單排 -> 單排+穿插 的佈局。內容上也經歷了不同商業化模式、人工排期到演算法等演進。
每個版本介面內部邏輯變化是十分大的,有明顯差異。如果只是簡單在service層根據版本進行判斷處理,會導致處理邏輯會變得異常複雜,並且還可能導致對低版本產生影響。同時商店首頁是十分重要的業務場景,結合風險考慮,類似這樣對場景,在介面URL上新增版本欄位,不同對版本使用不同的值,在控制層根據不同的版本進行不同的處理邏輯會更加合理,簡單有效。具體策略也有比如在URL上新增介面版本欄位/{version}/index、請求頭攜帶版本引數等。
場景二:客戶端版本號控制
類似首頁列表,商店的穿插Banner也經歷了多個版本的迭代。如下圖所示。這些穿插樣式都是在不同版本下出現的,在樣式佈局,支援跳轉能力等方面各個版本的支援程度不一樣,介面返回時需要進行相應的處理適配、過濾等處理。
這類場景如果採用場景一的方案升級新的介面也能夠解決,但是會存在大量重複程式碼,而且新增介面對於客戶端介面改造、特別是一些介面路徑會影響到大資料埋點統計,也是有比較高的溝通和維護成本在裡面。
為了提升程式碼複用性。使用客戶端版本號控制是首選考慮策略。但是需要注意,如果只是簡單的在程式碼層面根據客戶端版本號進行判斷,會存在以下問題需要考慮:
1)程式碼層面會存在各種判斷,造成的程式碼可讀性差,有沒有更加優雅的方法;
2)存在一個客觀情況。那就是客戶端的版本號是存在不確定性的。由於客戶端採用火車釋出模式 參考[2],多版本並行開發,導致版本號存在變動、版本跳躍不連續的情況時有發生,也給服務端開發帶來了不少困擾。
如何思考解決這些問題呢?其實對於不同的產品形態涉及的一些資源或者產品模組本身出現在不同的迭代週期,可以認為他們具備了版本或者時間的屬性。站在程式設計師視角,把某個資源支援對應的客戶端版本作為這個資源物件的一個成員屬性。每種資源具有這種屬性後,也有相應的邏輯行為來對應成員方法---根據屬性進行過濾。這樣的設計賦予資源了屬性和行為後,資源具備了統一的、靈活的過濾能力,而不再是簡單的硬編碼根據版本進行if-else判斷。
有了方案後,實施起來就比較容易了。開發分配資源ID,並且設定對應支援客戶端版本範圍。過濾邏輯統一到資源物件。
程式碼層面可以將過濾邏輯統一封裝到一個工具類(示例程式碼),在各個業務介面返回進行過濾。更加優雅的方案是建立統一的資源上層類,封裝資源過濾方法,所有資源位的資源物件實現該上層類,統一在獲取資源邏輯完成過濾能力。
場景三:新增功能標識引數
應用商店業務主要提供使用者發現和下載新應用、更新手機已安裝的應用。商店有增量更新可以減小更新包體積,因此也叫省流量更新,有效提升使用者體驗。前期我們使用開源的增量演算法,但是發現該演算法在部分機器合成拆分包會耗時很長,甚至引起crash。
於是專案組尋求更加高效拆分演算法。類似在這些已有介面的進行功能增強的場景,除了提供新的API或者內部簡單通過客戶端版本判斷進行擴充套件外,有沒有更好的方案呢?因為除了這些方案已知的弊端外,需要從長遠考慮,比如前面提到的演算法,後續還會不會存在升級的可能,下載介面會不會有更多能力的增強。
結合上面思考,在原來介面基礎上新增 標誌引數 欄位,表示該請求發出的客戶端支援的能力。為了後續擴充套件,欄位型別為整數值,不只是簡單的boolean,服務端通過 位運算完成判斷邏輯 。客戶端也擺脫某個功能與版本的強一致性,不用去記錄某個版本具有某種能力。
四、關於介面設計的更多思考
最後補充一些踩過的坑和反思。服務端在提供介面時,不能僅僅關注介面的實現,更多的時候需要關注介面的使用方,他們使用的場景、呼叫時機等等。否則開發在對介面問題排查、維護花費的時間會比實際開發的耗時要多上好幾倍。
1) 場景化 :具體到什麼是場景化呢,拿商店客戶端的幫助使用者檢測手機安裝的應用版本是否最新的服務舉例,檢測時機是存在不同的場景的,比如使用者啟動、使用者切換wlan環境、定時檢測等。當需要進行精細化分析,哪些請求是有效的,哪些會引起集中請求時,這個時候如果請求上沒有場景區分,那麼分析將無從下手。所以在與客戶端溝通介面設計時,請帶上場景這個因素。介面設計上可參考如/app/{scene}/upgrade,定義好各個場景名稱,在路徑上帶上具體的場景,這樣對線上不同來源請求量級、問題分析都會有很大好處。
2) 鑑權和服務隔離 :除了場景需要考慮外,介面呼叫在分配時做好記錄和鑑權以及服務隔離。比如商店的部分介面服務不僅提供給客戶端,同時也會提供給手機系統應用呼叫。目前vivo上億的存量使用者體量,這裡要十分小心,系統應用的呼叫量控制不當,併發可比商店本身要大的多。首先前期與服務呼叫方評估溝通、做好設計,避免出問題。即使在出問題時,也要有機制能夠快速發現問題、能夠分析出問題的來源,降低問題帶來的損失。
至此上面解決問題的思路,都與具體業務以及背景有一定關係。隨著技術不斷迭代和發展,在移動端APP頁面動態性,目前業界也有了更多高效的技術方案,比如谷歌的Flutter、Weex等。這些技術能夠實現靈活擴充套件,多端統一,效能也能夠接近native。不僅減少了客戶端發版頻次,也減少了服務端相容性處理成本。目前我們vivo也有團隊在使用和實踐。
技術不斷更迭,沒有最好的方案,只有最適合的方案。開發過程中不僅滿足當前實現,更多的是考慮到後續擴充套件性和可維護性。 開發不能一味追求高階技術,技術最終服務於業務,堅持長期主義,效率至上 。
五、參考資料
1、 The Costs of Versioning an API
2、 敏捷開發,火車釋出模式
- 事件驅動架構在 vivo 內容平臺的實踐
- vivo資料庫與儲存平臺的建設和探索
- 事件驅動架構在 vivo 內容平臺的實踐
- vivo 推送平臺架構演進
- Jetpack—LiveData元件的缺陷以及應對策略
- Jetpack—LiveData元件的缺陷以及應對策略
- vivo 推送平臺架構演進
- 前端質量提升利器-馬可程式碼覆蓋率平臺
- 前端質量提升利器-馬可程式碼覆蓋率平臺
- 版本不相容Jar包衝突該如何是好?
- DevTools 實現原理與效能分析實戰
- JDK ThreadPoolExecutor核心原理與實踐
- 版本不相容Jar包衝突該如何是好?
- DevTools 實現原理與效能分析實戰
- Kubernetes 叢集無損升級實踐
- 2021 VDC :vivo 網際網路服務億級使用者的技術架構演進之路 | 附PPT 下載
- Kubernetes 叢集無損升級實踐
- JavaMoney規範(JSR 354)與對應實現解讀
- vivo瀏覽器的快速開發平臺實踐-總覽篇
- JavaMoney規範(JSR 354)與對應實現解讀