博客 / 詳情

返回

Reactor 第九篇 WebFlux重構個人中心,效果顯著

1 重構背景

原有的開發人員早已離職,代碼細節沒人知道,經過了一段時間的維護,發現有以下問題:

個人中心繫統的特徵就是組裝各個業務的接口,輸出個人中心業務需要的數據,整個系統調用了幾十個第三方業務線的接口,如果編排不合理,可能會導致響應時間急劇上漲,尤其是彈窗業務,新的彈窗會不斷接入,整個接口可能會不可用。

2 整體架構

service:是最小的業務編排單元,request方法對infrastructure第三方接口進行編排調用;apply 方法對第三方接口調用的結果進行組裝,結果是service的業務返回;

infrastructure:是對第三方的異步非阻塞調用,不包含業務邏輯。一個service內部根據實際業務可以編排0個或者多個infrastructure服務。

在實際優化過程中我們抽象了30多個infrastructure第三方調用,40多個service。他們都是小而且獨立的類,減輕了開發同學尤其是新同學熟悉的成本。邊界也比較清晰,邏輯內聚。

2 編排舉例

每個 service 內部都是由一個或者多個 infrastructure 第三方調用組裝編排的業務單元,內部處理能異步處理的全是使用異步處理,實在不能異步處理的使用串行+並行的方式。

2.1 串行

需要串行的可以使用 flatMap 方法,可以參考以下格式。

這種方式會執行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的示例代碼,並行執行service1, service2, ......, service6,使用doOnError處理錯誤,onErrorReturn 處理異常返回,doOnFinally 監控整個接口調用量、耗時情況。

Mono.zip(service1, service2, service3, service4, service5, service6)
                .map(t -> {
                    String service1Ret = t.getT1();
                    String service2Ret = t.getT2();
          // ....
                    return "組合結果";
                })
        // 異常返回
                .onErrorReturn(new DTO())
                .doOnError(e -> {
                    // 異常詳情日誌;異常請求量監控
                })
                .doFinally(e -> {
          // 請求量、耗時監控
                });

2.3 並行-但只取第一個有數據的結果

彈窗類業務與一般service不通,它需要調用很多的業務的數據出不同的彈窗,但是每次都只能給用户展示確定的一個。但是如果串行的話,隨着上線的彈窗越來越多,整個彈窗接口的耗時會越來越長。

但是如果改成異步的話,又無法控制彈窗之間的優先級,優先級對於公司整體業務來説是必要的,把重要的業務放在高優的位置上,做到資源最大利用,才能實現利潤的最大化,從而做到基業長青。

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發出的元素異步地轉換為 publisher,然後將這些內部 publisher 扁平化為單個Flux,但按照源元素的順序合併它們。

如上圖所示,總共有S1、S2、S3、S4按順序的四個彈窗,會並行執行S1到S4,如果S1和S2沒有數據,S3有數據,則會返回S3。

偽代碼如下:

Flux<Map<String, Object>> monoFlux = Flux.fromIterable(serviceList)
                .flatMapSequential(serviceName -> {
                        
                            })
                            .onErrorContinue((err, i) -> {
                  // 某個service異常或者無數據,繼續執行
                            });
                })
                .onErrorContinue((err, i) -> {
          // 服務異常,繼續執行
                });
Mono<Map<String, Object>> mono = monoFlux.elementAt(0, Maps.newHashMap());        

這裏就是異步執行所有彈窗service,運行過程中某個彈窗異常或者無數據返回,則繼續下一個。通過monoFlux.elementAt(0, Maps.newHashMap())獲取第一個有數據的彈窗。

4 重構效果

4.1 後端指標

相比於原來的後端系統,所有接口耗時都有大幅度降低,:

  • 頭部身份信息接口響應速度提升:26%。
  • 卡片各業務線入口接口響應速度提升:87%。
  • 彈窗和浮標接口響應速度提升:146%。

經過 flatMapSequential 編排彈窗之後,耗時從220ms,降到160ms,絕對值下降了60ms,下降了 28%;

4.2 新需求開發和維護

新需求開發更快,QA 測試更快。

原來開發一個彈窗,需要考慮的事情很多:

  1. 開發的時候需要考慮代碼放在哪個層級上,是否與其他彈窗有耦合,
  2. 彈窗優先級需要通過if-else實現,很容易出錯;
  3. 彈窗自測很麻煩,需要註釋調其他彈窗;
  4. QA 需要測試所有彈窗的優先級是否有問題;

現在開發一個彈窗,只需要增加一個service類,然後把service配置再優先級列表中即可。

4.3 其他

框架使用了響應式框架 Spring WebFlux,也支持本地啓動,編寫了service層和基礎設施層的單測case,提升開發效率。

刪除了原來的業務網關層,使用公司層面的網關係統,配置即生效;刪除了原來業務網關中的業務邏輯代碼,把相關邏輯移動到業務層中,解除了原來的多層之間的耦合關係。

現在各個service之前相互獨立,異常不會相互影響。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.