Spring WebFlux& WebClient进行许多API调用

时间:2017-12-31 13:51:10

标签: spring spring-webflux

最近我问了问题Spring WebFlux create pool of no-blocking threads

我得到了答案,阅读了所提供的链接,但仍然不理解做这些事情的正确方法。

我使用 Spring WebFlux(WebClient)来编写我的REST服务。对于每个传入的请求,我向另一个REST服务发出了数百个请求,因此为了尽可能快地使它们,我想使用无阻塞线程。

让我收到客户的请求,我必须拨打600个API:

List<String> urls = Arrays.asList("www.example-rest.com/url1", "www.example-rest.com/url2", ..., "www.example-rest.com/url600");

  • 我想以并行方式制作它们并使用无阻塞线程(如Python中的eventlet)
  • 我想创建具有此类线程的单独共享工作池,以便不为每个传入请求创建一个。

以下是计划程序的文档 http://projectreactor.io/docs/core/release/reference/#schedulers

我找到了有关弹性线程池的信息:

  

弹性线程池(Schedulers.elastic())。它创造了新的工人   根据需要使用池,并重用空闲池。这是I / O的不错选择   例如阻止工作。 Schedulers.elastic()是一种方便的方法   给一个阻塞进程自己的线程,这样它就不会占用   其他资源。

但是我无法为每个请求创建新的工作池,并且该池中的线程仍然以阻塞方式工作。

如果有人与 Spring WebClient 做了类似的任务,请提供一个示例,解释一下正确的方法。

1 个答案:

答案 0 :(得分:0)

我做过类似的事情......我的目标是创建一个带有这样签名的方法:

Flux<BasicIssue> getIssues(WebClient webClient);

因为我调用的网站只提供了分页界面,所以我需要将多个REST调用的结果提供给一个Flux。以下是我的实施。请注意我使用CachedThreadPool。

Flux<BasicIssue> getIssues(WebClient webClient) {
    return Flux.generate(
        () -> new IssuesState(webClient),
        (state, sink) -> {
            BasicIssue ret = state.getNext();
            if (ret == null) {
                sink.complete();
            } else {
                sink.next(ret);
            }
            return state;
        }
}


class IssuesState {

    private final AtomicBoolean isDone = new AtomicBoolean(false);
    private final AtomicInteger threadCount = new AtomicInteger(1);
    private final Executor executor = Executors.newCachedThreadPool();
    private final LinkedBlockingQueue<BasicIssue> issueQueue = new LinkedBlockingQueue();

    public IssuesState(WebClient webClient) {
        executor.execute(() -> getNextBlock(webClient, 0));
    }

    private void getNextBlock(final WebClient webClient, final int startAt) {
        webClient
            .get()
            .uri(...)
            .header("Authorization", "Basic " + Base64Utils.encodeToString(("username:password".getBytes(UTF_8))))
            .accept(MediaType.APPLICATION_JSON)
            .retrieve()
            .bodyToMono(PageableIssue.class)
            .subscribe(pageableIssue -> {
                int maxResults = pageableIssue.getMaxResults();
                int total = pageableIssue.getTotal();
                if (startAt == 0) {
                    for (int i = startAt + maxResults; i < total; i += maxResults) {
                        threadCount.incrementAndGet();
                        final int x = i;
                        executor.execute(() -> getNextBlock(webClient, x));
                    }
                }
                synchronized (issueQueue) {
                    for (BasicIssue issue : pageableIssue.getIssues()) {
                        issueQueue.add(issue);
                    }

                    if (threadCount.decrementAndGet() == 0) {
                        isDone.set(true);
                    }
                }
            });
    }

    public BasicIssue getNext() {
        synchronized (issueQueue) {
            if (isDone.get() && issueQueue.isEmpty()) {
                return null;
            }
        }
        try {
            return issueQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

使用上述方法..

getIssues(webClient)
    .subscribe(basicIssue -> System.out.println(basicIssue.getName());