GraphQL Java - Batching

使用DataLoader

使用GraphQL的过程中,可能需要在一个图数据上做多次查询。使用原始的数据加载方式,很容易产生性能问题。

通过使用java-dataloader,可以结合缓存(Cache)和批处理(Batching)的方式,在图形数据上发起批量请求。如果dataloader已经获取过相关的数据,那么它会缓存数据的值,然后直接返回给调用方(无需重复发起请求)。

假设我们有一个StarWars的执行语句如下:它允许我们找到一个hero,他的朋友的名字以及朋友的朋友的名字。显然会有一部分朋友数据,会在这个查询中被多次请求到。

        {
            hero {
                name
                friends {
                    name
                    friends {
                       name
                    }
                }
            }
        }

其查询结果如下所示:

        {
          "hero": {
            "name": "R2-D2",
            "friends": [
              {
                "name": "Luke Skywalker",
                "friends": [
                  {"name": "Han Solo"},
                  {"name": "Leia Organa"},
                  {"name": "C-3PO"},
                  {"name": "R2-D2"}
                ]
              },
              {
                "name": "Han Solo",
                "friends": [
                  {"name": "Luke Skywalker"},
                  {"name": "Leia Organa"},
                  {"name": "R2-D2"}
                ]
              },
              {
                "name": "Leia Organa",
                "friends": [
                  {"name": "Luke Skywalker"},
                  {"name": "Han Solo"},
                  {"name": "C-3PO"},
                  {"name": "R2-D2"}
                ]
              }
            ]
          }
        }

比较原始的实现方案是,每次query的时候都调用一次DataFetcher来获取一个person对象。

在这种场景下,将会发起15次调用,并且其中有很多数据被多次、重复请求。结合dataLoader,可以使数据的请求效率更高。

针对Query语句的层级,GraphQL会逐层次下降依次查询。(例如:首先处理hero字段,然后处理friends,然后处理每个friend的friends)。data loader是一种契约,使用它可以获得查询的对象,但它将延迟发起对象数据的请求。在每一个层级上,dataloader.dispatch()方法会批量触发这一层级上的所有请求。在开启了缓存的条件下,任何之前已请求到的数据都会直接返回,而不会再次发起请求调用。

上述的实例中,只有五个唯一的person对象。通过使用缓存+批处理的获取方式,实际上只发起了三次网络调用就实现了数据的请求。

相比于原始的15次请求方式,效率大大提升。

如果使用了java.util.concurrent.CompletableFuture.supplyAsync(),还可以通过开启异步执行的方式,进一步提升执行效率,减少响应时间。

示例代码如下:

        //
        // a batch loader function that will be called with N or more keys for batch loading
        // This can be a singleton object since it's stateless
        //
        BatchLoader<String, Object> characterBatchLoader = new BatchLoader<String, Object>() {
            @Override
            public CompletionStage<List<Object>> load(List<String> keys) {
                //
                // we use supplyAsync() of values here for maximum parellisation
                //
                return CompletableFuture.supplyAsync(() -> getCharacterDataViaBatchHTTPApi(keys));
            }
        };

        //
        // use this data loader in the data fetchers associated with characters and put them into
        // the graphql schema (not shown)
        //
        DataFetcher heroDataFetcher = new DataFetcher() {
            @Override
            public Object get(DataFetchingEnvironment environment) {
                DataLoader<String, Object> dataLoader = environment.getDataLoader("character");
                return dataLoader.load("2001"); // R2D2
            }
        };

        DataFetcher friendsDataFetcher = new DataFetcher() {
            @Override
            public Object get(DataFetchingEnvironment environment) {
                StarWarsCharacter starWarsCharacter = environment.getSource();
                List<String> friendIds = starWarsCharacter.getFriendIds();
                DataLoader<String, Object> dataLoader = environment.getDataLoader("character");
                return dataLoader.loadMany(friendIds);
            }
        };

        //
        // this instrumentation implementation will dispatch all the data loaders
        // as each level of the graphql query is executed and hence make batched objects
        // available to the query and the associated DataFetchers
        //
        // In this case we use options to make it keep statistics on the batching efficiency
        //
        DataLoaderDispatcherInstrumentationOptions options = DataLoaderDispatcherInstrumentationOptions
                .newOptions().includeStatistics(true);

        DataLoaderDispatcherInstrumentation dispatcherInstrumentation
                = new DataLoaderDispatcherInstrumentation(options);

        //
        // now build your graphql object and execute queries on it.
        // the data loader will be invoked via the data fetchers on the
        // schema fields
        //
        GraphQL graphQL = GraphQL.newGraphQL(buildSchema())
                .instrumentation(dispatcherInstrumentation)
                .build();

        //
        // a data loader for characters that points to the character batch loader
        //
        // Since data loaders are stateful, they are created per execution request.
        //
        DataLoader<String, Object> characterDataLoader = DataLoader.newDataLoader(characterBatchLoader);

        //
        // DataLoaderRegistry is a place to register all data loaders in that needs to be dispatched together
        // in this case there is 1 but you can have many.
        //
        // Also note that the data loaders are created per execution request
        //
        DataLoaderRegistry registry = new DataLoaderRegistry();
        registry.register("character", characterDataLoader);

        ExecutionInput executionInput = newExecutionInput()
                .query(getQuery())
                .dataLoaderRegistry(registry)
                .build();

        ExecutionResult executionResult = graphQL.execute(executionInput);

如上,我们添加了DataLoaderDispatcherInstrument实例。因为我们想要调整它的初始化选项(Options)。如果不去显式指定的话,它默认会自动添加进来。

使用AsyncExecutionStrategy策略的Data Loader

graphql.execution.AsyncExecutionStrategy是dataLoader的唯一执行策略。这个执行策略可以自行确定dispatch的最佳时间,它通过追踪还有多少字段未完成,以及它们是否为列表值等来实现此目的。

其他的执行策略,例如:ExecutorServiceExecutionStrategy策略无法实现该功能。当data loader检测到并未使用AsyncExecutionStrategy策略时,它会在遇到每个field时都调用data loader的dispatch方法。虽然可以通过缓存值的方式减少请求次数,但无法使用批量请求策略。

request特定的Data Loader

如果正在发起Web请求,那么数据可以特定于请求它的用户。 如果有特定于用户的数据,且不希望缓存用于用户A的数据,然后在后续请求中将其提供给用户B。

DataLoader实例的作用域很重要。为每个web请求创建dataLoader实例,并确保数据仅仅缓存在该web请求中,而对于其他web请求无效。它也确保了调用仅仅影响本次graphql的执行,而不影响其他的graphql请求执行。

默认情况下,DataLoaders充当缓存。 如果访问到之前请求过的key的值,那么它们会自动返回它以便提高效率。

如果数据需要在多个web请求当中共享,那么需要修改data loader的缓存实现,以使不同的请求之间,其data loader可以通过一些中间层(如redis缓存或memcached)共享数据。

在使用的过程中,仍然为每次请求都创建一个data loaders,通过缓存层在不同的data loader之间开启数据共享。

        CacheMap<String, Object> crossRequestCacheMap = new CacheMap<String, Object>() {
            @Override
            public boolean containsKey(String key) {
                return redisIntegration.containsKey(key);
            }

            @Override
            public Object get(String key) {
                return redisIntegration.getValue(key);
            }

            @Override
            public CacheMap<String, Object> set(String key, Object value) {
                redisIntegration.setValue(key, value);
                return this;
            }

            @Override
            public CacheMap<String, Object> delete(String key) {
                redisIntegration.clearKey(key);
                return this;
            }

            @Override
            public CacheMap<String, Object> clear() {
                redisIntegration.clearAll();
                return this;
            }
        };

        DataLoaderOptions options = DataLoaderOptions.newOptions().setCacheMap(crossRequestCacheMap);

        DataLoader<String, Object> dataLoader = DataLoader.newDataLoader(batchLoader, options);

异步调用batch loader功能

采用data loader的编码模式,通过将所有未完成的data loader请求合并为一个批量加载的请求,提高了请求的效率。

GraphQL - Java会追踪那些尚未完成的data loader请求,并在最合适的时间调用dispatch方法,触发数据的批量请求。

TODO

原文地址:https://www.cnblogs.com/pku-liuqiang/p/11528338.html

时间: 2024-08-29 23:59:24

GraphQL Java - Batching的相关文章

Why GraphQL is Taking Over APIs

A few years ago, I managed a team at DocuSign that was tasked with re-writing the main DocuSign web app which was used by tens of millions of users. The APIs didn’t exist yet to support our new shiny front-end app because since the beginning the web

Java性能提示(全)

http://www.onjava.com/pub/a/onjava/2001/05/30/optimization.htmlComparing the performance of LinkedLists and ArrayLists (and Vectors) (Page last updated May 2001, Added 2001-06-18, Author Jack Shirazi, Publisher OnJava). Tips: ArrayList is faster than

Java高性能线程库:Jetlang

Jetlang 提供了一个高性能的Java线程库,该库是 JDK 1.5 中的 java.util.concurrent 包的补充,可用于基于并发消息机制的应用.该类库不提供远程的消息功能,其设计的宗旨是实现一个内存中的消息传递机制: 主要特点有: All messages to a particular Fiber are delivered sequentially. Components can easily keep state without synchronizing data ac

[Java Performance] 数据库性能最佳实践 - JPA和读写优化

数据库性能最佳实践 当应用需要连接数据库时,那么应用的性能就可能收到数据库性能的影响.比如当数据库的I/O能力存在限制,或者因缺失了索引而导致执行的SQL语句需要对整张表进行遍历.对于这些问题,仅仅对应用代码进行优化可能是不够,还需要了解数据库的知识和特点. 示例数据库 该数据库表示了128只股票在1年内(261个工作日)的股价信息. 其中有两张表:STOCKPRICE和STOCKOPTIONPRICE. STOCKPRICE中使用股票代码作为主键,另外还有日期字段.它有33408条记录(128

GraphQL介绍&amp;使用nestjs构建GraphQL查询服务

GraphQL介绍&使用nestjs构建GraphQL查询服务(文章底部附demo地址) GraphQL一种用为你 API 而生的查询语言.出自于Facebook,GraphQL非常易懂,直接看查询语句就能知道查询出来的数据是什么样的.本质上属于API Layer层,负责前端请求的合并.数据整理等功能. 查询示例 使用几个简单的例子看下GraphQL的查询是什么样子的. 普通查询 { me { name } } 查询出来的数据格式如下: { "me": { "name

Kafka笔记整理(二):Kafka Java API使用

[TOC] Kafka笔记整理(二):Kafka Java API使用 下面的测试代码使用的都是下面的topic: $ kafka-topics.sh --describe hadoop --zookeeper uplooking01:2181,uplooking02:2181,uplooking03:2181 Topic:hadoop PartitionCount:3 ReplicationFactor:3 Configs: Topic: hadoop Partition: 0 Leader:

以LeetCode为例——如何发送GraphQL Query获取数据

前言 ??GraphQL 是一种用于 API 的查询语言,是由 Facebook 开源的一种用于提供数据查询服务的抽象框架.在服务端 API 开发中,很多时候定义一个接口返回的数据相对固定,因此要获得更多信息或者只想得到某部分信息时,基于 RESTful API 的接口就显得不那么灵活.而 GraphQL 对 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具. ??目前,L

以LeetCode为例重庆幸运农场——如何发送GraphQL Query获取数据

前言GraphQL 是一种用于 API 的查询语言重庆幸运农场QQ2952777280[话仙源码论坛]hxforum.com[木瓜源码论坛]papayabbs.com ,是由 Facebook 开源的一种用于提供数据查询服务的抽象框架.在服务端 API 开发中,很多时候定义一个接口返回的数据相对固定,因此要获得更多信息或者只想得到某部分信息时,基于 RESTful API 的接口就显得不那么灵活.而 GraphQL 对 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要

PowerBuilder编程新思维3:适配(三层架构与GraphQL)

PowerBuilder编程新思维3:适配(三层架构与GraphQL) PB在富客户端时代,是一线开发工具.随着网络发展,主流架构演进到三层架构的时代,PB拿不出有力的三层架构,已经明显力不从心,市场份额也江河日下.今天我们来细数一下PB的三层架构方式及其改进方法. PB三层架构方式一:EAServer 这是PB官方首推的三层架构,但是用三句可以总结,无感的体验,无奈的价格,无语的速度. 事实上除了EAServer这个选择,可以自己开发服务端,比如topwiz公司的PBNIServ 使用BPNI