I was invited to answer a question about RxJava Backpressure mechanism on Zhihu before. Today I put it together in the hope that it can help more people.

The official documentation of RxJava describes the Backpressure mechanism systematically as follows:

Github.com/ReactiveX/R…

However, since the title of this article is to “vividly” describe each mechanism, naturally strive to express concise, so that people can understand at first glance. So, I’ll try to get rid of some abstract descriptions and use mostly metaphors to illustrate my understanding of these mechanisms.

First of all, from a larger perspective, although the title of the above document is called “Backpressure”, it is talking about a larger topic — “Flow Control”. Backpressure is just one of the solutions of Flow Control.

In RxJava, you can form a chain of calls to an Observable by calling multiple operators in succession, where data flows upstream and downstream. Flow Control is needed when upstream data is being sent faster than downstream data is being processed.

It’s like that elementary school math problem: a sink with an inlet pipe and an outlet pipe. If there is more water in the intake pipe, the pool will overflow after a while. This is what happens when you don’t have Flow Control.

What are the ideas for Flow Control? There are about four kinds:

  • (1) Backpressure.
  • (2) Throttling
  • (3) Packaging.
  • (4) Callstack blocking.

Details are given below.

Note: RxJava 1.x and 2.x version sequences coexist at present. 2.x has a great change in interface compared with 1.x, including the Backpressure part. However, the concepts of the Flow Control mechanism discussed here are applicable.

Several ideas of Flow Control

Backpressure

Backpressure, also known as Reactive Pull, means that the upstream sends as much as the downstream needs (specifically, through requests from the downstream). This is similar to traffic control in TCP, where the receiver controls the receive rate according to its own receive window, and controls the sender’s send rate through reverse ACK packets.

This scheme only works for so-called Cold Observables. A Cold Observable is a source that allows for slow speeds, such as sending a file from one machine to another, even down to a few bytes per second, if it takes long enough. The opposite example is live audio and video streaming, where the data rate drops below a certain value and the whole function fails (a Hot Observable).

Throttling

Throttling, “Throttling,” in plain English, means discarding. If you can’t consume it, you get rid of some of it and throw away the rest. In the case of live audio and video streaming, packets need to be discarded when the downstream can’t handle them.

There are different strategies for what to process and what to discard. There are three main strategies:

  • Sample (also called throttleLast)
  • throttleFirst
  • Debounce (also called throttleWithTimeout)

Explain each in detail.

Sample B. As an analogy for audio sampling, 8kHz audio is measured every 125 microseconds. Sample can be configured to, say, sample a value every 100 milliseconds, but in 100 milliseconds there may be a lot of values coming from upstream, so which one is going to be the last one. So it’s also called throttleLast.

ThrottleFirst is similar to sample, for example, sampling a value every 100 milliseconds, but selecting the first value within 100 milliseconds. ThrottleFirst can sometimes be used as an anti-jitter handle for click events in Android development because it can process the first click event (sampling the first value) for a specified period of time, but discard subsequent click events.

Debounce, also known as throttleWithTimeout, has an example in its name. For example, a network program maintains a TCP connection and sends and receives data continuously, but there is an interval when there is no data to send and receive. This interval is called idle time. When idle time exceeds a preset value, the connection may need to be disconnected, even if the idle time is time out. In fact, some server-side network programs work this way. After each packet is sent and received, a timer is started to wait for an Idle time. If packets are sent or received before the timer expires, the timer is reset and a new idle time is waiting. If the timer runs out, the connection can be closed. Debounce’s behavior, which is very similar to this one, can be used to find idle time timeout events after consecutive send and receive events. In other words, Debounce was able to find large intervals between successive events.

With packaging

Packaging is the small packages from upstream into large packages and distributed downstream. This reduces the number of packages that need to be processed downstream. Two such mechanisms are provided in RxJava: Buffer and Window.

Buffer and Window function basically the same, but the output format is different: the buffer package is represented by a List, while the Window package is an Observable.

Callstack blocking

This is a special case that blocks the entire Callstack. This is a special case because it only works if the entire call chain is executed synchronously on one thread, which requires that neither operator in the middle can start a new thread. In ordinary use, this should be relatively rare, because we often switch execution threads using subscribeOn or observeOn, and some complex operators themselves start new threads internally to handle it. On the other hand, if a fully synchronized chain of calls does occur, the other three Flow Control ideas above may still be applicable, but this way of blocking is simpler and does not require additional support.

Here’s an example of calling stack blocking compared to Backpressure. “Call stack congestion” is the equivalent of a lot of cars on a winding, one-lane road. Then the first car at the front of the line blocks the whole road, and the cars behind have to queue up behind. “Backpressure” is equivalent to the number of the window when the bank does business. The window actively calls a certain number (equivalent to a request), and the person goes to deal with it.

How do you make an Observable support Backpressure?

In RxJava 1.x, some Observables support Backpressure, while others don’t. However, an Observable that does not support Backpressure can be converted into an Observable that supports Backpressure through some operators. These operators include:

  • onBackpressureBuffer
  • onBackpressureDrop
  • onBackpressureLatest
  • OnBackpressureBlock (expired)

The observables they convert to have different Backpressure strategies.

In RxJava 2.x, an Observable no longer supports Backpressure, but Flowable supports Backpressure. The first three of the four operators mentioned above correspond to the three Backpressure policies of Flowable respectively:

  • BackpressureStrategy.BUFFER
  • BackpressureStrategy.DROP
  • BackpressureStrategy.LATEST

OnBackpressureBuffer is a processing method that does not discard data. Caches all received upstream and waits for requests to be sent downstream. It’s like a reservoir. But if you go upstream too fast, the buffer overflows.

OnBackpressureDrop and onBackpressureLatest are similar in that they both discard data. These two strategies are equivalent to a token mechanism (or quota mechanism), in which the downstream generates tokens (quotas) to the upstream through request, and sends as much data as the upstream receives. When the token count reaches zero, upstream starts to discard data. However, there is a subtle difference between the two strategies when the number of tokens is zero: onBackpressureDrop directly discards data and does not cache any data; OnBackpressureLatest, on the other hand, caches the latest data, so that when upstream receives a new token, it sends the last cached “latest” data to downstream first. It can be understood in combination with the following two pictures.

OnBackpressureBlock is to see if there is any demand from downstream and send it to downstream. If there is no demand from downstream, it does not discard it, but tries to block the upstream entrance (whether it can really block depends on the upstream situation) and does not cache itself. This strategy has fallen into disuse.


This paper focuses on describing and comparing various mechanisms of Flow Control mechanism and Backpressure in RxJava from a macro perspective. Many details are not involved. For example, buffers and Windows can package a fixed amount of data in addition to the amount of data received over a period of time. Another example is how onBackpressureDrop and onBackpressureLatest behave when they receive multiple requests for downstream data at once. You can refer to the corresponding API Reference to get the answer, also welcome to leave a comment with me to discuss.

(after)

Other selected articles:

  • The growth curve of technology
  • Asynchronous processing in Android and iOS development (iv) — Asynchronous tasks and queues
  • Authentic technology with wild way
  • The programmer’s cosmic timeline
  • On the turning point of life
  • How are collection types implemented in Redis?
  • Little White’s path to data advancement
  • Do you need to know about deep learning and neural networks?
  • Those antipatterns of programmers