Spring Boot高效數據聚合之道-並行

接口開發是後端開發中最常見的場景, 可能是RESTFul接口, 也可能是RPC接口. 接口開發往往是從各處撈出數據, 然後組裝成結果, 特別是那些偏業務的接口.

如何方便快速的開發高性能的接口, 是一個必須思考的問題.

例如, 我現在需要實現一個接口, 拉取 用戶基礎信息 + 用戶的博客列表 + 用戶的粉絲數據 的整合數據, 假設已經有如下三個接口可以使用, 分別用來獲取 用戶基礎信息 , 用戶博客列表, 用戶的粉絲數據.

用戶基礎信息:

用戶博客列表:

用戶的粉絲數據:

注意, 每一個方法都 sleep了 1s以模擬業務耗時.

我們需要再封裝一個接口, 來拼裝以上三個接口的數據.

PS: 這樣的場景實際在工作中很常見, 而且往往我們需要拼湊的數據, 是要走網絡請求調到第三方去的. 另外可能有人會想, 爲何不分成3個請求? 實際爲了客戶端網絡性能考慮, 往往會在一次網絡請求中, 儘可能多的傳輸數據, 當然前提是這個數據不能太大, 否則傳輸的耗時會影響渲染. 許多APP的首頁, 看着複雜, 實際也只有一個接口, 一次性拉下所有數據, 客戶端開發也簡單.

 串 行 實 現 

編寫性能優良的接口不僅是每一位後端程序員的技術追求, 也是業務的基本訴求. 一般情況下, 爲了保證更好的性能, 往往需要編寫更復雜的代碼實現.

但凡人皆有惰性, 因此, 往往我們會像下面這樣編寫串行調用的代碼

 很明顯, 上面的代碼, 效率低下, 起碼要3s才能拿到結果, 且一旦用到某個接口的數據, 便需要注入相應的 service, 複用麻煩.

 並 行 實 現 

有追求的程序員可能立馬會考慮到, 這幾項數據之間並無強依賴性, 完全可以並行獲取嘛, 通過 異步線程 + CountDownLatch + Future 實現, 就像下面這樣.

上面的代碼, 將串行調用改爲並行調用, 在有限併發級別下, 能極大提高性能. 但很明顯, 它過於複雜, 如果每個接口都爲了並行執行都寫這樣一段代碼, 簡直是噩夢.

 優雅的註解實現 

熟悉 java的都知道, java有一種非常便利的特性 ~~ 注 解. 簡直是黑魔法. 往往只需要給類或者方法上添加一些註解, 便可以實現非常複雜的功能.

有了註解, 再結合Spring依賴自動注入的思想, 那麼我們可不可以通過註解的方式, 自動注入依賴, 自動並行調用接口呢? 答案是肯定的.

首先, 我們先定義一個聚合接口 (當然也可以不定義聚合類, 所有代碼寫在原Service 類中同樣可以)

其中:

  • @DataProvider 表示這個方法是一個數據提供者, 數據 Id爲 userFullData

  • @DataConsumer 表示這個方法的參數, 需要消費數據, 數據 Id分別爲 user , postsfollowers.

當然, 原來的3個原子服務 用戶基礎信息 ,用戶博客列表, 用戶的粉絲數據, 也分別需要添加一些註解

 

其中:

  • @DataProvider 與前面的含義相同, 表示這個方法是一個數據提供者

  • @InvokeParameter 表示方法執行時, 需要手動傳入的參數

這裏注意 @InvokeParameter@DataConsumer的區別, 前者需要用戶在最上層調用時手動傳參; 而後者, 是由框架自動分析依賴, 並異步調用取得結果之後注入的.

最後, 僅僅只需要調用一個統一的門面( Facade )接口, 傳遞數據 Id, InvokeParameters,以及返回值類型. 剩下的並行處理, 依賴分析和注入, 完全由框架自動處理.

 如何用在項目中 

上面的功能, 已經封裝爲一個 spring boot starter, 併發布到 maven中央倉庫.

只需在你的項目引入依賴.

<!-- https://mvnrepository.com/artifact/io.github.lvyahui8/spring-boot-data-aggregator-starter -->
<dependency>
    <groupId>io.github.lvyahui8</groupId>
    <artifactId>spring-boot-data-aggregator-starter</artifactId>
    <version>1.1.3</version>
</dependency>

並在 application.properties 文件中聲明註解的掃描路徑.

  1. # 替換成你需要掃描註解的包

  2. io.github.lvyahui8.spring.base-packages = xx.xxxxxx.xxxxxx.xxxxxx.example

之後, 就可以使用如下註解和 Spring Bean 實現聚合查詢

  • @DataProvider

  • @DataConsumer

  • @InvokeParameter

  • Spring Bean DataBeanAggregateQueryFacade

注意, @DataConsumer@InvokeParameter 可以混合使用, 可以用在同一個方法的不同參數上. 且方法的所有參數必須有其中一個註解, 不能有沒有註解的參數.

 特   性 

  • 異步獲取依賴

所有 @DataConsumer 定義的依賴將異步獲取. 當 provider方法參數中的所有依賴獲取完成, 才執行 provider方法

  • 不限級嵌套

依賴關係支持深層嵌套. 上面的示例只有一層

  • 異常處理

目前支持兩種處理方式: 忽略 or 終止

忽略是指 provider方法在執行時, 忽略拋出的異常並 returnnull值; 終止是指一旦有一個 provider方法拋出了異常, 將逐級向上拋出, 終止後續處理.

配置支持 consumer級或者全局, 優先級 : consumer級 > 全局

  • 查詢緩存

在調用 Facadequery方法的一次查詢生命週期內, 方法調用結果可能複用, 只要方法簽名以及傳參一致, 則默認方法是冪等的, 將直接使用緩存的查詢結果. 但這個不是絕對的, 考慮到多線程的特性, 可能有時候不會使用緩存

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章