Spring boot 反应堆流量平面图操作员吞吐量/并发控制和实现背压

Spring boot 反应堆流量平面图操作员吞吐量/并发控制和实现背压,spring-boot,spring-webflux,project-reactor,reactive-streams,spring-webclient,Spring Boot,Spring Webflux,Project Reactor,Reactive Streams,Spring Webclient,我正在使用Flux构建我的反应管道。在管道中,我需要调用3种不同的外部系统RESTAPI,它们对访问速率要求非常严格。 如果我违反每秒速率阈值,我将被指数级限制。每个系统都有自己的阈值 我正在使用SpringWebClient进行RESTAPI调用;在3个API中,2个是GET,1个是POST 在我的reactor管道中,WebClient被包装在flatMap中以执行API调用,如下代码所示: WebClient getApiCall1 = WebClient.builder().build(

我正在使用Flux构建我的反应管道。在管道中,我需要调用3种不同的外部系统RESTAPI,它们对访问速率要求非常严格。 如果我违反每秒速率阈值,我将被指数级限制。每个系统都有自己的阈值

我正在使用SpringWebClient进行RESTAPI调用;在3个API中,2个是GET,1个是POST

在我的reactor管道中,WebClient被包装在flatMap中以执行API调用,如下代码所示:

WebClient getApiCall1 = WebClient.builder().build().get("api-system-1").retrieve().bodyToMono(String.class) //actual return DTO is different from string
WebClient getApiCall2 = WebClient.builder().build().get("api-system-2").retrieve().bodyToMono(String.class) //actual return DTO is different from string
WebClient getApiCall3 = WebClient.builder().build().get("api-system-3").retrieve().bodyToMono(String.class) //actual return DTO is different from string

    Flux.generator(generator) // Generator pushes the elements from source 1 at a time

    // make call to 1st API Service
    .flatMap(data -> getApiCall1)
    .map(api1Response -> api1ResponseModified)

    // make call to 2nd API Service
    .flatMap(api1ResponseModified -> getApiCall2)
    .map(api2Response -> api2ResponseModified)

// make call to 3rd API Service
.flatMap(api2ResponseModified -> getApiCall3)
.map(api3Response -> api3ResponseModified)

// rest of the pipeline operators

//end
.subscriber();
问题是,如果我没有将
concurrency
值设置为flatMap,那么我会在服务启动的几秒钟内发现管道执行超过阈值。 如果我将
concurrency
的值设置为1、2、5、10,那么吞吐量将变得非常低

问题是,在不对并发性设置任何值的情况下,我如何实现应遵守外部系统速率限制的背压?

如果您有“每秒速率”要求,我将明确设置通量窗口,并将每个窗口限制在选定的时间段内。这将在不受限制的情况下为您提供最大吞吐量

我将使用一个类似于以下内容的助手函数:

public static <T> Flux<T> limitIntervalRate(Flux<T> flux, int ratePerInterval, Duration interval) {
    return flux
            .window(ratePerInterval)
            .zipWith(Flux.interval(Duration.ZERO, interval))
            .flatMap(Tuple2::getT1);
}
然后,您可以根据需要将此映射到
WebClient
调用,同时遵守每个API的限制:

sourceFlux
        //...assume API 1 has a limit of 10 calls per second
        .transform(f -> limitIntervalRate(f, 10, Duration.ofSeconds(1)))
        .flatMap(data -> getApiCall1)
        .map(api1Response -> api1ResponseModified)

        //...assume API 2 has a limit of 20 calls per second
        .transform(f -> limitIntervalRate(f, 20, Duration.ofSeconds(1))) 
        .flatMap(api1ResponseModified -> getApiCall2)
        .map(api2Response -> api2ResponseModified)
…等等。

如果您有“每秒速率”的要求,我将明确地设置流量窗口,并将每个窗口限制在所选的时间段内。这将在不受限制的情况下为您提供最大吞吐量

我将使用一个类似于以下内容的助手函数:

public static <T> Flux<T> limitIntervalRate(Flux<T> flux, int ratePerInterval, Duration interval) {
    return flux
            .window(ratePerInterval)
            .zipWith(Flux.interval(Duration.ZERO, interval))
            .flatMap(Tuple2::getT1);
}
然后,您可以根据需要将此映射到
WebClient
调用,同时遵守每个API的限制:

sourceFlux
        //...assume API 1 has a limit of 10 calls per second
        .transform(f -> limitIntervalRate(f, 10, Duration.ofSeconds(1)))
        .flatMap(data -> getApiCall1)
        .map(api1Response -> api1ResponseModified)

        //...assume API 2 has a limit of 20 calls per second
        .transform(f -> limitIntervalRate(f, 20, Duration.ofSeconds(1))) 
        .flatMap(api1ResponseModified -> getApiCall2)
        .map(api2Response -> api2ResponseModified)

…等等。

Resilience4j支持反应堆的速率限制。 见:


Resilience4j支持反应堆的速率限制。 见:


谢谢您的建议和示例代码@Michael Berry。还有一个问题,我将通过编程方式创建一个序列,在生成器下没有提到背压。但是,在Create中,我看到支持背压。这是否意味着,如果我需要背压支持,我应该使用Create而不是Generate?@NaveenKumar
Generate()
从背压的角度来看是理想的场景-需求完全由背压驱动。因此,它不需要溢出策略,因为元素在显式预取之前不会被请求
create()
本质上是另一个API/源的包装器,它可以根据需要任意发出任意数量的事件,因此它没有明确的背压控制,因此需要某种形式的溢出策略来确定消费者无法跟上时的行为<代码>生成()因此,如果您可以使用它,它几乎总是首选。我在最初的问题中遗漏了一个要点。当我应用一个调度器
.subscribeOn(Schedulers.boundedElastic())
时,这一切是如何进行的?我所看到的是,当我在管道中应用调度程序时,请求率非常高。@NaveenKumar不确定这会有什么不同——我似乎无法快速生成它。我要开始一个新的问题,并确保包括一个。如果有多个源(每个请求的流量),在这种情况下,上述代码将超过每秒请求的数量。我们如何控制多流量(多来源)每秒的付款数量感谢Michael Berry的建议和示例代码。还有一个问题,我将通过编程方式创建一个序列,在生成器下没有提到背压。但是,在Create中,我看到支持背压。这是否意味着,如果我需要背压支持,我应该使用Create而不是Generate?@NaveenKumar
Generate()
从背压的角度来看是理想的场景-需求完全由背压驱动。因此,它不需要溢出策略,因为元素在显式预取之前不会被请求
create()
本质上是另一个API/源的包装器,它可以根据需要任意发出任意数量的事件,因此它没有明确的背压控制,因此需要某种形式的溢出策略来确定消费者无法跟上时的行为<代码>生成()因此,如果您可以使用它,它几乎总是首选。我在最初的问题中遗漏了一个要点。当我应用一个调度器
.subscribeOn(Schedulers.boundedElastic())
时,这一切是如何进行的?我所看到的是,当我在管道中应用调度程序时,请求率非常高。@NaveenKumar不确定这会有什么不同——我似乎无法快速生成它。我要开始一个新的问题,并确保包括一个。如果有多个源(每个请求的流量),在这种情况下,上述代码将超过每秒请求的数量。我们如何控制多流量(多来源)每秒的付款数量嗨,Martin Tarjányi,谢谢你的指点,是的!事实上,我正在探索它们。嗨,马丁·塔尔贾尼,谢谢你的指点,是的!事实上,我正在探索它们。