An important aspect of reactive
approach to concurrent programming is non-blocking processing. This post compares blocking vs non-blocking processing in general terms to highlight reactive
idea in a nutshell.
Blocking Processing
Blocking (synchronous) processing has several characteristics:
Bound to the processing thread
Processing thread is waiting in case any I/O operation is performed
Under highload this approach has following consequences:
CPU & RAM resources are wasted, while thread is waiting to the I/O results.
If all threads are waiting, new user requests are either put to the queue or dropped down. This leads to poor user experience.
If all threads are waiting, service becomes unresponsive for API clients. This leads to timeouts and API clients failure. Basically, failure leads to more failure.
Non-Blocking Processing
Non-Blocking (aka reactive
) processing has several characteristics:
Not bound to specific processing thread
Threads are not waiting in case I/O operation is performed
Threads are reused between calls
Under highload this approach has following consequences:
High CPU & RAM utilization
Less threads are needed to serve same number of requests as in blocking case
However, non-blocking procesing comes with a cost:
Backend design is complicated, since the need to track origin and arrival of responses & errors. This require new design patterns to be employed (hopefully, wrapped into frameworks like RxJava and Project Reactor).
Frontend design is complicated, since responses will come asynchronously via websockets, server-sent events, etc.
Conclusion
In both cases response time is limited by I/O operations (filesystem, database, network) and response time of downstream services.
Threads used for non-blocking processing don’t wait for I/O operations to complete. This gives better resource utilization and increases throughput, compared to blocking processing.