Java GraphQL數(shù)據(jù)加載器批處理的實(shí)現(xiàn)詳解
介紹
GraphQL 是一種強(qiáng)大而靈活的 API 查詢(xún)語(yǔ)言,使客戶(hù)端能夠準(zhǔn)確請(qǐng)求他們所需的數(shù)據(jù),從而消除信息的過(guò)度獲取和獲取不足。然而,隨著 GraphQL 查詢(xún)變得更加復(fù)雜并涉及多個(gè)數(shù)據(jù)源,有效地檢索數(shù)據(jù)并向客戶(hù)端提供數(shù)據(jù)可能具有挑戰(zhàn)性。這就是 GraphQL 數(shù)據(jù)加載器發(fā)揮作用的地方。
GraphQL 數(shù)據(jù)加載器是優(yōu)化 GraphQL API 的關(guān)鍵組件,旨在解決臭名昭著的 N+1 查詢(xún)問(wèn)題,該問(wèn)題在 GraphQL 服務(wù)器重復(fù)獲取相關(guān)項(xiàng)目列表的相同數(shù)據(jù)時(shí)發(fā)生。數(shù)據(jù)加載器通過(guò)批處理和緩存請(qǐng)求,幫助簡(jiǎn)化從各種來(lái)源(例如數(shù)據(jù)庫(kù)、API,甚至本地緩存)獲取數(shù)據(jù)的過(guò)程。通過(guò)這樣做,他們顯著提高了 GraphQL 查詢(xún)的效率和性能。
在本中,我們將深入研究批處理功能,通過(guò)查看數(shù)據(jù)加載器的 java 實(shí)現(xiàn)來(lái)探索它如何發(fā)揮其魔力。
批處理
批處理是將多個(gè)單獨(dú)的數(shù)據(jù)檢索請(qǐng)求收集到單個(gè)批處理請(qǐng)求中的過(guò)程,從而減少對(duì)數(shù)據(jù)源的調(diào)用次數(shù)。在處理 GraphQL 查詢(xún)中的關(guān)系時(shí),這一點(diǎn)尤其重要。
考慮一個(gè)典型場(chǎng)景,其中 GraphQL 查詢(xún)請(qǐng)求一個(gè)項(xiàng)目列表,以及每個(gè)項(xiàng)目的附加相關(guān)數(shù)據(jù)(例如用戶(hù)信息)。如果不進(jìn)行批處理,這將導(dǎo)致對(duì)每個(gè)項(xiàng)目進(jìn)行單獨(dú)的數(shù)據(jù)庫(kù)查詢(xún)或 API 請(qǐng)求,從而導(dǎo)致 N+1 查詢(xún)問(wèn)題。通過(guò)批處理,可以將這些單獨(dú)的請(qǐng)求有效地組合成單個(gè)請(qǐng)求,從而大大減少數(shù)據(jù)源的往返次數(shù)
Java 數(shù)據(jù)加載器批處理
假設(shè)我們有一個(gè)如下所示的 graphql 查詢(xún)
{
user {
name
friends {
name
}
}
}
它生成以下查詢(xún)結(jié)果
{ "user": { "name": "zhangsan", “friends”: [ { "name": "lisi", }, { "name": "wanmgwu", }, { "name": "zhouliu", } ] } }
一個(gè)簡(jiǎn)單的實(shí)現(xiàn)方法是為查詢(xún)響應(yīng)中的每個(gè)用戶(hù)執(zhí)行一次調(diào)用以檢索一個(gè)用戶(hù)對(duì)象,即 4 次調(diào)用,一次針對(duì)根對(duì)象,一次針對(duì)列表中的每個(gè)好友。
然而,它DataLoader
不會(huì)立即執(zhí)行遠(yuǎn)程調(diào)用,它只是將調(diào)用排入隊(duì)列并返回一個(gè) Promise ( CompletableFuture
) 來(lái)傳遞用戶(hù)對(duì)象。一旦我們將構(gòu)建查詢(xún)結(jié)果的所有調(diào)用排入隊(duì)列,我們??必須請(qǐng)求DataLoader
開(kāi)始執(zhí)行它們。這就是奇跡發(fā)生的地方。將DataLoader
開(kāi)始提取每次調(diào)用的用戶(hù) ID 并將其放入一個(gè)列表中,該列表將用于查詢(xún)我們配置的后端,并僅使用一個(gè)請(qǐng)求即可檢索用戶(hù)列表。
批處理通常按級(jí)別進(jìn)行,在本例中我們有 2 個(gè)級(jí)別。root 用戶(hù)和他的朋友。通過(guò)使用DataLoader
batchig,此響應(yīng)將只需要 2 次調(diào)用。
代碼示例
讓我們添加一些代碼來(lái)展示如何使用它。
我們首先需要擁有一個(gè)BatchLoader
. 它將從用戶(hù)后端批量加載用戶(hù),從而減少對(duì)該后端的 API 調(diào)用量。
List<User> loadUsersById(List<Long> userIds) { System.out.println("Api call to load users = " + userIds); return users.stream().filter(u -> userIds.contains(u.id())).toList(); } BatchLoader<Long, User> userBatchLoader = new BatchLoader<>() { @Override public CompletionStage<List<User>> load(List<Long> userIds) { return CompletableFuture.supplyAsync(() -> { return loadUsersById(userIds); }); } };
然后我們需要?jiǎng)?chuàng)建一個(gè)DataLoader
將使用前面的BatchLoader
來(lái)執(zhí)行整個(gè)用戶(hù)樹(shù)的加載。
var userLoader = DataLoaderFactory.newDataLoader(userBatchLoader); var userDTO = new UserDTO(); userLoader.load(1L).thenAccept(user -> { userDTO.id = user.id(); userDTO.name = user.name(); user.friends().forEach(friendId -> { userLoader.load(friendId).thenAccept(friend -> { userDTO.friends.add(new FriendDTO(friend.id(), friend.name())); }); }); }); userLoader.dispatchAndJoin(); System.out.println(userDTO);
它將產(chǎn)生以下調(diào)試輸出
Api call to load users = [1]
Api call to load users = [2, 3, 4]
UserDTO{id=1, name='John', friends=[FriendDTO[id=2, name=Jane], FriendDTO[id=3, name=Bob], FriendDTO[id=4, name=Alice]]}
如果您對(duì)它的內(nèi)部工作原理感到好奇,我將向您展示用戶(hù)的一種自定義實(shí)現(xiàn)DataLoader
。不是真正的。只需一個(gè)簡(jiǎn)化版本即可幫助您了解全貌。
static class UserLoader { BatchLoader<Long, User> userBatchLoader; record QueueEntry(long id, CompletableFuture<User> value) { } List<QueueEntry> loaderQueue = new ArrayList<>(); UserLoader(BatchLoader<Long, User> userBatchLoader) { this.userBatchLoader = userBatchLoader; } CompletableFuture<User> load(long userId) { var future = new CompletableFuture<User>(); loaderQueue.add(new QueueEntry(userId, future)); return future; } List<User> dispatchAndJoin() { List<User> joinedResults = dispatch().join(); List<User> results = new ArrayList<>(joinedResults); while (loaderQueue.size() > 0) { joinedResults = dispatch().join(); results.addAll(joinedResults); } return results; } CompletableFuture<List<User>> dispatch() { var userIds = new ArrayList<Long>(); final List<CompletableFuture<User>> queuedFutures = new ArrayList<>(); loaderQueue.forEach(qe -> { userIds.add(qe.id()); queuedFutures.add(qe.value()); }); loaderQueue.clear(); var userFutures = userBatchLoader.load(userIds).toCompletableFuture(); return userFutures.thenApply(users -> { for (int i = 0; i < queuedFutures.size(); i++) { var userId = userIds.get(i); var user = users.get(i); var future = queuedFutures.get(i); future.complete(user); } return users; }); } }
所以,首先看一下CompletableFuture<User> load(long userId)
,它不執(zhí)行任何 userId 查找,它只是:
- 將查找排入隊(duì)列
- 生成一個(gè),
CompletableFuture
讓您根據(jù)您提供的查找鏈接進(jìn)一步查找。因此,查找被推遲,直到我們實(shí)際使用dispatchAndJoin()
現(xiàn)在,看看List<User> dispatchAndJoin()
。一旦我們準(zhǔn)備好檢索用戶(hù)列表,就會(huì)調(diào)用該函數(shù)。它會(huì):
1.調(diào)用 CompletableFuture<List<User>> dispatch()
將執(zhí)行以下操作:
將所有 userId 分組到一個(gè)列表中,并將其發(fā)送到底層BatchLoader
,底層對(duì)后端執(zhí)行實(shí)際的 API 調(diào)用。
完成我們注冊(cè)查找時(shí)(當(dāng)我們調(diào)用 )時(shí)提供的 CompletableFuture CompletableFuture<User> load(long userId)
,從而向 中添加更多元素loaderQueue
。此時(shí),下一級(jí)的 userId 查找已排隊(duì)。
2.當(dāng)中還有剩余元素時(shí)重復(fù)該過(guò)程loaderQueue
。
到此這篇關(guān)于Java GraphQL數(shù)據(jù)加載器批處理的實(shí)現(xiàn)詳解的文章就介紹到這了,更多相關(guān)Java GraphQL數(shù)據(jù)加載器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java?11新特性HttpClient主要組件及發(fā)送請(qǐng)求示例詳解
這篇文章主要為大家介紹了java?11新特性HttpClient主要組件及發(fā)送請(qǐng)求示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06關(guān)于Spring?Cloud的熔斷器監(jiān)控問(wèn)題
Turbine是一個(gè)聚合Hystrix監(jiān)控?cái)?shù)據(jù)的工具,它可將所有相關(guān)/hystrix.stream端點(diǎn)的數(shù)據(jù)聚合到一個(gè)組合的/turbine.stream中,從而讓集群的監(jiān)控更加方便,接下來(lái)通過(guò)本文給大家介紹Spring?Cloud的熔斷器監(jiān)控,感興趣的朋友一起看看吧2022-01-01HashSet工作原理_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
HashSet 底層采用 HashMap 來(lái)保存所有元素,因此 HashSet 的實(shí)現(xiàn)比較簡(jiǎn)單。接下來(lái)通過(guò)本文給大家介紹HashSet工作原理_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理,需要的朋友可以參考下2017-04-04Java異常處理Guava?Throwables類(lèi)使用實(shí)例解析
這篇文章主要為大家介紹了Java異常處理神器Guava?Throwables類(lèi)使用深入詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12Spring Boot 指定外部啟動(dòng)配置文件詳解
在springboot項(xiàng)目中,也可以使用yml類(lèi)型的配置文件代替properties文件。接下來(lái)通過(guò)本文給大家分享Springboot配置文件的使用,感興趣的朋友一起看看吧2021-09-09Java之InputStreamReader類(lèi)的實(shí)現(xiàn)
這篇文章主要介紹了Java之InputStreamReader類(lèi)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11Nacos配合SpringBoot實(shí)現(xiàn)動(dòng)態(tài)線程池的基本步驟
使用Nacos配合Spring Boot實(shí)現(xiàn)動(dòng)態(tài)線程池,可以讓你的應(yīng)用動(dòng)態(tài)地調(diào)整線程池參數(shù)而無(wú)需重啟,這對(duì)于需要高度可配置且需要適應(yīng)不同負(fù)載情況的應(yīng)用來(lái)說(shuō)非常有用,本文給大家介紹實(shí)現(xiàn)動(dòng)態(tài)線程池的基本步驟,需要的朋友可以參考下2024-02-02