我正在使用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::expand
和Flux::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
我使用expand
向client::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进行硬编码感觉有点愚蠢。我可能最终会这样做,但是仍然要弄碎这个螺母。)
答案 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);
}