Are subscribers in Spring Reactor unbounded by default?

北战南征 提交于 2020-07-15 08:47:39

问题


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 exist, and I have also read that RxJava defaults to unbounded until you define whether to buffer, drop, etc.

So, can anyone clarify for me: What is the default backpressure behavior for a Flux in Reactor 3?

I tried searching for the answer but didn't find any clear answers, only definitions of Backpressure or that answer linked above for RxJava


回答1:


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, so it's natural that it respects the request from the downstream.

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.

2. 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?

3. 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



来源:https://stackoverflow.com/questions/57296097/are-subscribers-in-spring-reactor-unbounded-by-default

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!