Until now as everybody knows, microservice architecture represents a collection of multiple services. Each service contains it’s own business logic in comparison to the monolithic architecture which contains everything in one place. This means we have to maintain multiple services usually at the same time.
The microservices communicate with each other in order to fulfil their needs. As usually, when you need it the most, an instance of one of the microservices can go down or have delay in response, what we usually call unreachable service. The chances of failure need to be taken into consideration and to be handled in an appropriate way.
Why taking care of latency is important in the microservice architecture?
Increased latency one can face is when one of the microservices is:
- Reading/writing to database
- Synchronously calling another service
- Hitting the timeout of asynchronous communication
If we consider the following scenario:
We have 5 microservices that communicate with each other. If microservice 5 goes down, all the other services that depend on it can be affected.
In this type of scenario, the solution for this is the strategy of fault-tolerance.
Circuit breaker
A circuit breaker is a pattern that can help in achieving fault tolerance. The circuit breaker detects when external service fails and in that case, the circuit breaker is open. All the incoming requests to the unhealthy service will be rejected and errors will be returned instead of trying to reach out to the unhealthy service over and over again. For this, we can use Hystrix.
What is Hystrix?
So, how does Hystrix actually work?
Let’s take again the architecture from above. So suppose that there are multiple user requests from microservice One that are requiring a piece of information from microservice Five. In this situation, the possibility of microservice One being blocked is very obvious since it might wait for responses from microservice Five. Also microservice Five can be overloaded with the requests so the outcome would be blocking the whole service. This is when Hystrix kicks in and helps avoiding the problem.
The external requests to the service of microservice Five are wrapped in HystrixCommand which defines the behaviour of the requests. The behaviour is defined as the available number of threads that can handle the requests. In our example, the service in microservice Five can be defined as ten available threads for handling external requests. By wrapping the service in HystrixCommand, we are limiting the number of requests which service is supposed to get.
By default, Hystrix uses ten threads. If there are more concurrent threads than the default value, the rest of the requests are rejected – redirected to the fallback method.
Using Hystrix in Spring boot
First thing, adding the dependency in pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Add the @HystrixCommand annotation to the main class
@SpringBootApplication
@EnableHystrix
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class, args);
}
}
The next step is to define the fallback method for HystrixCommand:
@Service
@Slf4j
public class HystrixService {
@HystrixCommand(fallbackMethod="fallbackHystrix",
commandProperties = {@HystrixProperty(name =
"execution.isolation.thread.timeoutInMilliseconds", value = "2000")})
public String testHystrix(String message) throws InterruptedException {
Thread.sleep(4000);
return message != null ? message : "Message is null";
}
public String fallbackHystrix(String message) {
log.error("Request took to long. Timeout limit: 2000ms.");
return "Request took to long. Timeout limit: 2000ms. Message: " + message;
}
}
This is just a simple example of how the library can be used.
In order to improvise a timeout, Thread.sleep(4000) is set in milliseconds and the timeout for response is set to 2000 milliseconds as a HystrixProperty in HystrixCommand annotation, after which the call should end up in the fallback method.
Now we can test the implementation by executing the following request:
http://localhost:8080/hystrix-example?message=hello
If we want to change the default thread pool size of HystrixCommand, we can add the following thread pool properties:
@Service
@Slf4j
public class HystrixService {
@HystrixCommand(fallbackMethod = "fallbackHystrix",
commandProperties = {@HystrixProperty(name =
"execution.isolation.thread.timeoutInMilliseconds", value = "2000")},
threadPoolProperties = {@HystrixProperty(name = "coreSize", value = "3")})
public String testHystrix(String message) throws InterruptedException {
return message != null ? message : "Message is null";
}
public String fallbackHystrix(String message) {
log.error("Request took to long. Timeout limit: 2000ms.");
return "Request took to long. Timeout limit: 2000ms. Message: " + message;
}
}
The fallback method is called when some fault occurs.
An important thing to notice here is that the signature of the fallback method should be the same as the method on which HystrixCommand annotation is defined.
The working example of the above exercise can be found here hystrix-example
Summary
Moving away from monolithic architecture to microservices is usually coming with quite some challenges.
In this blog post, we took a look at one of them, but we just scratched the surface.
As more challenges are coming in the pipeline, stay tuned and hope to see you in one of the next posts.