How does backpressure work in Project Reactor?

后端 未结 1 1473
北海茫月
北海茫月 2020-12-10 09:35

I\'ve been working in Spring Reactor and had some previous testing that made me wonder how Fluxes handle backpressure by default. I know that onBackpressureBuffer and such e

相关标签:
1条回答
  • 2020-12-10 09:36

    I will share what I know about this topic which might be too much for your question but I hope it would be useful for others who are just getting familiar with backpressure and I might get corrections in comments if I misunderstand something about this concept.

    What is backpressure?

    Backpressure or the ability for the consumer to signal the producer that the rate of emission is too high - Reactor Reference

    When we are talking about backpressure we have to separate sources/publishers into two groups: the ones that respect the demand from the subscriber, and those that ignore it.

    Generally hot sources do not respect subscriber demand, since they often produce live data, like listening into a Twitter feed. In this example the subscriber doesn't have control over at what rate tweets are created, so it could get easily overwhelmed.

    On the other hand a cold source usually generates data on demand when subscription happens, like making an HTTP request and then processing the response. In this case the HTTP server you are calling will only send a response after you sent your request.

    Important to note that this is not a rule: not every hot source ignores the demand and not every cold source respects it. You can read more on hot and cold sources here.

    Let's look at some examples that might help in understanding.

    1. Publisher that respects the demand

    Given a Flux that produces numbers from 1 to Integer.MAX_VALUE and given a processing step that takes 100ms to process a single element:

    Flux.range(1, Integer.MAX_VALUE)
        .log()
        .concatMap(x -> Mono.delay(Duration.ofMillis(100)), 1) // simulate that processing takes time
        .blockLast();
    

    Let's see the logs:

    [ INFO] (main) | onSubscribe([Synchronous Fuseable] FluxRange.RangeSubscription)
    [ INFO] (main) | request(1)
    [ INFO] (main) | onNext(1)
    [ INFO] (main) | request(1)
    [ INFO] (main) | onNext(2)
    [ INFO] (parallel-1) | request(1)
    [ INFO] (parallel-1) | onNext(3)
    [ INFO] (parallel-2) | request(1)
    [ INFO] (parallel-2) | onNext(4)
    [ INFO] (parallel-3) | request(1)
    [ INFO] (parallel-3) | onNext(5)
    

    We can see that before every onNext there is a request. The request signal is sent by concatMap operator. It is signaled when concatMap finished the actual element and ready to accept the next one. The source only sends the next item when it receives a request from the downstream.

    In this example backpressure is automatic, we don't need to define any strategy because the operator knows what it can handle and the source respects it.

    1. Publisher that ignores the demand and no backpressure strategy is defined

    For the sake of simplicity I selected an easy to understand cold publisher for this example. It's Flux.interval. It makes sense that this cold publisher does not respect demand since it would be quite strange to see items emitted by different, longer intervals than the one originally specified.

    Let's see the code:

    Flux.interval(Duration.ofMillis(1))
        .log()
        .concatMap(x -> Mono.delay(Duration.ofMillis(100)))
        .blockLast();
    

    Source emits one item every millisecond. Subscriber is able to process one item every 100 milliseconds. It's clear that the subscriber is not able to keep up with the producer and we get an exception something like this quite soon:

    reactor.core.Exceptions$OverflowException: Could not emit tick 32 due to lack of requests (interval doesn't support small downstream requests that replenish slower than the ticks)
        at reactor.core.Exceptions.failWithOverflow(Exceptions.java:215)
        ...
    

    What can we do to avoid this exception?

    1. Publisher that ignores the demand and backpressure strategy is defined

    The default backpressure strategy is the one we have seen above: terminating with error. Reactor does not enforce any error handling strategy on us. When we see this kind of error we can decide which one is the most applicable for our use case.

    You can find a couple of them in Reactor reference.

    For this example we will use the simplest one: onBackpressureDrop.

    Flux.interval(Duration.ofMillis(1))
        .onBackpressureDrop()
        .concatMap(a -> Mono.delay(Duration.ofMillis(100)).thenReturn(a))
        .doOnNext(a -> System.out.println("Element kept by consumer: " + a))
        .blockLast();
    

    Output:

    Element kept by consumer: 0
    Element kept by consumer: 1
    Element kept by consumer: 2
    Element kept by consumer: 3
    Element kept by consumer: 4
    Element kept by consumer: 5
    Element kept by consumer: 6
    Element kept by consumer: 7
    Element kept by consumer: 8
    Element kept by consumer: 9
    Element kept by consumer: 10
    Element kept by consumer: 11
    Element kept by consumer: 12
    Element kept by consumer: 13
    Element kept by consumer: 14
    Element kept by consumer: 15
    Element kept by consumer: 16
    Element kept by consumer: 17
    Element kept by consumer: 18
    Element kept by consumer: 19
    Element kept by consumer: 20
    Element kept by consumer: 21
    Element kept by consumer: 22
    Element kept by consumer: 23
    Element kept by consumer: 24
    Element kept by consumer: 25
    Element kept by consumer: 26
    Element kept by consumer: 27
    Element kept by consumer: 28
    Element kept by consumer: 29
    Element kept by consumer: 30
    Element kept by consumer: 31
    Element kept by consumer: 2399
    Element kept by consumer: 2400
    Element kept by consumer: 2401
    Element kept by consumer: 2402
    Element kept by consumer: 2403
    Element kept by consumer: 2404
    Element kept by consumer: 2405
    Element kept by consumer: 2406
    Element kept by consumer: 2407
    

    We can see that after the first 32 items there is a quite big skip to 2400. The elements between are dropped due to the defined strategy.

    Key takeaways

    • Backpressure is often automatic and we don't need to do anything since we get data on demand.
    • In case of sources which do not respect subscriber demand we need to define a strategy to avoid terminating error.

    UPDATE: Useful read: How to control request rate

    0 讨论(0)
提交回复
热议问题