南充住房和城鄉(xiāng)建設(shè)廳網(wǎng)站農(nóng)產(chǎn)品營銷方案
1 重構(gòu)背景
原有的開發(fā)人員早已離職,代碼細節(jié)沒人知道,經(jīng)過了一段時間的維護,發(fā)現(xiàn)有以下問題:
個人中心系統(tǒng)的特征就是組裝各個業(yè)務(wù)的接口,輸出個人中心業(yè)務(wù)需要的數(shù)據(jù),整個系統(tǒng)調(diào)用了幾十個第三方業(yè)務(wù)線的接口,如果編排不合理,可能會導致響應(yīng)時間急劇上漲,尤其是彈窗業(yè)務(wù),新的彈窗會不斷接入,整個接口可能會不可用。
2 整體架構(gòu)
service:是最小的業(yè)務(wù)編排單元,request方法對infrastructure第三方接口進行編排調(diào)用;apply 方法對第三方接口調(diào)用的結(jié)果進行組裝,結(jié)果是service的業(yè)務(wù)返回;
infrastructure:是對第三方的異步非阻塞調(diào)用,不包含業(yè)務(wù)邏輯。一個service內(nèi)部根據(jù)實際業(yè)務(wù)可以編排0個或者多個infrastructure服務(wù)。
在實際優(yōu)化過程中我們抽象了30多個infrastructure第三方調(diào)用,40多個service。他們都是小而且獨立的類,減輕了開發(fā)同學尤其是新同學熟悉的成本。邊界也比較清晰,邏輯內(nèi)聚。
2 編排舉例
每個 service 內(nèi)部都是由一個或者多個 infrastructure 第三方調(diào)用組裝編排的業(yè)務(wù)單元,內(nèi)部處理能異步處理的全是使用異步處理,實在不能異步處理的使用串行+并行的方式。
2.1 串行
需要串行的可以使用 flatMap 方法,可以參考以下格式。
這種方式會執(zhí)行S1,然后S2。
偽代碼如下:
Mono.from(service1.func()).flatMap(service1Res-> {return service2.func();})
2.2 并行
zip 和 zipWith,zipWith一次組裝一個Mono,zip 一次可以組裝多個Mono。
示例代碼如下:
service1.zipWith(service2)Mono.zip(service1, service2, service3)
一個使用 zip 組裝多個service的示例代碼,并行執(zhí)行service1, service2, …, service6,使用doOnError處理錯誤,onErrorReturn 處理異常返回,doOnFinally 監(jiān)控整個接口調(diào)用量、耗時情況。
Mono.zip(service1, service2, service3, service4, service5, service6).map(t -> {String service1Ret = t.getT1();String service2Ret = t.getT2();// ....return "組合結(jié)果";})// 異常返回.onErrorReturn(new DTO()).doOnError(e -> {// 異常詳情日志;異常請求量監(jiān)控}).doFinally(e -> {// 請求量、耗時監(jiān)控});
2.3 并行-但只取第一個有數(shù)據(jù)的結(jié)果
彈窗類業(yè)務(wù)與一般service不通,它需要調(diào)用很多的業(yè)務(wù)的數(shù)據(jù)出不同的彈窗,但是每次都只能給用戶展示確定的一個。但是如果串行的話,隨著上線的彈窗越來越多,整個彈窗接口的耗時會越來越長。
但是如果改成異步的話,又無法控制彈窗之間的優(yōu)先級,優(yōu)先級對于公司整體業(yè)務(wù)來說是必要的,把重要的業(yè)務(wù)放在高優(yōu)的位置上,做到資源最大利用,才能實現(xiàn)利潤的最大化,從而做到基業(yè)長青。
Flux 有個flatMapSequential方法,它能完美解決這個問題,看看它的注釋:
Transform the elements emitted by this Flux asynchronously into Publishers, then flatten these inner publishers into a single Flux, but merge them in the order of their source element.
將此Flux發(fā)出的元素異步地轉(zhuǎn)換為 publisher,然后將這些內(nèi)部 publisher 扁平化為單個Flux,但按照源元素的順序合并它們。
如上圖所示,總共有S1、S2、S3、S4按順序的四個彈窗,會并行執(zhí)行S1到S4,如果S1和S2沒有數(shù)據(jù),S3有數(shù)據(jù),則會返回S3。
偽代碼如下:
Flux<Map<String, Object>> monoFlux = Flux.fromIterable(serviceList).flatMapSequential(serviceName -> {}).onErrorContinue((err, i) -> {// 某個service異?;蛘邿o數(shù)據(jù),繼續(xù)執(zhí)行});}).onErrorContinue((err, i) -> {// 服務(wù)異常,繼續(xù)執(zhí)行});
Mono<Map<String, Object>> mono = monoFlux.elementAt(0, Maps.newHashMap());
這里就是異步執(zhí)行所有彈窗service,運行過程中某個彈窗異?;蛘邿o數(shù)據(jù)返回,則繼續(xù)下一個。通過monoFlux.elementAt(0, Maps.newHashMap())
獲取第一個有數(shù)據(jù)的彈窗。
4 重構(gòu)效果
4.1 后端指標
相比于原來的后端系統(tǒng),所有接口耗時都有大幅度降低,:
- 頭部身份信息接口響應(yīng)速度提升:26%。
- 卡片各業(yè)務(wù)線入口接口響應(yīng)速度提升:87%。
- 彈窗和浮標接口響應(yīng)速度提升:146%。
經(jīng)過 flatMapSequential 編排彈窗之后,耗時從220ms,降到160ms,絕對值下降了60ms,下降了 28%;
4.2 新需求開發(fā)和維護
新需求開發(fā)更快,QA 測試更快。
原來開發(fā)一個彈窗,需要考慮的事情很多:
- 開發(fā)的時候需要考慮代碼放在哪個層級上,是否與其他彈窗有耦合,
- 彈窗優(yōu)先級需要通過if-else實現(xiàn),很容易出錯;
- 彈窗自測很麻煩,需要注釋調(diào)其他彈窗;
- QA 需要測試所有彈窗的優(yōu)先級是否有問題;
現(xiàn)在開發(fā)一個彈窗,只需要增加一個service類,然后把service配置再優(yōu)先級列表中即可。
4.3 其他
框架使用了響應(yīng)式框架 Spring WebFlux,也支持本地啟動,編寫了service層和基礎(chǔ)設(shè)施層的單測case,提升開發(fā)效率。
刪除了原來的業(yè)務(wù)網(wǎng)關(guān)層,使用公司層面的網(wǎng)關(guān)系統(tǒng),配置即生效;刪除了原來業(yè)務(wù)網(wǎng)關(guān)中的業(yè)務(wù)邏輯代碼,把相關(guān)邏輯移動到業(yè)務(wù)層中,解除了原來的多層之間的耦合關(guān)系。
現(xiàn)在各個service之前相互獨立,異常不會相互影響。