zoukankan      html  css  js  c++  java
  • GraphQL Java

    使用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

  • 相关阅读:
    Headless MSBuild Support for SSDT (*.sqlproj) Projects
    dbDacFx Provider for Incremental Database publishing
    Cannot spawn... TortoisePlink
    Windows server 2012同时进行多个会话登陆的策略设置
    Workspace Cloning / Sharing in Jenkins
    How to change Jenkins default folder on Windows?
    使用Jenkins配置自动化构建
    Auto push git tag
    Azure Deploy
    sql server中index的REBUILD和REORGANIZE
  • 原文地址:https://www.cnblogs.com/pku-liuqiang/p/11528338.html
Copyright © 2011-2022 走看看