Saturday, April 18, 2020

Reactive Architecture: Reactive Microservices

Microservices are a subset of Service Oriented Architecture(SOA). Major point of difference is that SOA doesn't put any constraint upon way of deployment. We can deploy SOA based application as a monolith or individually deploy SOA based services. Microervices strictly require services to be deployed independent of each other. Individual service can be deployed whenever it's needed and it can be deployed on as many number of machines as required.
Major advantages and features of microservices are:
  • Each service can be deployed independently
  • Services can evolve on technologically different path 
  • Each service has it's own database
  • Services can be scaled independently, as per requirement of each service
  • Isolated failures, thus higher availability, fault tolerance and resilient
  • Services communicate synchronously(request-response protocol like HTTP) or asynchronously(reactive microservices)
  • Services are loosely couple
  • Rapid deployments(upto continuous integration)
  • Feature releases as and when ready
  • Teams are more devops oriented

Principles of Isolation

When we talk about correct size of reactive microservices, a better way of putting it is to how to isolate microservices so that they are less coupled and more scalable. Reactive microservices must be tried to be isolated in terms of state, space, time and failure.

Isolation of State
This means that any access to microservice's data is possible only via it's public API. There must be no attempt to directly access microservice's database. This helps in ensuring that microservice can evolve internally without having any impact on other parts of the application. We can keep changing code implementation, data schemas etc as much as we want, but external world remains undisturbed as long as public API of the microservice remains same.

Isolation in Space
It is critical that microservices invoking each other are not impacted by fact that where they are deployed. They may be running on same hardware or different machines or different  data centres. This is called isolation of space. This ensures that we can easily scale up or down the microservices.

Isolation in Time
Reactive microservices communicate through asynchronous non-blocking messaging. This means that they don't wait for each other's response after invocation. This greatly helps in efficient usage of resources like threads, CPU cycles, memory etc., else these resources are kept blocked by request-response style of communication. This is called isolation in time. Also, reactive microservices must expect eventual consistency. This helps in achieving higher scalability. Strong/total consistency requires central coordination which limits scalability.

Isolation of Failure
Isolation of failure means that if a microservice fails then this failure must be restricted to it, and other dependent microservices must continue to function. This makes our system resilient and fault tolerant.

Isolation Techniques

There are several techniques which allow us to achieve above-mentioned levels of isolation.

Bulkheading
Bulkheading has its origin in shipping industry. In ships. we have different sections which are totally isolated from each other, and during accidents if one of the section is filled with water other sections are isolated from it and ship continues to work.
Similarly, in context of reactive architecture, we isolate our components so that failure in a component is limited to it and system continues to perform, even if a bit degraded. A properly bulkheaded system would ensure that failure in one microservice doesn't cascade into other microservices.

Circuit Breakers
Circuit breakers is a mechanism which helps to ensure that components which are already not performing as expected due to heavy stress are not put under more stress. If we keep calling a service which is already failing then we are putting it under more stress. Circuit breakers work in three states. Under normal circumstances, circuit breaker is in CLOSED state and it allows every call to the target service. If service starts failing, circuit breaker goes into OPEN state, meaning it doesn't allow any request to the service and we may return a custom response/exception. Circuit breaker would wait for a configured time period, and once that time period is over, it goes into a HALF-OPEN state and  allows a single request to the target service. If request succeeds, circuit breaker goes into CLOSED state again, else it goes back to OPEN state.

Asynchronous Messaging
In traditional request-response based communication, caller component keeps waiting fro response from called component. This blocks the costly resources like threads, CPU cycles etc. and hence hits the performance of the system. Also, if there is a failure in called service, calling service too fails.
Asynchronous messaging decouples components both in time and failure. Calling service just send a request to another service and keeps doing other stuff, thus totally decoupled in time. There is no blocking of threads, CPU cycles. Also, if a failure is there we can handle it in a much better and easy way.

Gateway Service
Though adding microservices to a system has lots of advantages, like high saclability, resilience, and more isolation in terms of failure. But it also adds more complexity to the system, particularly to the client. Now client may need to make multiple calls to the individual services to aggregate data. To solve this, we may introduce intermediate service between client and the microservices. Intermediate service is called gateway service. Client calls gateway service, which then calls microservices and aggregate the data returned, and send it back to client. We can even have one gateway service per microservice.

No comments:

Post a Comment