作者:vivo互聯(lián)網(wǎng)服務(wù)器團(tuán)隊(duì)-Song jie
一、前言
筆者曾負(fù)責(zé)vivo應(yīng)用商店服務(wù)器開發(fā),有幸見證應(yīng)用商店從百萬(wàn)日活到幾千萬(wàn)日活的發(fā)展歷程。應(yīng)用商店客戶端經(jīng)歷了大大小小上百個(gè)版本迭代后,服務(wù)端也在架構(gòu)上完成了單體到服務(wù)集群、微服務(wù)升級(jí)。
下面主要聊一聊在業(yè)務(wù)快速發(fā)展過(guò)程中,產(chǎn)品不斷迭代,服務(wù)端在兼容不同版本客戶端的
API遇到的問(wèn)題的一些經(jīng)驗(yàn)和心得。一方面讓團(tuán)隊(duì)內(nèi)童鞋對(duì)已有的一些設(shè)計(jì)思想有一個(gè)更徹底的理解,另一方面也是希望能引起一些遇到類似場(chǎng)景同行的共鳴,提供解決思路。
二、通用解決方案
應(yīng)用商店客戶端迭代非常頻繁,發(fā)布新的APP版本的時(shí)候,勢(shì)必導(dǎo)致出現(xiàn)多版本,這樣服務(wù)端就會(huì)導(dǎo)致多個(gè)不同的客戶端請(qǐng)求。強(qiáng)制用戶升級(jí)APP,可能會(huì)導(dǎo)致用戶流失,因此采用多版本共存就是必須的。以下是業(yè)界討論過(guò)的的一些SOA服務(wù)API版本控制方法參考[1]。在實(shí)際開發(fā)中原則上離不開以下三個(gè)方案。
方案一:The Knot 無(wú)版本——即平臺(tái)的API永遠(yuǎn)只有一個(gè)版本,所有的用戶都必須使用最新的API,任何API的修改都會(huì)影響到平臺(tái)所有的用戶。(如下圖1)
方案二:Point-to-Point——點(diǎn)對(duì)點(diǎn),即平臺(tái)的API版本自帶版本號(hào),用戶根據(jù)自己的需求選擇使用對(duì)應(yīng)的API,需要使用新的API特性,用戶必須自己升級(jí)。(如下圖2)
方案三:Compatible Versioning——兼容性版本控制,和The Knot一樣,平臺(tái)只有一個(gè)版本,但是最新版本需要兼容以前版本的API行為。(如下圖3)
(引用自:The Costs of Versioning an API)
簡(jiǎn)單分析,The Knot只維護(hù)最新版本,對(duì)服務(wù)端而言維護(hù)有一定簡(jiǎn)化了,但是要求服務(wù)使用者及時(shí)適配最新的版本,這種做法不太適用用戶產(chǎn)品,目前內(nèi)部服務(wù)比較適用。Point-Point針對(duì)不同客戶的版本提供獨(dú)立的服務(wù),當(dāng)隨著版本的增加開發(fā)和運(yùn)維的維護(hù)成本會(huì)增加,這種在后面我們面對(duì)“協(xié)議升級(jí)”的時(shí)候有使用。
方案三應(yīng)該是最常用的情況,服務(wù)端向后兼容。后面案例也主要采用這種思想,具體的做法也是有很多種,會(huì)結(jié)合具體的業(yè)務(wù)場(chǎng)景使用不同策略,這個(gè)會(huì)是接下來(lái)討論的重點(diǎn)。
三、具體業(yè)務(wù)場(chǎng)景面臨的挑戰(zhàn)和探索
3.1 The Knot 無(wú)版本和Point-to-Point模式的應(yīng)用場(chǎng)景
上圖是我們應(yīng)用商店迭代變化的一個(gè)縮影,業(yè)務(wù)發(fā)展到一定階段面臨以下挑戰(zhàn):
1)業(yè)務(wù)發(fā)展前期,作為服務(wù)提供方,服務(wù)端不僅要支撐多個(gè)版本應(yīng)用商店客戶端,同時(shí)服務(wù)于軟件側(cè)的PC助手;
2)產(chǎn)品形態(tài)變化多樣,服務(wù)端接口變更和維護(hù)面臨多版本客戶端兼容的挑戰(zhàn);
3)架構(gòu)邏輯上,服務(wù)端采用早期傳統(tǒng)架構(gòu),開發(fā)和維護(hù)成本比較高;服務(wù)端與客戶端進(jìn)行交互的協(xié)議優(yōu)化升級(jí);以及服務(wù)拆分勢(shì)在必行。
所以服務(wù)端協(xié)議、框架升級(jí)以及公共服務(wù)拆分是首要解決的方向。改造經(jīng)歷了兩個(gè)過(guò)程:
-
階段一新版本新的接口一律采用新的JSON協(xié)議;已有功能接口進(jìn)行兼容處理,根據(jù)客戶端版本進(jìn)行區(qū)分,返回不同協(xié)議的格式內(nèi)容。
-
階段二隨著業(yè)務(wù)迭代,新的版本商店依賴的所有接口都完成了協(xié)議升級(jí)后,為了提升服務(wù)的穩(wěn)定性,舊的協(xié)議性能無(wú)法明顯提升,一方面升級(jí)后端架構(gòu)和框架,提升開發(fā)效率和可維護(hù)性。同時(shí)拆分和獨(dú)立新的工程,實(shí)現(xiàn)歷史工程只提供給歷史版本使用。我們針對(duì)大流量高并發(fā)、以及基礎(chǔ)服務(wù)場(chǎng)景比如首頁(yè)、詳情、下載進(jìn)行獨(dú)立服務(wù)獨(dú)立拆分。同時(shí)也提取一些公共的內(nèi)部RPC服務(wù),比如獲取應(yīng)用詳情、過(guò)濾服務(wù)等。
經(jīng)過(guò)改造,服務(wù)端架構(gòu)如上圖所示。
1)至此Old-Service后續(xù)只用進(jìn)行相應(yīng)的維護(hù)工作即可,對(duì)應(yīng)Point-to-Point版本。
2)內(nèi)部的RPC服務(wù)由于只提供內(nèi)部服務(wù),服務(wù)端和客戶端可以隨時(shí)同步升級(jí),只要維護(hù)最新的版本就可以,采用The Knot模式。這里需要注意的是服務(wù)的升級(jí)需要注意保持向下兼容,在擴(kuò)展字段或者修改字段的時(shí)候需要特別小心,不然可能在服務(wù)升級(jí)的時(shí)候會(huì)引起客戶端調(diào)用的異常。
3.2 Compatible Versioning:兼容性版本控制
兼容性版本控制應(yīng)該是最常見的版本控制方式,特別是在C/S架構(gòu)當(dāng)中,具體的兼容性版本不同的策略總結(jié)有API版本、客戶端版本號(hào)、功能參數(shù)標(biāo)志等。
場(chǎng)景一:API版本號(hào)控制
隨著互聯(lián)網(wǎng)發(fā)展的,用戶體驗(yàn)要求也是越來(lái)越高,產(chǎn)品形式也會(huì)隨之每年有不一樣的變化。除了避免審美疲勞外,也是在不斷探索如何提升屏效、點(diǎn)擊率和轉(zhuǎn)化。就拿應(yīng)用商店首頁(yè)列表舉例。
應(yīng)用列表在形態(tài)上經(jīng)歷過(guò)單一的應(yīng)用雙排 -> 單排 -> 單排 穿插的布局。內(nèi)容上也經(jīng)歷了不同商業(yè)化模式、人工排期到算法等演進(jìn)。
每個(gè)版本接口內(nèi)部邏輯變化是十分大的,有明顯差異。如果只是簡(jiǎn)單在service層根據(jù)版本進(jìn)行判斷處理,會(huì)導(dǎo)致處理邏輯會(huì)變得異常復(fù)雜,并且還可能導(dǎo)致對(duì)低版本產(chǎn)生影響。同時(shí)商店首頁(yè)是十分重要的業(yè)務(wù)場(chǎng)景,結(jié)合風(fēng)險(xiǎn)考慮,類似這樣對(duì)場(chǎng)景,在接口URL上新增版本字段,不同對(duì)版本使用不同的值,在控制層根據(jù)不同的版本進(jìn)行不同的處理邏輯會(huì)更加合理,簡(jiǎn)單有效。具體策略也有比如在URL上新增接口版本字段/{version}/index、請(qǐng)求頭攜帶版本參數(shù)等。
場(chǎng)景二:客戶端版本號(hào)控制
類似首頁(yè)列表,商店的穿插Banner也經(jīng)歷了多個(gè)版本的迭代。如下圖所示。這些穿插樣式都是在不同版本下出現(xiàn)的,在樣式布局,支持跳轉(zhuǎn)能力等方面各個(gè)版本的支持程度不一樣,接口返回時(shí)需要進(jìn)行相應(yīng)的處理適配、過(guò)濾等處理。
這類場(chǎng)景如果采用場(chǎng)景一的方案升級(jí)新的接口也能夠解決,但是會(huì)存在大量重復(fù)代碼,而且新增接口對(duì)于客戶端接口改造、特別是一些接口路徑會(huì)影響到大數(shù)據(jù)埋點(diǎn)統(tǒng)計(jì),也是有比較高的溝通和維護(hù)成本在里面。
為了提升代碼復(fù)用性。使用客戶端版本號(hào)控制是首選考慮策略。但是需要注意,如果只是簡(jiǎn)單的在代碼層面根據(jù)客戶端版本號(hào)進(jìn)行判斷,會(huì)存在以下問(wèn)題需要考慮:
1)代碼層面會(huì)存在各種判斷,造成的代碼可讀性差,有沒(méi)有更加優(yōu)雅的方法;
2)存在一個(gè)客觀情況。那就是客戶端的版本號(hào)是存在不確定性的。由于客戶端采用火車發(fā)布模式 參考[2],多版本并行開發(fā),導(dǎo)致版本號(hào)存在變動(dòng)、版本跳躍不連續(xù)的情況時(shí)有發(fā)生,也給服務(wù)端開發(fā)帶來(lái)了不少困擾。
如何思考解決這些問(wèn)題呢?其實(shí)對(duì)于不同的產(chǎn)品形態(tài)涉及的一些資源或者產(chǎn)品模塊本身出現(xiàn)在不同的迭代周期,可以認(rèn)為他們具備了版本或者時(shí)間的屬性。站在程序員視角,把某個(gè)資源支持對(duì)應(yīng)的客戶端版本作為這個(gè)資源對(duì)象的一個(gè)成員屬性。每種資源具有這種屬性后,也有相應(yīng)的邏輯行為來(lái)對(duì)應(yīng)成員方法---根據(jù)屬性進(jìn)行過(guò)濾。這樣的設(shè)計(jì)賦予資源了屬性和行為后,資源具備了統(tǒng)一的、靈活的過(guò)濾能力,而不再是簡(jiǎn)單的硬編碼根據(jù)版本進(jìn)行if-else判斷。
有了方案后,實(shí)施起來(lái)就比較容易了。開發(fā)分配資源ID,并且設(shè)置對(duì)應(yīng)支持客戶端版本范圍。過(guò)濾邏輯統(tǒng)一到資源對(duì)象。
代碼層面可以將過(guò)濾邏輯統(tǒng)一封裝到一個(gè)工具類(示例代碼),在各個(gè)業(yè)務(wù)接口返回進(jìn)行過(guò)濾。更加優(yōu)雅的方案是建立統(tǒng)一的資源上層類,封裝資源過(guò)濾方法,所有資源位的資源對(duì)象實(shí)現(xiàn)該上層類,統(tǒng)一在獲取資源邏輯完成過(guò)濾能力。
場(chǎng)景三:新增功能標(biāo)識(shí)參數(shù)
應(yīng)用商店業(yè)務(wù)主要提供用戶發(fā)現(xiàn)和下載新應(yīng)用、更新手機(jī)已安裝的應(yīng)用。商店有增量更新可以減小更新包體積,因此也叫省流量更新,有效提升用戶體驗(yàn)。前期我們使用開源的增量算法,但是發(fā)現(xiàn)該算法在部分機(jī)器合成拆分包會(huì)耗時(shí)很長(zhǎng),甚至引起crash。
于是項(xiàng)目組尋求更加高效拆分算法。類似在這些已有接口的進(jìn)行功能增強(qiáng)的場(chǎng)景,除了提供新的API或者內(nèi)部簡(jiǎn)單通過(guò)客戶端版本判斷進(jìn)行擴(kuò)展外,有沒(méi)有更好的方案呢?因?yàn)槌诉@些方案已知的弊端外,需要從長(zhǎng)遠(yuǎn)考慮,比如前面提到的算法,后續(xù)還會(huì)不會(huì)存在升級(jí)的可能,下載接口會(huì)不會(huì)有更多能力的增強(qiáng)。
結(jié)合上面思考,在原來(lái)接口基礎(chǔ)上新增標(biāo)志參數(shù)字段,表示該請(qǐng)求發(fā)出的客戶端支持的能力。為了后續(xù)擴(kuò)展,字段類型為整數(shù)值,不只是簡(jiǎn)單的boolean,服務(wù)端通過(guò)位運(yùn)算完成判斷邏輯??蛻舳艘矓[脫某個(gè)功能與版本的強(qiáng)一致性,不用去記錄某個(gè)版本具有某種能力。
四、關(guān)于接口設(shè)計(jì)的更多思考
最后補(bǔ)充一些踩過(guò)的坑和反思。服務(wù)端在提供接口時(shí),不能僅僅關(guān)注接口的實(shí)現(xiàn),更多的時(shí)候需要關(guān)注接口的使用方,他們使用的場(chǎng)景、調(diào)用時(shí)機(jī)等等。否則開發(fā)在對(duì)接口問(wèn)題排查、維護(hù)花費(fèi)的時(shí)間會(huì)比實(shí)際開發(fā)的耗時(shí)要多上好幾倍。
1)場(chǎng)景化:具體到什么是場(chǎng)景化呢,拿商店客戶端的幫助用戶檢測(cè)手機(jī)安裝的應(yīng)用版本是否最新的服務(wù)舉例,檢測(cè)時(shí)機(jī)是存在不同的場(chǎng)景的,比如用戶啟動(dòng)、用戶切換wlan環(huán)境、定時(shí)檢測(cè)等。當(dāng)需要進(jìn)行精細(xì)化分析,哪些請(qǐng)求是有效的,哪些會(huì)引起集中請(qǐng)求時(shí),這個(gè)時(shí)候如果請(qǐng)求上沒(méi)有場(chǎng)景區(qū)分,那么分析將無(wú)從下手。所以在與客戶端溝通接口設(shè)計(jì)時(shí),請(qǐng)帶上場(chǎng)景這個(gè)因素。接口設(shè)計(jì)上可參考如/app/{scene}/upgrade,定義好各個(gè)場(chǎng)景名稱,在路徑上帶上具體的場(chǎng)景,這樣對(duì)線上不同來(lái)源請(qǐng)求量級(jí)、問(wèn)題分析都會(huì)有很大好處。
2)鑒權(quán)和服務(wù)隔離:除了場(chǎng)景需要考慮外,接口調(diào)用在分配時(shí)做好記錄和鑒權(quán)以及服務(wù)隔離。比如商店的部分接口服務(wù)不僅提供給客戶端,同時(shí)也會(huì)提供給手機(jī)系統(tǒng)應(yīng)用調(diào)用。目前vivo上億的存量用戶體量,這里要十分小心,系統(tǒng)應(yīng)用的調(diào)用量控制不當(dāng),并發(fā)可比商店本身要大的多。首先前期與服務(wù)調(diào)用方評(píng)估溝通、做好設(shè)計(jì),避免出問(wèn)題。即使在出問(wèn)題時(shí),也要有機(jī)制能夠快速發(fā)現(xiàn)問(wèn)題、能夠分析出問(wèn)題的來(lái)源,降低問(wèn)題帶來(lái)的損失。
至此上面解決問(wèn)題的思路,都與具體業(yè)務(wù)以及背景有一定關(guān)系。隨著技術(shù)不斷迭代和發(fā)展,在移動(dòng)端APP頁(yè)面動(dòng)態(tài)性,目前業(yè)界也有了更多高效的技術(shù)方案,比如谷歌的Flutter、Weex等。這些技術(shù)能夠?qū)崿F(xiàn)靈活擴(kuò)展,多端統(tǒng)一,性能也能夠接近native。不僅減少了客戶端發(fā)版頻次,也減少了服務(wù)端兼容性處理成本。目前我們vivo也有團(tuán)隊(duì)在使用和實(shí)踐。
技術(shù)不斷更迭,沒(méi)有最好的方案,只有最適合的方案。開發(fā)過(guò)程中不僅滿足當(dāng)前實(shí)現(xiàn),更多的是考慮到后續(xù)擴(kuò)展性和可維護(hù)性。開發(fā)不能一味追求高端技術(shù),技術(shù)最終服務(wù)于業(yè)務(wù),堅(jiān)持長(zhǎng)期主義,效率至上。
五、參考資料
1、The Costs of Versioning an API
2、敏捷開發(fā),火車發(fā)布模式
本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。