如何限制活动的Spring WebClient调用次数

时间:2018-07-30 16:28:01

标签: java webclient project-reactor reactor

我有一个要求,在使用Kafka主题编写数据之前,我需要使用Spring Batch从SQL DB中读取一堆行(数千),并调用REST服务以丰富内容。

使用Spring Reactive webClient时,如何限制活动的非阻塞服务调用的数量?使用Spring Batch读取数据后,应该在循环中以某种方式引入Flux吗?

(我了解delayElements的用法,并且它有不同的用途,例如当单个Get Service Call带来大量数据并且您希望服务器速度变慢时-尽管这里,我的用例有些不同因为我要进行许多WebClient调用,并且希望限制调用次数以避免内存不足问题,但仍然获得了非阻塞调用的优势。

1 个答案:

答案 0 :(得分:1)

非常有趣的问题。我仔细考虑了一下,然后想到了一些有关如何实现的想法。我将分享我的想法,希望这里有一些想法可能对您的调查有所帮助。

不幸的是,我对Spring Batch不熟悉。但是,这听起来像是rate limiting或经典producer-consumer problem的问题。

因此,我们的生产者产生的消息太多,以至于我们的消费者无法跟上,中间的缓冲变得难以忍受。

我看到的问题是,正如您所描述的,您的Spring Batch进程不能作为流或管道运行,而您的反应式Web客户端可以。

因此,如果我们能够将数据作为流读取,那么当记录开始进入管道时,这些记录将由反应式Web客户端处理,并使用反压,我们可以控制来自生产者/数据库方面。

生产者方

因此,我要更改的第一件事是如何从数据库中提取记录。我们需要控制一次从数据库中读取多少条记录,方法是分页进行数据检索,或者控制fetch size,然后施加反压,控制通过反应式管道向下游发送的记录数量

因此,请考虑包装在Flux中的以下(基本)数据库数据检索。

Flux<String> getData(DataSource ds)  {
    return Flux.create(sink -> {
        try {
            Connection con = ds.getConnection();
            con.setAutoCommit(false);
            PreparedStatement stm = con.prepareStatement("SELECT order_number FROM orders WHERE order_date >= '2018-08-12'", ResultSet.TYPE_FORWARD_ONLY);
            stm.setFetchSize(1000);
            ResultSet rs = stm.executeQuery();

            sink.onRequest(batchSize -> {
                try {
                    for (int i = 0; i < batchSize; i++) {
                        if (!rs.next()) {
                            //no more data, close resources!
                            rs.close();
                            stm.close();
                            con.close();
                            sink.complete();
                            break;
                        }
                        sink.next(rs.getString(1));
                    }
                } catch (SQLException e) {
                    //TODO: close resources here
                    sink.error(e);
                }
            });
        }
        catch (SQLException e) {
            //TODO: close resources here
            sink.error(e);
        }
    });
}

在上面的示例中:

  • 我通过设置提取大小将每批读取的记录数量控制为1000。
  • 接收器将发送用户请求的记录量(即batchSize),然后等待它使用背压请求更多记录。
  • 如果结果集中没有更多记录,那么我们完成接收器并关闭资源。
  • 如果在任何时候发生错误,我们都会发回错误并关闭资源。
  • 或者,我本可以使用分页来读取数据,因为可能必须在每个请求周期重新发出查询,从而简化了资源处理。
  • 如果取消或处置订阅(sink.onCancelsink.onDispose),您可能会考虑采取其他措施,因为在此关闭连接和其他资源至关重要。

消费者方

在用户端,您注册的订户当时仅以1000的速度请求消息,并且仅在处理完该批次后才会请求更多消息。

getData(source).subscribe(new BaseSubscriber<String>() {

    private int messages = 0;

    @Override
    protected void hookOnSubscribe(Subscription subscription) {
        subscription.request(1000);
    }

    @Override
    protected void hookOnNext(String value) {
        //make http request
        System.out.println(value);
        messages++;
        if(messages % 1000 == 0) {
            //when we're done with a batch
            //then we're ready to request for more
            upstream().request(1000);
        }
    }
});

在上面的示例中,订阅开始时,它请求第一批1000条消息。在onNext中,我们处理第一批,使用Web客户端发出http请求。

完成该批次后,我们将要求发布者再提供1000个批次,依此类推。

在那里,您拥有它!使用反压力,您可以控制您当时有多少个打开的HTTP请求。

我的示例非常初级,需要进行一些额外的工作才能使其准备就绪,但是我相信,这有望提供一些可以适应您的Spring Batch方案的想法。