京東購物車如何提升30%性能
01背景
購物車面臨的挑戰(zhàn):
1)新業(yè)務(wù):隨著業(yè)務(wù)形態(tài)的豐富,購物車在不斷支持各種新業(yè)務(wù),依賴的外部接口也隨之增加;
2)下沉:一些前端調(diào)用的接口下沉到購物車中臺(tái);
3)前置:結(jié)算流程很多業(yè)務(wù)前置到購物車中,如優(yōu)惠券、京豆;
4)擴(kuò)容:為改善用戶體驗(yàn)購物車可容納的商品數(shù)量在不斷增長(zhǎng);
這些導(dǎo)致購物車依賴的RPC接口數(shù)量及分頁調(diào)用次數(shù)都在不斷增加。購物車作為交易流程開端,本身流量較大,在業(yè)務(wù)復(fù)雜化的背景下,如何提高性能保證用戶體驗(yàn),成為購物車面臨的較大挑戰(zhàn)。
02全異步化改造方案
通過增加服務(wù)器資源雖然能在一定程度上解決問題,但會(huì)帶來較大的成本開銷,也與工匠精神相悖。能否通過技術(shù)手段提升性能呢?通過分析,異步化改造成為解決這一問題的有效手段。
1)不同RPC并行
購物車依賴接口達(dá)幾十個(gè),各接口間存在復(fù)雜依賴關(guān)系。必須先梳理各接口間依賴,識(shí)別哪些可以并行。然后將原有代碼拆分為兩部分:RPC異步請(qǐng)求和結(jié)果處理,按照依賴關(guān)系,讓RPC最大限度并行執(zhí)行,減少在結(jié)果處理階段異步響應(yīng)等待時(shí)間,從而達(dá)到提升性能的目的。
2)批量接口多分頁并行
購物車依賴接口多為批量接口,且單次調(diào)用有數(shù)據(jù)量限制,需將數(shù)據(jù)拆分為多個(gè)分頁調(diào)用。那么多個(gè)分頁間也可以并行,改造中封裝了異步分頁工具,使業(yè)務(wù)層對(duì)分頁邏輯無感知,異步工具自動(dòng)將超過接口上限的數(shù)據(jù)拆分為多個(gè)分頁并行調(diào)用,提升單接口響應(yīng)速度。
3)底層采用JSF異步調(diào)用
異步調(diào)用基于京東RPC框架JSF,推薦使用1.7.5以后版本,支持CompletableFuture。
03問題及解決
異步化改造的總體方案并不復(fù)雜,但是在實(shí)際落地過程中,遇到了很多細(xì)節(jié)問題:
1)異常重試需精細(xì)化
同步調(diào)用時(shí),如果超時(shí)會(huì)重試調(diào)用。改為異步后重試會(huì)失效,因?yàn)樵谡{(diào)用時(shí)一般不會(huì)報(bào)錯(cuò),需要在結(jié)果處理階段獲取異步響應(yīng)超時(shí)后,再進(jìn)行重試。
另外,多分頁并行時(shí),當(dāng)某一頁請(qǐng)求超時(shí)后,應(yīng)該只重試出錯(cuò)的分頁。底層對(duì)分頁調(diào)用進(jìn)行了封裝,上層業(yè)務(wù)代碼在獲取數(shù)據(jù)時(shí)無法感知是哪一頁超時(shí),所以必須在異步調(diào)用時(shí)將現(xiàn)場(chǎng)信息保存在包裝類中,一起返回給業(yè)務(wù)層,在Get數(shù)據(jù)超時(shí)后,單獨(dú)重試出錯(cuò)的分頁。
發(fā)生異常時(shí),并不是所有情況都需要重試,當(dāng)遇到限流等異常時(shí),不能進(jìn)行重試。底層工具需要自動(dòng)過濾限流異常,當(dāng)然也支持自定義規(guī)則。
2)異步RPC監(jiān)控更復(fù)雜
底層RPC耗時(shí)監(jiān)控需要拆分為兩部分,在分頁調(diào)用時(shí)記為開始時(shí)間,在異步結(jié)果到達(dá)后,記為結(jié)束時(shí)間。如果調(diào)用異常或Get超時(shí),需要標(biāo)記本次調(diào)用失敗。對(duì)于重試同樣需要記錄調(diào)用耗時(shí),且正常調(diào)用與重試調(diào)用需分開記錄。
除了需要監(jiān)控RPC耗時(shí)外,還需要監(jiān)控結(jié)果處理階段Get等待時(shí)長(zhǎng),這個(gè)時(shí)間才是真正對(duì)應(yīng)用性能有影響的時(shí)間。由于底層是分頁調(diào)用,所以業(yè)務(wù)調(diào)用次數(shù)和底層RPC調(diào)用次數(shù)并不相同。
3)分頁異步結(jié)果不能合并,否則無法獲取異常Provider信息
底層異步調(diào)用結(jié)果,必須通過包裝類原樣返回給上層,除了上邊提到的需要單分頁重試外,另一個(gè)原因是必須保留異步結(jié)果,在分頁超時(shí)后才能輸出超時(shí)的Provider信息。這是由于Provider信息依賴JSF框架的JSFCompletableFuture,如果在底層合并結(jié)果,會(huì)導(dǎo)致信息丟失。
4)每頁超時(shí)時(shí)間需單獨(dú)控制
分頁調(diào)用過程如上圖所示,在結(jié)果處理時(shí),每頁Get超時(shí)時(shí)間需要單獨(dú)控制,因?yàn)楂@取結(jié)果是順序進(jìn)行,獲取后邊的分頁時(shí),前邊分頁等待的時(shí)間也應(yīng)計(jì)算在內(nèi),以保證整個(gè)獲取結(jié)果的時(shí)間不超過單個(gè)分頁的最大超時(shí)時(shí)間。計(jì)算公式如下:
超時(shí)=RPC超時(shí)時(shí)間 > (當(dāng)前時(shí)間-異步調(diào)用開始時(shí)間) ? RPC超時(shí)時(shí)間 – (當(dāng)前時(shí)間-異步調(diào)用開始時(shí)間) : 0
5)分頁均衡
為避免最后一頁數(shù)據(jù)過少造成數(shù)據(jù)傾斜,需要將請(qǐng)求數(shù)據(jù)均分到每一頁,以最大限度提高整個(gè)請(qǐng)求的性能。
04收益
改造完成后購物車核心接口耗時(shí)減少30%,保證用戶體驗(yàn),節(jié)省大量服務(wù)器資源。后續(xù)增加新的RPC接口時(shí),只要處在調(diào)用拓?fù)涞姆顷P(guān)鍵路徑上,對(duì)購物車性能沒有太大影響。另外,容量增加時(shí)除少數(shù)不能分頁調(diào)用的接口外,對(duì)性能影響已經(jīng)比較小。