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

  • 相关阅读:
    UVA 10600 ACM Contest and Blackout(次小生成树)
    UVA 10369
    UVA Live 6437 Power Plant 最小生成树
    UVA 1151 Buy or Build MST(最小生成树)
    UVA 1395 Slim Span 最小生成树
    POJ 1679 The Unique MST 次小生成树
    POJ 1789 Truck History 最小生成树
    POJ 1258 Agri-Net 最小生成树
    ubuntu 用法
    ubuntu 搭建ftp服务器,可以通过浏览器访问,filezilla上传文件等功能
  • 原文地址:https://www.cnblogs.com/pku-liuqiang/p/11528338.html
Copyright © 2011-2022 走看看