title | shortTitle | description | category | language | tag | ||||
---|---|---|---|---|---|---|---|---|---|
Backpressure Pattern in Java: Gracefully regulate producer-to-consumer data flow to prevent overload. |
Backpressure |
Dive into the Backpressure design pattern in Java through practical examples, discovering how it prevents overload while ensuring stability and peak performance by aligning data flow with consumer capacity. |
Concurrency |
en |
|
- Flow Control
- Rate Limiting Mechanism
Control the rate of data production so downstream consumers are not overwhelmed by excessive load.
Real-world example
Imagine a busy coffee shop where multiple baristas brew drinks (producers), and a single barista is responsible for carefully crafting latte art (consumer). If drinks are brewed faster than the latte-art barista can decorate them, they pile up, risking quality issues or discarded drinks. By introducing a pacing system—only sending new cups once the latte-art barista is ready—everyone stays synchronized, ensuring minimal waste and a consistently enjoyable customer experience.
In plain words
The Backpressure design pattern is a flow control mechanism that prevents overwhelming a system by regulating data production based on the consumer’s processing capacity.
Wikipedia says
Back pressure (or backpressure) is the term for a resistance to the desired flow of fluid through pipes. Obstructions or tight bends create backpressure via friction loss and pressure drop. In distributed systems in particular event-driven architecture, back pressure is a technique to regulate flow of data, ensuring that components do not become overwhelmed.
Sequence diagram
This example demonstrates how backpressure can be implemented using Project Reactor. We begin by creating a simple publisher that emits a stream of integers, introducing a small delay to mimic a slower production rate:
public class Publisher {
public static Flux<Integer> publish(int start, int count, int delay) {
return Flux.range(start, count).delayElements(Duration.ofMillis(delay)).log();
}
}
Next, we define a custom subscriber by extending Reactor’s BaseSubscriber. It simulates slow processing by sleeping for 500ms per item. Initially, the subscriber requests ten items; for every five items processed, it requests five more:
public class Subscriber extends BaseSubscriber<Integer> {
private static final Logger logger = LoggerFactory.getLogger(Subscriber.class);
@Override
protected void hookOnSubscribe(@NonNull Subscription subscription) {
logger.info("subscribe()");
request(10); //request 10 items initially
}
@Override
protected void hookOnNext(@NonNull Integer value) {
processItem();
logger.info("process({})", value);
if (value % 5 == 0) {
// request for the next 5 items after processing first 5
request(5);
}
}
@Override
protected void hookOnComplete() {
//completed processing.
}
private void processItem() {
try {
Thread.sleep(500); // simulate slow processing
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
}
Finally, in the main
method, we publish a range of integers and subscribe using the custom subscriber. A short sleep in the main thread allows the emission, backpressure requests, and processing to be fully observed:
public static void main(String[] args) throws InterruptedException {
Subscriber sub = new Subscriber();
Publisher.publish(1, 8, 200).subscribe(sub);
Thread.sleep(5000); //wait for execution
}
Below is an example of the program’s output. It shows the subscriber’s log entries, including when it requests more data and when each integer is processed:
23:09:55.746 [main] DEBUG reactor.util.Loggers -- Using Slf4j logging framework
23:09:55.762 [main] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onSubscribe(FluxConcatMapNoPrefetch.FluxConcatMapNoPrefetchSubscriber)
23:09:55.762 [main] INFO com.iluwatar.backpressure.Subscriber -- subscribe()
23:09:55.763 [main] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- request(10)
23:09:55.969 [parallel-1] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(1)
23:09:56.475 [parallel-1] INFO com.iluwatar.backpressure.Subscriber -- process(1)
23:09:56.680 [parallel-2] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(2)
23:09:57.185 [parallel-2] INFO com.iluwatar.backpressure.Subscriber -- process(2)
23:09:57.389 [parallel-3] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(3)
23:09:57.894 [parallel-3] INFO com.iluwatar.backpressure.Subscriber -- process(3)
23:09:58.099 [parallel-4] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(4)
23:09:58.599 [parallel-4] INFO com.iluwatar.backpressure.Subscriber -- process(4)
23:09:58.805 [parallel-5] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(5)
23:09:59.311 [parallel-5] INFO com.iluwatar.backpressure.Subscriber -- process(5)
23:09:59.311 [parallel-5] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- request(5)
23:09:59.516 [parallel-6] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(6)
23:10:00.018 [parallel-6] INFO com.iluwatar.backpressure.Subscriber -- process(6)
23:10:00.223 [parallel-7] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(7)
23:10:00.729 [parallel-7] INFO com.iluwatar.backpressure.Subscriber -- process(7)
23:10:00.930 [parallel-8] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(8)
23:10:01.436 [parallel-8] INFO com.iluwatar.backpressure.Subscriber -- process(8)
23:10:01.437 [parallel-8] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onComplete()
- Use in Java systems where data is produced at high velocity and consumers risk overload.
- Applicable in reactive or event-driven architectures to maintain stability under varying load conditions.
Benefits:
- Protects consumers from saturation and resource exhaustion.
Trade-offs:
- Introduces possible delays if production must slow down to match consumer capacity.
- Requires careful orchestration in complex systems with multiple concurrent data sources.
- Observer Pattern: Both patterns involve a producer notifying consumers. Observer is synchronous and tightly coupled (observers know the subject).
- Publish-Subscribe Pattern: Both patterns deal with asynchronous data flow and can work together to manage message distribution and consumption effectively.