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

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

目前常见的做法将数据同步到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微服务 入门 实战与进阶》作者, 公众号猿天地发起人。

让API并行调用变得如丝般顺滑的绝招的更多相关文章

  1. 如何把 Caffeine Cache 用得如丝般顺滑?

    一.关于 Caffeine Cache 在推荐服务中,虽然允许少量请求因计算超时等原因返回默认列表.但从运营指标来说,越高的"完算率"意味着越完整的算法效果呈现,也意味着越高的商业 ...

  2. 如丝般顺滑地从Windows迁移SQLServer数据库到Linux

    老鸟看过菜鸟的上一篇<MSSQL On Linux备份与还原>文章后,很满意,但是还是忍不住发问:"这篇文章讲的是MSSQL在Linux系统上的备份与还原,如果我之前是Windo ...

  3. 大促密集,CDN如何保障电商体验如丝般顺滑?

    简介: 前不久,阿里云技术天团空降CSDN在线峰会,对核心技术竞争力进行解读.其中,阿里云高级技术专家曾福华分享了<双11: CDN如何保障电商大促如丝般顺滑>的议题.俗话说:养兵千日,用 ...

  4. 如丝般顺滑:DDD再实践之类目树管理

    在上次反思DDD实践之后,在类目树管理项目中再次实践DDD.从需求分析到建模和具体的落地,结合个人体会,都是干货.

  5. 想让安卓 APP 如丝般顺滑?

    随着安卓手机市场占有率的节节攀升,随便在大街上找几个人估计 80% 用的都是安卓手机吧!用安卓手机的人这么多,不知道大家是否曾经感觉到过 APP 卡顿.死机?是否遇到应用程序无响应.闪退?本文就为大家 ...

  6. 微软 Build 大会发布大量开发工具与服务!编码、协作、发布,如丝般顺滑

    Microsoft Build 2020开发者大会已经圆满落幕,在连续两天48小时的不间断直播中,来自全世界的开发者共赴盛宴,场面相当壮观.在这一年一度的大聚会里,微软也是诚意满满,带来了一连串的产品 ...

  7. ios滑动流畅(丝般顺滑)滚动

    在ios html->body->list(少一个样式都不行!) html->body->list <!DOCTYPE html> <html lang=&q ...

  8. 【AMAD】django-silk -- 为Django提供如丝般顺滑的性能测量

    动机 简介 个人评分 动机 Django作为一个web框架,进行性能测量是很复杂的,不可以使用传统的程序profile工具. 因为,web app的性能是多维度的,不仅仅是代码执行效率,还包括网络延时 ...

  9. BumbleBee: 如丝般顺滑构建、交付和运行 eBPF 程序

    本文地址:https://www.ebpf.top/post/bumblebee 1. 前言 不久前,Solo.io 公司在官网博客宣布了开源了一个名称为 BumbleBee 的新项目.该项目专注于简 ...

随机推荐

  1. 【GDKOI2014】JZOJ2020年8月13日提高组T2 石油储备计划

    [GDKOI2014]JZOJ2020年8月13日提高组T2 石油储备计划 题目 Description Input Output 对于每组数据,输出一个整数,表示达到"平衡"状态 ...

  2. 在django中使用原生sql语句

    raw # row方法:(掺杂着原生sql和orm来执行的操作) res = CookBook.objects.raw('select id as nid from epos_cookbook whe ...

  3. 使用pip安装的Python扩展模块是从哪里下载的?

    对于初学者才开始使用Python安装扩展模块时,发现只要输入扩展模块名就可以安装,无需先下载再安装,不免疑惑那些要安装的软件是在哪里下载的?是否可以从别的地方下载? 这个问题答案如下: pip安装第三 ...

  4. PyQt(Python+Qt)学习随笔:Qt Designer中toolBar的orientation属性和iconSize属性

    orientation属性 orientation属性用于确认工具栏是水平方向还是垂直方向,这个属性对于QMainWindow中的工具栏来说没有意义,因为QMainWindow中的工具栏支持在上下左右 ...

  5. ⑤SpringCloud 实战:引入Zuul组件,开启网关路由

    这是SpringCloud实战系列中第4篇文章,了解前面第两篇文章更有助于更好理解本文内容: ①SpringCloud 实战:引入Eureka组件,完善服务治理 ②SpringCloud 实战:引入F ...

  6. LeetCode初级算法之数组:283 移动零

    移动零 题目地址:https://leetcode-cn.com/problems/move-zeroes/ 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺 ...

  7. Linux内核源码分析之setup_arch (二)

    1. 概述 接着上一篇<Linux内核源码分析之setup_arch (一)>继续分析,本文首先分析arm_memblock_init函数,然后分析内核启动阶段的是如何进行内存管理的. 2 ...

  8. Nginx 转发时的一个坑,运维居然让我背锅!!

    最近遇到一个 Nginx 转发的坑,一个请求转发到 Tomcat 时发现有几个 http header 始终获取不到,导致线上出现 bug,运维说不是他的问题,这个锅我背了. 新增的几个 header ...

  9. 【学习笔记】K-D tree 区域查询时间复杂度简易证明

    查询算法的流程 如果查询与当前结点的区域无交集,直接跳出. 如果查询将当前结点的区域包含,直接跳出并上传答案. 有交集但不包含,继续递归求解. K-D Tree 如何划分区域 可以借助下文图片理解. ...

  10. 九、git学习之——git基本命令全总结

    初始化一个Git仓库,使用git init命令. 添加文件到Git仓库,分两步: git add <file>,注意,可反复多次使用,添加多个文件: 使用命令git commit,完成. ...