當資料量較大的時候,都會通過分庫分表來拆分,分擔讀寫的壓力,分庫分表后比較麻煩的就是查詢的問題,如果不是直接根據分片鍵去查詢的話,需要對多個表進行查詢,
在一些復雜的業務場景下,比如訂單搜索,除了訂單號,用戶,商家 這些常用的搜索條件,可能還有時間,商品等等,
目前常見的做法將資料同步到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微服務 入門 實戰與進階》作者, 公眾號***猿天地***發起人,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/242383.html
標籤:java
