从分页的网络调用序列中生成Spring Flux

时间:2019-06-26 11:02:38

标签: java spring-webflux reactive

我正在使用Spring响应式WebFlux客户端调用API,api.magicthegathering.io / v1 / cards。响应是一个包含100张卡片的页面,以及包含“下一页”和“最后一页”链接的标题,例如“ last”是api.magicthegathering.io/v1/cards?page=426(而“ next”就是n + 1)。我想生成一个Flux<Card>,它可以通过单个入口点(例如, Flux<Card> getAllCards()

我目前有一个CardsClient组件,它返回一个Mono<CardPage>CardPage上有一个cards()方法,该方法返回其中的所有卡(这是API响应模型的1:1表示)。最重要的是,我有一个CardCatalog组件,上面带有该getAllCards()方法。

我尝试使用Flux::expandFlux::generate,虽然有些奏效,但是这些实现都有缺陷。

这是我当前的CardCatalog::getAllCards()迭代的摘要。问题在于expand的递归性质导致对client::getNextPage的冗余调用;显然我没有使用正确的方法。

  @Override
  public Flux<Card> getAllCards() {
    return client.getFirstPage().flux().expand(client::getNextPage)
        .map(Page::cards)
        .flatMap(Flux::fromIterable)
        .map(mapper::convert)
        .cache();
  }

以前我使用的是generate,但是问题是,即使订户只决定使用take(20)张卡,它也总是会抓取所有页面(相当慢):

 @Override
  public Flux<Card> getAllCards() {
    final Flux<Page> pageFlux =
        generate(client::getFirstPage, (response, sink) -> {
          final var page = response.block();
          sink.next(page);

          if (page.next().isPresent()) {
            return client.getNextPage(page);
          }
          sink.complete();
          return null;
        });

    return pageFlux.flatMapIterable(Page::cards).map(mapper::convert);
  }

完整代码在这里:https://github.com/myersadamk/mtg-api-client

我使用expandclient::getNextPage()添加了打印件。如您所见,该图正在创建中,产生了多余的调用。

Getting https://api.magicthegathering.io/v1/cards?page=1
Getting https://api.magicthegathering.io/v1/cards?page=7
Getting https://api.magicthegathering.io/v1/cards?page=2
Getting https://api.magicthegathering.io/v1/cards?page=8
Getting https://api.magicthegathering.io/v1/cards?page=3
Getting https://api.magicthegathering.io/v1/cards?page=9
Getting https://api.magicthegathering.io/v1/cards?page=4
Getting https://api.magicthegathering.io/v1/cards?page=10
Getting https://api.magicthegathering.io/v1/cards?page=5
Getting https://api.magicthegathering.io/v1/cards?page=11
Getting https://api.magicthegathering.io/v1/cards?page=6
Getting https://api.magicthegathering.io/v1/cards?page=12
Getting https://api.magicthegathering.io/v1/cards?page=7

我想要更多类似的东西:

Getting https://api.magicthegathering.io/v1/cards?page=1
Getting https://api.magicthegathering.io/v1/cards?page=2
Getting https://api.magicthegathering.io/v1/cards?page=3

(最后一点:将其并行化并直接调用URI当然会更快,但是绕过下一个/最后一个机制并对URI进行硬编码感觉有点愚蠢。我可能最终会这样做,但是仍然要弄碎这个螺母。)

2 个答案:

答案 0 :(得分:0)

好的,我想出了一些可行的方法。我决定使用页面计数方法来尝试并行化,尽管由于网络IO仍然是瓶颈,这种方法并没有很快。我可能会回到标题链接爬网并使用缓存。简而言之,魔术数字和所有数字,看起来就是这样:

  @Override
  public Flux<Card> getAllCards() {
    return client.getPageCount().flatMapMany(pageCount ->
        Flux.concat(
            range(1, pageCount)
                .parallel(pageCount / 6).runOn(Schedulers.parallel())
                .map(client::getPage)
        ).map(Page::cards).flatMap(Flux::fromIterable).map(mapper::convert)
    );
  }

答案 1 :(得分:0)

我认为这是执行此操作的顺序无阻塞方式:

public Flux<Card> getAllCards() {
    PaginationParams paginationParams = new PaginationParams();

    final Flux<Page> pageFlux = Mono
            .defer(() -> client.getPage(paginationParams))
            .doOnNext(page -> {
                if (page.next().isPresent()) {
                    paginationParams.setPage(page.next().get());
                } else {
                    paginationParams.setPage(null);
                }
            })
            .repeat(() -> paginationParams.getPage() != null);

    return pageFlux.flatMapIterable(Page::cards).map(mapper::convert);
}