亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring Boot高效數據聚合之道深入講解

發布時間:2020-08-23 12:05:10 來源:腳本之家 閱讀:157 作者:lvyahui 欄目:編程語言

背景

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

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

用戶基礎信息

@Service
public class UserServiceImpl implements UserService {
 @Override
 public User get(Long id) {
 try {Thread.sleep(1000L);} catch (InterruptedException e) {}
 /* mock a user*/
 User user = new User();
 user.setId(id);
 user.setEmail("lvyahui8@gmail.com");
 user.setUsername("lvyahui8");
 return user;
 }
}

用戶博客列表

@Service
public class PostServiceImpl implements PostService {
 @Override
 public List<Post> getPosts(Long userId) {
 try { Thread.sleep(1000L); } catch (InterruptedException e) {}
 Post post = new Post();
 post.setTitle("spring data aggregate example");
 post.setContent("No active profile set, falling back to default profiles");
 return Collections.singletonList(post);
 }
}

用戶的粉絲數據

@Service
public class FollowServiceImpl implements FollowService {
 @Override
 public List<User> getFollowers(Long userId) {
 try { Thread.sleep(1000L); } catch (InterruptedException e) {}
 int size = 10;
 List<User> users = new ArrayList<>(size);
 for(int i = 0 ; i < size; i++) {
  User user = new User();
  user.setUsername("name"+i);
  user.setEmail("email"+i+"@fox.com");
  user.setId((long) i);
  users.add(user);
 };
 return users;
 }
}

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

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

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

串行實現

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

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

@Component
public class UserQueryFacade {
 @Autowired
 private FollowService followService;
 @Autowired
 private PostService postService;
 @Autowired
 private UserService userService;
 
 public User getUserData(Long userId) {
  User user = userService.get(userId);
  user.setPosts(postService.getPosts(userId));
  user.setFollowers(followService.getFollowers(userId));
  return user;
 }
}

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

并行實現

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

@Component
public class UserQueryFacade {
 @Autowired
 private FollowService followService;
 @Autowired
 private PostService postService;
 @Autowired
 private UserService userService;
 
 public User getUserDataByParallel(Long userId) throws InterruptedException, ExecutionException {
  ExecutorService executorService = Executors.newFixedThreadPool(3);
  CountDownLatch countDownLatch = new CountDownLatch(3);
  Future<User> userFuture = executorService.submit(() -> {
   try{
    return userService.get(userId);
   }finally {
    countDownLatch.countDown();
   }
  });
  Future<List<Post>> postsFuture = executorService.submit(() -> {
   try{
    return postService.getPosts(userId);
   }finally {
    countDownLatch.countDown();
   }
  });
  Future<List<User>> followersFuture = executorService.submit(() -> {
   try{
    return followService.getFollowers(userId);
   }finally {
    countDownLatch.countDown();
   }
  });
  countDownLatch.await();
  User user = userFuture.get();
  user.setFollowers(followersFuture.get());
  user.setPosts(postsFuture.get());
  return user;
 }
}

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

優雅的注解實現

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

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

首先, 我們先定義一個聚合接口

@Component
public class UserAggregate {
 @DataProvider(id="userFullData")
 public User userFullData(@DataConsumer(id = "user") User user,
        @DataConsumer(id = "posts") List<Post> posts,
        @DataConsumer(id = "followers") List<User> followers) {
  user.setFollowers(followers);
  user.setPosts(posts);
  return user;
 }
}

其中

  • @DataProvider 表示這個方法是一個數據提供者, 數據Id為 userFullData
  • @DataConsumer 表示這個方法的參數, 需要消費數據, 數據Id為 user ,posts, followers.

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

@Service
public class UserServiceImpl implements UserService {
 @DataProvider(id = "user")
 @Override
 public User get(@InvokeParameter("userId") Long id) {
@Service
public class PostServiceImpl implements PostService {
 @DataProvider(id = "posts")
 @Override
 public List<Post> getPosts(@InvokeParameter("userId") Long userId) {
@Service
public class FollowServiceImpl implements FollowService {
 @DataProvider(id = "followers")
 @Override
 public List<User> getFollowers(@InvokeParameter("userId") Long userId) {

其中

  • @DataProvider 與前面的含義相同, 表示這個方法是一個數據提供者
  • @InvokeParameter 表示方法執行時, 需要手動傳入的參數

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

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

@Component
public class UserQueryFacade {
 @Autowired
 private DataBeanAggregateQueryFacade dataBeanAggregateQueryFacade;

 public User getUserFinal(Long userId) throws InterruptedException, 
    IllegalAccessException, InvocationTargetException {
  return dataBeanAggregateQueryFacade.get("userFullData",
    Collections.singletonMap("userId", userId), User.class);
 }
}

如何用在你的項目中

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

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

<dependency>
 <groupId>io.github.lvyahui8</groupId>
 <artifactId>spring-boot-data-aggregator-example</artifactId>
 <version>1.0.1</version>
</dependency>

并在 application.properties 文件中聲明注解的掃描路徑.

# 替換成你需要掃描注解的包
io.github.lvyahui8.spring.base-packages=io.github.lvyahui8.spring.example

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

  • @DataProvider
  • @DataConsumer
  • @InvokeParameter
  • Spring Bean DataBeanAggregateQueryFacade

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

項目地址和上述示例代碼: https://github.com/lvyahui8/spring-boot-data-aggregator

后期計劃

后續筆者將繼續完善異常處理, 超時邏輯, 解決命名沖突的問題, 并進一步提高插件的易用性, 高可用性, 擴展性

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

新乡县| 山丹县| 宜昌市| 得荣县| 潮安县| 铜鼓县| 临城县| 永川市| 天祝| 额尔古纳市| 灵川县| 融水| 泽州县| 肥西县| 广东省| 灌阳县| 汉阴县| 恭城| 墨脱县| 东城区| 吉木萨尔县| 嘉善县| 铜陵市| 苗栗县| 常宁市| 满洲里市| 交城县| 洛浦县| 余江县| 安国市| 湄潭县| 丹江口市| 禹城市| 大同市| 建水县| 扎赉特旗| 东方市| 阿巴嘎旗| 南部县| 崇信县| 定襄县|