Search
Close this search box.
Search
Close this search box.
Search
Close this search box.

Microservice Communication: Minimization, Monitoring and Fault-tolerance

Category:

Published:

June 2, 2022

As a software developer, were there any instances when a job required creating applications with several independent tasks that didn’t interact with each other?

That should be the case in an ideal world. But, in my experience, it is extremely rare. Sure, most applications have several tasks separate from the rest of the program. A nightly job that imports data or an online store’s product catalogue are examples of this. However, some tasks are more complex and cannot be easily implemented in a standalone service that does not have any kind of interaction with other parts of the system.

In these situations, your microservices must communicate with one another. But that appears to be a lot easier than it is. You can’t avoid building a distributed system as soon as your services need to interact with one another. In this article, we’ll explain what microservice communication is, then go over some of the common issues it causes and how to avoid them.

What is Microservice Communication?

In monolithic applications, we say that communications are inter-process communications. As a result, communication works with a single process that calls another using method calls. This communication is very straightforward, but the components are tightly coupled and difficult to separate and scale independently.

However, changing the communication mechanism is one of the most challenging aspects of moving to a microservices-based application. Because microservices are distributed, each service has its own instance and process. As a result, services must communicate using inter-service communication protocols such as HTTP, gRPC, or AMQP for message brokers.

We should be cautious when considering communication types and managing them during the design process because microservices are a complex structure of independently developed and deployed services.

What are the two types of Microservice Communication?

Clients and services can communicate in various ways, each aimed at a different scenario and set of objectives. Those types of communications can be divided into two axes at first.

The first axis defines whether the Microservice protocol is synchronous or asynchronous.

Synchronous protocol

The HTTP protocol is an example of a synchronous protocol. The client submits a request to the service and waits for a response. This means that the client code may only continue its work after receiving the HTTP server response.

Asynchronous protocol

Asynchronous messages are used by other protocols such as AMQP (supported by various OS systems and cloud environments). In most cases, the client code or message sender does not wait for a response. Instead, it just delivers the message to a RabbitMQ queue or any other message broker as it would any other message broker.

The second axis of Microservice communication dictates whether the communication channel has single or multiple receivers.

  • Single receiver:

Each request must be handled by exactly one receiver or service in single receiver channels. The Command behavioural design pattern in OOP is an example of this communication.

  • Multiple receivers: 

One or more recipients can handle each request. Asynchronous communication is a requirement for this form of communication. An example of this is the publish/subscribe method, which is utilised in patterns like Event-driven architecture. Topics and subscriptions are typically used in a service bus or comparable technology, such as Azure Service Bus.

What are Common Problems in Microservice Communication?

When you build a monolith, you can usually ignore many of the challenges that come with distributed systems. Some of them include fault tolerance and monitoring.

Monitoring

Monitoring is a significant challenge in a distributed environment. As long as your system is deployed as a single large monolith, you only need to monitor one application, and all log files are stored in one location. However, these tasks become much more difficult in a distributed system.

You must now simultaneously monitor many services, some of which may employ distinct technologies. Choosing a decent technology becomes crucial when you need to analyse something in your log files, check the log files of several services, and track one user request through multiple systems.

For example, Retrace can help you overcome logging and monitoring issues regarding fault tolerance; this is not the case. These issues must be handled in the program design. The ideal way is to design your architecture to reduce dependencies between your microservices.

Fault Tolerance

When designing a distributed system, fault tolerance becomes essential.

In a monolith, either all parts of your application are available or not available. This is one of the many disadvantages of a monolith. When one part of your application fails, the entire application suffers. However, it also reduces the system’s complexity. It is impossible for one part of your application to be up and running while another is not.

You must anticipate this scenario when using a distributed microservices system. Independently deployed services are also prone to failure. As a result, you must implement your communication in a fault-tolerant manner so that one service’s downtime does not affect other services.

Implementing an Asynchronous REST Endpoint

For quite some time, Java EE has supported asynchronous REST calls. The JAX-RS specification defines it, and you can use it with any spec-compliant implementation. In addition, the JAX-RS specification makes creating an asynchronous REST endpoint extremely simple.

@Stateless
@Path("/books")
public class BookEndpoint {
	@Resource
	ManagedExecutorService exec;
	
	@GET
	@Path("/async")
	public void async(@Suspended AsyncResponse response) {
		response.setTimeout(5, TimeUnit.SECONDS);
		
		String firstThread = Thread.currentThread().getName();
		log.info("First thread: "+firstThread);
		
		exec.execute(new Runnable() {
			
			@Override
			public void run() {
				String secondThread = Thread.currentThread().getName();
				log.info("Second thread: "+secondThread);
				
				// do something useful ...
				
				// resume request and return result
				response.resume(Response.ok("Some result ...").build());
			}
		});
	}
}

You should do a few more things to implement an asynchronous REST endpoint, as you can see in the code snippet. To begin, set a timeout, after which the request will be cancelled, and the client will receive an HTTP 503 Service Unavailable response. You can do this by using the injected AsyncResponse object’s setTimeout method. In a separate thread, you should also use a ManagedExecutorService to execute the logic of your REST endpoint.

The only thing to do now is to add your business logic to the run method and call the REST endpoint asynchronously.

Implementing an Asynchronous REST Client

An asynchronous client call is straightforward to implement. It’s almost identical to a synchronous call in terms of implementation. The only difference is the async method on the Invocation.Builder must be called.

Client client = ClientBuilder.newBuilder().build();
WebTarget webTarget = client.target("http://localhost:8080/bookStore/rest/books/async");

Invocation.Builder request = webTarget.request();
AsyncInvoker asyncInvoker = request.async();
Future futureResp = asyncInvoker.get();

log.info("Do something while server process async request ...");

Response response = futureResp.get(); //blocks until client responds or times out

String responseBody = response.readEntity(String.class);
log.info("Received: "+responseBody);

The get method returns a Future object when called. It can be used to wait for a request to complete and retrieve the result. That’s all there is to creating an asynchronous REST call.

Wrapping Up

In this article, we looked at Microservice communication and how the system’s distributed nature adds a lot of technical complexity. We also looked at why someone should try to avoid any dependencies between the services and make them independent.

Building distributed networks adds complexity, so you must design your services, infrastructure, and services to handle the additional complexity. Monitoring tools such as Retrace can assist you in gathering the necessary information from all systems. As a result, by implementing communication asynchronously, you can reduce the performance impact of remote calls.


Interested in joining our Technology team at Dialectica? Visit our dedicate Technology Careers page to explore our Software Engineer, Cloud Engineer and more tech job vacancies. Apply today for our Senior Data Engineer, Business Intelligence (BI), Full Stack, Front-end Developer, Back-end Developer jobs in Athens and become a member of #teamDialectica.

Get access to unique insights and untapped expert knowledge!

Explore our services

Share