让API并行调用变得如丝般顺滑的绝招
当数据量较大的时候,都会通过分库分表来拆分,分担读写的压力。分库分表后比较麻烦的就是查询的问题,如果不是直接根据分片键去查询的话,需要对多个表进行查询。
在一些复杂的业务场景下,比如订单搜索,除了订单号,用户,商家 这些常用的搜索条件,可能还有时间,商品等等。
目前常见的做法将数据同步到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微服务 入门 实战与进阶》作者, 公众号猿天地发起人。
最新文章
- 在ThoughtWorks工作这几年我学到了什么?
- 【131031】rel 属性 -- link标签中的rel属性,定义了文档与链接的关系
- Linux crontab 定时任务详解
- JS settimeout 使用笔记
- 安装完最小化 RHEL/CentOS 7 后需要做的 30 件事情(二)转载自码农网
- ABBYY FineReader 12PDF选项卡有保存模式吗
- MVC开发Markdown编辑器(2)
- 《Apache之访问本地用户家目录》——RHEL6.3
- iOS 10 的适配问题
- Terrocotta - 基于JVM的Java应用集群解决方案
- [RxJS] Transformation operator: scan
- 二维离散平稳小波重构iswt2
- zabbix 问题汇总
- 【NOIP2012】开车旅行(倍增)
- python入门(12)dict
- luogu 2154 离散化+杨辉三角+树状数组
- 2018-2019-1 20189201 《LInux内核原理与分析》第七周作业
- bootstrap 栅栏系统
- magic cube
- Android 屏蔽Power键 Home键
热门文章
- leetcode 练习--反转链表
- Ajax Status(状态码) &; readyState()
- 获取Win和Linux系统启动时间,类似uptime功能,用于判断是否修改过系统时间
- 关于热力图的loss的一点感想
- Docker实战 | 第二篇:IDEA集成Docker插件实现一键自动打包部署微服务项目,一劳永逸的技术手段值得一试
- go语言之---数组(array)和切片(slice)
- 赶紧收藏吧!MyBatis-Plus万字长文图解笔记,错过了这个村可就没这个店了
- java ipv6发邮件需要注意的点
- 第9.4节 Python中用readline读取二进制文件方式打开文件
- 函数与函数式编程(生成器 &;&; 列表解析 &;&; map函数 &;&; filter函数)-(四)