Spring boot 反应堆流量平面图操作员吞吐量/并发控制和实现背压
我正在使用Flux构建我的反应管道。在管道中,我需要调用3种不同的外部系统RESTAPI,它们对访问速率要求非常严格。 如果我违反每秒速率阈值,我将被指数级限制。每个系统都有自己的阈值 我正在使用SpringWebClient进行RESTAPI调用;在3个API中,2个是GET,1个是POST 在我的reactor管道中,WebClient被包装在flatMap中以执行API调用,如下代码所示: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(
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?@NaveenKumarGenerate()
从背压的角度来看是理想的场景-需求完全由背压驱动。因此,它不需要溢出策略,因为元素在显式预取之前不会被请求create()
本质上是另一个API/源的包装器,它可以根据需要任意发出任意数量的事件,因此它没有明确的背压控制,因此需要某种形式的溢出策略来确定消费者无法跟上时的行为<代码>生成()因此,如果您可以使用它,它几乎总是首选。我在最初的问题中遗漏了一个要点。当我应用一个调度器.subscribeOn(Schedulers.boundedElastic())
时,这一切是如何进行的?我所看到的是,当我在管道中应用调度程序时,请求率非常高。@NaveenKumar不确定这会有什么不同——我似乎无法快速生成它。我要开始一个新的问题,并确保包括一个。如果有多个源(每个请求的流量),在这种情况下,上述代码将超过每秒请求的数量。我们如何控制多流量(多来源)每秒的付款数量嗨,Martin Tarjányi,谢谢你的指点,是的!事实上,我正在探索它们。嗨,马丁·塔尔贾尼,谢谢你的指点,是的!事实上,我正在探索它们。