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

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

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

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

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

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

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

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

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

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

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

但批量查询还是同步模式,下面介绍如果使用CompletableFuture来实现异步并发调用,直接用原生的CompletableFuture也可以,但是编排能力没有那么强,这里我们选择一款基于CompletableFuture封装的并行编排框来实现,详细介绍查看我之前的这篇文章:https://mp.weixin.qq.com/s/3EE8ccydK16gC1oY4AWnoA

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

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

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

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

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

List<AsyncCall> params = new ArrayList<>();
AsyncCall<Integer, Integer> goodsQuery = new AsyncCall("goodsQuery", 1);
params.add(goodsQuery);
AsyncCall<String, OrderResponse> orderQuery = new AsyncCall("orderQuery", "100");
params.add(orderQuery);
UserQuery q = new UserQuery();
q.setAge(18);
q.setName("yinjihuan");
AsyncCall<UserQuery, UserResponse> userQuery = new AsyncCall("userQuery", q);
params.add(userQuery);
AsyncTemplate.call(params, p -> {
if (p.getTaskId().equals("goodsQuery")) {
AsyncCall<Integer, Integer> query = p;
return goodsService.getGoodsName(query.getParam());
}
if (p.getTaskId().equals("orderQuery")) {
AsyncCall<String, OrderResponse> query = p;
return orderService.getOrder(query.getParam());
}
if (p.getTaskId().equals("userQuery")) {
AsyncCall<UserQuery, UserResponse> query = p;
return userService.getUser(query.getParam());
}
return null;
});

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

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

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

最新文章

  1. 在ThoughtWorks工作这几年我学到了什么?
  2. 【131031】rel 属性 -- link标签中的rel属性,定义了文档与链接的关系
  3. Linux crontab 定时任务详解
  4. JS settimeout 使用笔记
  5. 安装完最小化 RHEL/CentOS 7 后需要做的 30 件事情(二)转载自码农网
  6. ABBYY FineReader 12PDF选项卡有保存模式吗
  7. MVC开发Markdown编辑器(2)
  8. 《Apache之访问本地用户家目录》——RHEL6.3
  9. iOS 10 的适配问题
  10. Terrocotta - 基于JVM的Java应用集群解决方案
  11. [RxJS] Transformation operator: scan
  12. 二维离散平稳小波重构iswt2
  13. zabbix 问题汇总
  14. 【NOIP2012】开车旅行(倍增)
  15. python入门(12)dict
  16. luogu 2154 离散化+杨辉三角+树状数组
  17. 2018-2019-1 20189201 《LInux内核原理与分析》第七周作业
  18. bootstrap 栅栏系统
  19. magic cube
  20. Android 屏蔽Power键 Home键

热门文章

  1. leetcode 练习--反转链表
  2. Ajax Status(状态码) &amp; readyState()
  3. 获取Win和Linux系统启动时间,类似uptime功能,用于判断是否修改过系统时间
  4. 关于热力图的loss的一点感想
  5. Docker实战 | 第二篇:IDEA集成Docker插件实现一键自动打包部署微服务项目,一劳永逸的技术手段值得一试
  6. go语言之---数组(array)和切片(slice)
  7. 赶紧收藏吧!MyBatis-Plus万字长文图解笔记,错过了这个村可就没这个店了
  8. java ipv6发邮件需要注意的点
  9. 第9.4节 Python中用readline读取二进制文件方式打开文件
  10. 函数与函数式编程(生成器 &amp;&amp; 列表解析 &amp;&amp; map函数 &amp;&amp; filter函数)-(四)