让API并行调用变得如丝般顺滑的绝招

运维 数据库运维
当数据量较大的时候,都会通过分库分表来拆分,分担读写的压力。分库分表后比较麻烦的就是查询的问题,如果不是直接根据分片键去查询的话,需要对多个表进行查询。

[[360995]]

本文转载自微信公众号「猿天地」,作者尹吉欢。转载本文请联系猿天地公众号。  

 当数据量较大的时候,都会通过分库分表来拆分,分担读写的压力。分库分表后比较麻烦的就是查询的问题,如果不是直接根据分片键去查询的话,需要对多个表进行查询。

在一些复杂的业务场景下,比如订单搜索,除了订单号,用户,商家 这些常用的搜索条件,可能还有时间,商品等等。

目前常见的做法将数据同步到 ES 这类搜索框架中进行查询,然后通过搜出来的结果,一般是主键 ID, 再去具体的数据表中查询完整的数据,组装返回给调用方。

比如下面这段代码,首先查询出文章信息,然后根据文章中的用户 ID 去查询用户的昵称。

  1. List<ArticleBO> articleBos = articleDoPage.getRecords().stream().map(r -> { 
  2.     String nickname = userManager.getNickname(r.getUserId()); 
  3.     return articleBoConvert.convertPlus(r, nickname); 
  4. }).collect(Collectors.toList()); 

如果文章有 10 条数据,那么就需要调用 10 次用户服务提供的接口,而且是同步调用操作。

当然我们也可以用并行流来实现并发调用,代码如下:

  1. List<ArticleBO> articleBos = articleDoPage.getRecords().parallelStream().map(r -> { 
  2.     String nickname = userManager.getNickname(r.getUserId()); 
  3.     return articleBoConvert.convertPlus(r, nickname); 
  4. }).collect(Collectors.toList()); 

并行流的优点很明显,代码不用做特别大的改动。需要注意如果用并行流,最好单独定义一个 ForkJoinPool。

除了用并行流,还可以使用批量查询的方式来提高性能,降低 RPC 的调用次数,代码如下:

  1. List<Long> userIds = articleDoPage.getRecords().stream().map(article -> article.getUserId()).collect(Collectors.toList()); 
  2. Map<Long, String> nickNameMap = userManager.queryByIds(userIds).stream().collect(Collectors.toMap(UserResponse::getId, UserResponse::getNickname)); 
  3. List<ArticleBO> articleBos = articleDoPage.getRecords().stream().map(r -> { 
  4.     String nickname = nickNameMap.containsKey(r.getUserId()) ? nickNameMap.get(r.getUserId()) : CommonConstant.DEFAULT_EMPTY_STR; 
  5.     return articleBoConvert.convertPlus(r, nickname); 
  6. }).collect(Collectors.toList()); 

但批量查询还是同步模式,下面介绍如果使用 CompletableFuture 来实现异步并发调用,直接用原生的 CompletableFuture 也可以,但是编排能力没有那么强,这里我们选择一款基于 CompletableFuture 封装的并行编排框来实现。

稍微做了下封装,提供了更方便使用的工具类来实现并发调用多个接口的逻辑。

第一种方式,适用于比如从 ES 查出了一批 ID, 然后根据 ID 去数据库中或者调用 RPC 查询真实数据,最后得到一个 Map,可以根据 Key 获取对应的数据。

内部是多线程并发调用,会等到结果全部返回。

  1. public Object aggregationApi() { 
  2.     long s = System.currentTimeMillis(); 
  3.     List<String> ids = new ArrayList<>(); 
  4.     ids.add("1"); 
  5.     ids.add("2"); 
  6.     ids.add("3"); 
  7.     Map<String, UserResponse> callResult = AsyncTemplate.call(ids, id -> { 
  8.         return userService.getUser(id); 
  9.     }, u -> u.getId(), COMMON_POOL); 
  10.     long e = System.currentTimeMillis(); 
  11.     System.out.println("耗时:" + (e-s) + "ms"); 
  12.     return ""

另一个场景就是 API 聚合的场景,需要并行调用多个接口,将结果进行组装。

  1. List<AsyncCall> params = new ArrayList<>(); 
  2. AsyncCall<IntegerInteger> goodsQuery = new AsyncCall("goodsQuery", 1); 
  3. params.add(goodsQuery); 
  4. AsyncCall<String, OrderResponse> orderQuery = new AsyncCall("orderQuery""100"); 
  5. params.add(orderQuery); 
  6. UserQuery q = new UserQuery(); 
  7. q.setAge(18); 
  8. q.setName("yinjihuan"); 
  9. AsyncCall<UserQuery, UserResponse> userQuery = new AsyncCall("userQuery", q); 
  10. params.add(userQuery); 
  11. AsyncTemplate.call(params, p -> { 
  12.     if (p.getTaskId().equals("goodsQuery")) { 
  13.         AsyncCall<IntegerInteger> query = p; 
  14.         return goodsService.getGoodsName(query.getParam()); 
  15.     } 
  16.     if (p.getTaskId().equals("orderQuery")) { 
  17.         AsyncCall<String, OrderResponse> query = p; 
  18.         return orderService.getOrder(query.getParam()); 
  19.     } 
  20.     if (p.getTaskId().equals("userQuery")) { 
  21.         AsyncCall<UserQuery, UserResponse> query = p; 
  22.         return userService.getUser(query.getParam()); 
  23.     } 
  24.     return null
  25. }); 

AsyncCall 中定义参数和响应的类型,响应结果会在执行完后会自动设置到 AsyncCall 中。在 call 方法中需要根据 taskId 去做对应的处理逻辑,不同的 taskId 调用的接口不一样。

源码参考:https://github.com/yinjihuan/kitty

关于作者:尹吉欢,简单的技术爱好者,《Spring Cloud 微服务-全栈技术与案例解析》, 《Spring Cloud 微服务 入门 实战与进阶》作者, 公众号 猿天地 发起人。

原文链接:http://cxytiandi.com/blog/user/1

 

责任编辑:武晓燕 来源: 猿天地
相关推荐

2024-09-06 07:48:58

2017-06-05 10:01:24

互联网

2019-04-04 17:15:13

2017-10-31 13:20:00

H5翻页库框架

2010-08-06 17:09:14

加薪

2022-05-31 09:01:13

GitHub工具安全

2021-06-07 17:46:31

Python 3.8Python编程语言

2015-10-28 14:03:32

数据迁移数据

2010-09-01 20:30:14

虚拟园区网网络架构H3C

2022-10-10 09:41:54

LinuxWindowsWSL2

2021-08-16 12:13:02

SwiftUIList ArticleList

2010-09-15 21:14:48

IT管理网络构架Juniper Net

2009-04-16 09:13:09

PHP代码优化提速

2011-06-21 15:42:32

笔记本技巧

2022-03-04 20:28:02

VueReact网页

2022-10-26 10:24:21

2020-07-20 10:40:52

Linux命令Ubuntu

2011-07-07 17:08:55

PHP

2015-12-02 09:37:24

数据中心数据中心优化

2021-10-25 12:14:28

智慧城市物联网
点赞
收藏

51CTO技术栈公众号