Overview:
Modern application technology has become very complex. Everything is connected to the internet. Even a Pressure cooker / Light Bulbs have apps & can be controlled via internet nowadays!
Big monolithic applications are split into easily deployable, self-containing microservices. It has been a while since Microservices have become the trend! Microservices do have some disadvantages as well! When we have multiple services, One service (A) might send a request to another service (B) and wait for the response from the microservice (B) to proceed further. This is synchronous / blocking the thread from doing the actual work. We have to deal with a lot of network calls like this which is IO bound. One possible solution is to create a thread and send the request via the newly created thread. So that the main thread can do other tasks. Even though it sounds like a good solution, this is what all the old frameworks have been doing all these years. Threads consume memory. We can not create infinite number of threads in a machine. If the threads are going to wait for the response, it is wasting of resources. If we expect more number of users to use our application in future, then we need more hardware to support the user load. It does not sound like a scalable solution. Usually IO bound requests are slower and we need a better solution to solve our problem.
Thread-per-Request Programming:
Lets consider we would like to perform the below task.
- Execute a DB query based on the given input parameters
- Process the DB query result (say lowercase to uppercase)
- Write the processed result into a file.
Here steps 1 and 3 are I/O tasks and 2 is a computation task. Above 3 steps are synchronous blocking calls. That is step 2 can not be done when the step 1 is not completed and step 3 can not be done until step 2 is completed. Lets assume that the above DB query is a multi-table join query which will take some time to execute. Lets also assume that We could receive hundreds of concurrent requests to do the above task.
We normally encapsulate all the steps to be performed by creating an object. When we receive concurrent requests, in the traditional programming model, we create a thread and create an object for the request; and the thread executes the steps using the object. the thread will perform the steps. If we receive hundreds of requests, then we create hundreds of threads. The problem here is – threads consume memory! That is, Java has to allocate a procedure stack with considerable amount of memory to the thread for the execution. While each and every thread is performing the IO tasks, it has to wait for the step to be completed before proceeding with next steps. For ex: The thread has to wait for the DB server to execute the query and returns the result. As thread consumes memory and if it waits, then it is not practical and scalable. Then it sets a limit to number of threads we can create for the application.
Event-Driven Programming:
The event-driven programming is based on asynchronous procedure call. That is, we split the above 1 big synchronous task into 3 synchronous simple tasks (ie each step becomes a task). All these tasks are queued. All these tasks are read from a queue one by one and executed using dedicated worker threads from a thread-pool. When there are no tasks in queue, worker threads would simply wait for the tasks to arrive. Usually number of worker threads would be small enough to match the number of available processors. This way, You can have 10000 tasks in the queue and process them all efficiently – but we can not create 10000 threads in a single machine.
So the event-driven programming model is NOT going to increase the performance (up to certain limit) or overall the response time could be still same. But it can process more number of concurrent requests efficiently!
Reactive Programming:
Reactive programming is event-driven programming or special case of event-driven programming. Lets consider a simple example using Microsoft Excel. Lets take the first three cells A1, B1 and C1 in a spreadsheet. Lets assume that we have the formula for C1 which is = A1 + B1. Now whenever you enter / change the data in A1 or B1 cells, the C1 cell value gets updated immediately. You do not press any button to recalculate. It happens asynchronously.
Reactive programming is a declarative programming paradigm / an asynchronous programming style in which we use an event based model to push the data streams to the consumers / observers as and when the data is available / updated. It is completely asynchronous and non-blocking.
In reactive programming, threads are not blocked or waiting for a request to complete. Instead they are notified when the request is complete / the data changes. Till then they can do other tasks. This makes us to use less resources to serve more requests.
Reactive programming is based on Observer design pattern. It has following interfaces / components.
- Publisher / Observable
- Observer / Subscriber
- Subscription
- Processor / Operators
Publisher:
Publisher is observable whom we are interested in listening to! These are the data sources or streams.
Observer:
Observer subscribes to Observable/Publisher. Observer reacts to the data emitted by the Publisher. Publisher pushes the data to the Observers. Publishers are read-only whereas Observers are write-only.
Subscription:
Observer subscribes to Observable/Publisher via an object called Subscription. Publisher’s emitting rate might be higher than Observer’s processing rate. So in that case, Observer might send some feedback to the Publisher how it wants the data / what needs to be done when the publisher’s emitting rate is high via Subscription object.
For example:
- We keep the volume up or down based on the current volume when we listen to music.
- We slow down the speed of a talk in YouTube if the speaker is too fast.
Processor:
Processor acts as both Publisher and Observer. They stay in between a Publisher and an Observer. It consumes the messages from a publisher, manipulates it and sends the processed message to its subscribers. They can be used to chain multiple processors in between a Publisher or an Observer.
For example, in the above excel example, Lets assume that we are interested in calculating the square of the sum of cell values A1 and B1. Lets assume A1 is 3 and B1 is 2. We need to find the square of 3 + 2. So here C1 is = A1 + B1. D1 is = C1 * C1. So, whenever A1 / B1 value changes, C1 is updated which updates D1 as well. C1 here acts like a Processor.
Reactive Stream Implementation:
Reactive Stream is a specification which specifies the above 4 interfaces to standardize the programming libraries for Java. Some of the implementations are
We would be using Project Reactor in our examples going forward as this is what spring boot uses internally.
Reactive System Principles:
The following 4 principles are the building blocks of a reactive application as defined in the Reactive Manifesto.
- Event Driven
- Scalable
- Responsive
- Resilient
Event Driven:
Observers subscribe to Publisher. As and when a publisher emits data, the observer reacts to that event. Components of a reactive application communicate via non-blocking and asynchronous messages.
Scalable:
Lets assume that we have below services for an application. User is trying to order a product which involves all these microservices. User-service has to update the inventory in the product-service, have to make a call to the order-service and payment-service for processing. Lets assume that each call takes 5 seconds. In traditional approach, We have to make these calls sequentially. So overall order processing will take up to 15 seconds.
To save time, We can create 3 threads ourselves, make parallel calls. But It is wasting resources by creating additional threads by consuming CPU and Memory. In the reactive programming, we send messages to these services asynchronously without creating any additional threads ourselves which saves both time and resources. It makes our application easily scalable.
Responsive:
Reactive applications react to the events / messages quickly and provide overall positive user experience to the user. Here it is very difficult to define how quickly it should be! But if the user experience is same in both cases when the application is under less load and more load, then it can be defined as the system is responsive.
Resilient:
A reactive application must be responsive even in case of failures like timeout errors or system crash etc. The subscriber interface has nice set of methods. They not only handle just happy path scenarios, but also the errors. Subscribers are designed to handle the errors and they know how to react when something unexpected happens.
We already have talked about designing resilient microservices as part of below articles.
Schedulers:
Other than Observable and Observer, another key point in Reactive programming is Schedulers! Behind the scenes, asynchronous behavior is achieved using threads. Reactive libraries hide the complexity and provide rich set of APIs to manage threads for Observables and Observers. It has 2 important methods.
- subscribeOn – specifies the threads on which observable should operate or the thread pool to be used by the source to emit the data.
- publishOn – specifies the threads on which observers should operate. Any publishOn affects the thread pools used by the subsequent observers.
We will discuss its usage and behaviors in a separate article.
Publisher Types:
There are 2 types of Observables / Publishers
- Cold Publisher
- This is lazy
- Starts producing/emitting only when a subscriber subscribes to this publisher
- Publisher creates a data producer and generates new sets of values for each new subscription
- When there are multiple observers, each observer might get different values
- Example: Netflix. Movie will start streaming only if the subscriber wants to watch. Each subscriber can watch a movie any time from the beginning
- Hot Publisher
- Values are generated outside the publisher even when there are no observers.
- There will be only one data producer
- All the observers get the value from the single data producer irrespective of the time they started subscribing to the publisher. It means any new observer might not see the old value emitted by the publisher.
- Example: Radio Stream. Listeners will start listening to the song currently playing. It might not be from the beginning.
More details here.
Summary:
Main aim of this article is to provide a high level idea about reactive programming. We would be discussing how to implement that in a microservice in upcoming articles.
Happy learning 🙂
Hi Vinod,
Great article:) Thanks for your effort.
Learning a lot from your articles and understanding the wider concepts of different system architectures.
Happy coding:)
Thanks. I am glad that you liked it.