如何使用WebClient限制请求/秒?

时间:2018-05-17 09:10:14

标签: spring spring-webflux project-reactor

我使用WebClient对象向服务器发送Http Post请求。 它非常迅速地发送大量请求(QueueChannel中有大约4000条消息)。问题是......似乎服务器响应速度不够快......所以我收到很多服务器错误500并且连接器过早关闭。

有没有办法限制每秒的请求数量?或者限制它使用的线程数?

编辑:

QueueChannel中的消息端点处理消息:

@MessageEndpoint
public class CustomServiceActivator {

    private static final Logger logger = LogManager.getLogger();

    @Autowired
    IHttpService httpService;

    @ServiceActivator(
            inputChannel = "outputFilterChannel",
            outputChannel = "outputHttpServiceChannel",
            poller = @Poller( fixedDelay = "1000" )
    )
    public void processMessage(Data data) {
        httpService.push(data);
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

WebClient服务类:

@Service
public class HttpService implements IHttpService {

    private static final String URL = "http://www.blabla.com/log";

    private static final Logger logger = LogManager.getLogger();

    @Autowired
    WebClient webClient;

    @Override
    public void push(Data data) {
        String body = constructString(data);
        Mono<ResponseEntity<Response>> res = webClient.post()
                .uri(URL + getLogType(data))
                .contentLength(body.length())
                .contentType(MediaType.APPLICATION_JSON)
                .syncBody(body)
                .exchange()
                .flatMap(response -> response.toEntity(Response.class));

        res.subscribe(new Consumer<ResponseEntity<Response>>() { ... });
    }
}

5 个答案:

答案 0 :(得分:5)

问题Limiting rate of requests with Reactor提供了两个回复者(一个在评论中)

zip与另一个充当速率限制器的通量

.zipWith(Flux.interval(Duration.of(1,ChronoUnit.SECONDS)))

只是延迟每个网络请求

使用 delayElements 功能

编辑:以下答案对于阻止RestTemplate有效,但不太适合反应模式。

WebClient无法限制请求,但您可以使用合成轻松添加此功能。

您可以使用Guava /中的RateLimiter从外部限制客户端 (https://google.github.io/guava/releases/19.0/api/docs/index.html?com/google/common/util/concurrent/RateLimiter.html

在本教程http://www.baeldung.com/guava-rate-limiter中,您将了解如何以阻止方式或超时使用速率限制器。

我会在单独的类

中修饰所有需要限制的调用
  1. 限制每秒通话次数
  2. 使用WebClient执行实际的网络通话

答案 1 :(得分:2)

我希望参加聚会不迟到。无论如何,限制请求的速率只是一周前我创建爬网程序时遇到的问题之一。这里是问题:

  1. 我必须执行一个递归,分页的顺序请求。分页参数包含在我要调用的API中。
  2. 一旦收到响应,请暂停1秒钟,然后再执行下一个请求。
  3. 对于遇到的某些错误,请重试
  4. 重试时,暂停几秒钟

这是解决方案:

"""
---------------------------
d.txt
---------------------------
alice
wonderland
alice-again
oh-dear-alice
---------------------------
alice.txt
---------------------------
aline
alice
oh-no-alice
---------------------------

"""

dictionary = list(open('d.txt','r'))
dictionary = set([i.strip() for i in dictionary])
#Once you have your list of words
#dictionary = set(get_dict_list())
input_file = list(open('alice.txt','r'))
input_file = set([i.strip() for i in input_file])
#input_file = set(get_story_list())
misspelled_words = input_file - dictionary

答案 2 :(得分:1)

我用它来限制活动请求的数量:

public DemoClass(WebClient.Builder webClientBuilder) {
    AtomicInteger activeRequest = new AtomicInteger();
    this.webClient = webClientBuilder
            .baseUrl("http://httpbin.org/ip")
            .filter(
                    (request, next) -> Mono.just(next)
                            .flatMap(a -> {
                                if (activeRequest.intValue() < 3) {
                                    activeRequest.incrementAndGet();
                                    return next.exchange(request)
                                            .doOnNext(b -> activeRequest.decrementAndGet());
                                }
                              return Mono.error(new RuntimeException("Too many requests"));
                            })
                            .retryWhen(Retry.anyOf(RuntimeException.class)
                                    .randomBackoff(Duration.ofMillis(300), Duration.ofMillis(1000))
                                    .retryMax(50)
                            )
            )
            .build();
}

public Mono<String> call() {
    return webClient.get()
            .retrieve()
            .bodyToMono(String.class);
}

答案 3 :(得分:0)

Resilience4j对Project Reactor的无阻塞速率限制提供了出色的支持。

必需的依赖项(在Spring WebFlux旁边):

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-reactor</artifactId>
    <version>1.6.1</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-ratelimiter</artifactId>
    <version>1.6.1</version>
</dependency>

示例:

import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import io.github.resilience4j.reactor.ratelimiter.operator.RateLimiterOperator;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicInteger;

public class WebClientRateLimit
{
    private static final AtomicInteger COUNTER = new AtomicInteger(0);

    private final WebClient webClient;
    private final RateLimiter rateLimiter;

    public WebClientRateLimit()
    {
        this.webClient = WebClient.create();

        // enables 3 requests every 5 seconds
        this.rateLimiter = RateLimiter.of("my-rate-limiter",
                RateLimiterConfig.custom()
                                 .limitRefreshPeriod(Duration.ofSeconds(5))
                                 .limitForPeriod(3)
                                 .timeoutDuration(Duration.ofMinutes(1)) // max wait time for a request, if reached then error
                                 .build());
    }

    public Mono<?> call()
    {
        return webClient.get()
                        .uri("https://jsonplaceholder.typicode.com/todos/1")
                        .retrieve()
                        .bodyToMono(String.class)
                        .doOnSubscribe(s -> System.out.println(COUNTER.incrementAndGet() + " - " + LocalDateTime.now()
                                + " - call triggered"))
                        .transformDeferred(RateLimiterOperator.of(rateLimiter));
    }

    public static void main(String[] args)
    {
        WebClientRateLimit webClientRateLimit = new WebClientRateLimit();

        long start = System.currentTimeMillis();

        Flux.range(1, 16)
            .flatMap(x -> webClientRateLimit.call())
            .blockLast();

        System.out.println("Elapsed time in seconds: " + (System.currentTimeMillis() - start) / 1000d);
    }
}

示例输出:

1 - 2020-11-30T15:44:01.575003200 - call triggered
2 - 2020-11-30T15:44:01.821134 - call triggered
3 - 2020-11-30T15:44:01.823133100 - call triggered
4 - 2020-11-30T15:44:04.462353900 - call triggered
5 - 2020-11-30T15:44:04.462353900 - call triggered
6 - 2020-11-30T15:44:04.470399200 - call triggered
7 - 2020-11-30T15:44:09.461199100 - call triggered
8 - 2020-11-30T15:44:09.463157 - call triggered
9 - 2020-11-30T15:44:09.463157 - call triggered
11 - 2020-11-30T15:44:14.461447700 - call triggered
10 - 2020-11-30T15:44:14.461447700 - call triggered
12 - 2020-11-30T15:44:14.461447700 - call triggered
13 - 2020-11-30T15:44:19.462098200 - call triggered
14 - 2020-11-30T15:44:19.462098200 - call triggered
15 - 2020-11-30T15:44:19.468059700 - call triggered
16 - 2020-11-30T15:44:24.462615 - call triggered
Elapsed time in seconds: 25.096

文档:https://resilience4j.readme.io/docs/examples-1#decorate-mono-or-flux-with-a-ratelimiter

答案 4 :(得分:0)

我们可以自定义ConnectionBuilder以对WebClient上的活动连接进行速率限制。

由于队列的默认大小始终为2 * maxConnections,因此需要为队列中的等待请求数添加endingAquiredMaxCount。

此速率限制了Web客户端一次处理请求。

ConnectionProvider provider = ConnectionProvider.builder('builder').maxConnections(maxConnections).pendingAcquireMaxCount(maxPendingRequests).build()
TcpClient tcpClient = TcpClient
                        .create(provider)
       
WebClient client = WebClient.builder()
                        .baseUrl('url')
                        .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))