本文转载自微信公众号《猿世界》,作者尹继焕。转载本文请联系元天地公众号。当数据量较大时,会分库分表拆分,分担读写压力。分库分表后比较麻烦的就是查询问题了。如果不直接根据shardkey查询,需要查询多张表。在一些复杂的业务场景中,比如订单搜索,除了订单号、用户、商户等常见的搜索条件外,还可能有时间、商品等。目前普遍的做法是将数据同步到ES等搜索框架进行查询,然后使用搜索到的结果,通常是主键ID,在具体的数据表中查询完整的数据,组装返回给调用方。比如下面的代码先查询文章信息,然后根据文章中的用户ID查询用户的昵称。ListarticleBos=articleDoPage.getRecords().stream().map(r->{Stringnickname=userManager.getNickname(r.getUserId());returnarticleBoConvert.convertPlus(r,nickname);}).collect(收集器.toList());如果文章有10条数据,需要调用10次用户服务提供的接口,同步调用操作。当然,我们也可以使用并行流来实现并发调用。代码如下:ListarticleBos=articleDoPage.getRecords().parallelStream().map(r->{Stringnickname=userManager.getNickname(r.getUserId());returnarticleBoConvert.convertPlus(r,nickname);}).collect(Collectors.toList());并行流的优势很明显,代码不需要做很大改动。需要注意的是,如果使用并行流,最好单独定义一个ForkJoinPool。除了使用并行流,还可以使用批量查询来提高性能,减少RPC调用次数。代码如下:ListuserIds=articleDoPage.getRecords().stream().map(article->article.getUserId()).collect(Collectors.toList());MapnickNameMap=userManager.queryByIds(userIds).stream().collect(Collectors.toMap(UserResponse::getId,UserResponse::getNickname));ListarticleBos=articleDoPage.getRecords().stream().map(r->{Stringnickname=nickNameMap.containsKey(r.getUserId())?nickNameMap.get(r.getUserId()):CommonConstant.DEFAULT_EMPTY_STR;returnarticleBoConvert.convertPlus(r,nickname);}).collect(Collectors.toList());但是批量查询还是同步方式。下面介绍如果使用CompletableFuture实现异步并发调用,也可以直接使用原来的CompletableFuture,但是编排能力没有那么强,这里我们选择基于CompletableFuture封装的并行编排盒来实现。稍微封装一下,提供一个更方便的工具类来实现并发调用多个接口的逻辑。第一种方法适用于,比如从ES中找到一批ID,然后根据ID去数据库或者调用RPC查询真实数据,最后得到一个Map,可以根据钥匙。在内部,它被多个线程并发调用,它会等待所有结果返回。publicObjectaggregationApi(){longs=System.currentTimeMillis();Listids=newArrayList<>();ids.add("1");ids.add("2");ids.add("3");MapcallResult=AsyncTemplate.call(ids,id->{returnuserService.getUser(id);},u->u.getId(),COMMON_POOL);longe=System.currentTimeMillis();System.out.println("耗时:"+(e-s)+"ms");return"";}另一种场景是API聚合场景,需要并行调用多个接口拼装结果。Listparams=newArrayList<>();AsyncCallgoodsQuery=newAsyncCall("goodsQuery",1);params.add(goodsQuery);AsyncCallorderQuery=newAsyncCall("orderQuery","100");params.add(orderQuery);UserQueryq=newUserQuery();q.setAge(18);q.setName("yinjihuan");AsyncCalluserQuery=newAsyncCall("userQuery",q);params.add(userQuery);AsyncTemplate.call(params,p->{if(p.getTaskId().equals("goodsQuery")){AsyncCallquery=p;returngoodsService.getGoodsName(query.getParam());}if(p.getTaskId().equals("orderQuery")){AsyncCallquery=p;returnorderService.getOrder(query.getParam());}if(p.getTaskId().equals("userQuery")){AsyncCallquery=p;returnuserService.getUser(query.getParam());}returnnull;});AsyncCall中定义参数和响应的类型,响应结果会在执行完成之后,它将自动设置为AsyncCall。在call方法中,需要根据taskId做相应的处理逻辑。不同的taskId调用不同的接口。源码参考:https://github.com/yinjihuan/kitty作者简介:尹继焕,单纯技术爱好者,?、《Spring Cloud 微服务 入门 实战与进阶》作者,公众号猿世界发起人。原文链接:http://cxytiandi.com/blog/user/1