Location>code7788 >text

An in-depth look at synchronous communication mechanisms in microservices architecture

Popularity:625 ℃/2024-08-09 09:40:25

Microservices architecture is a design approach that divides an application into a set of small services, each running in a separate process, often organized according to business capabilities. These services interact with each other through a variety of communication methods to realize the functionality of the entire application. Today we focus on synchronous communication, about asynchronous communication and message queuing (MQ) and other content will be explained later.

By communication here, we mean inter-service communication that we do inside the client, not by calling an external web service for access. Okay, let's get started.

load balancing

server-side load balancing

Before we dive into client-side load balancing, we should first have a more in-depth understanding of server-side load balancing, such as the nginx component. Now let's take a closer look:

image

nginx is typically used to load forward requests within our servers, while clients are load sharing within individual services.

Client load balancing

In Spring Cloud, for example when using Ribbon, the client maintains a list of server addresses and selects a server to access through a load balancing algorithm before sending a request. This approach is called client-side load balancing because it does the work of assigning load balancing algorithms within the client.

image

synchronous communication

In general, we often use a variety of tools to make HTTP requests. I believe that we may often wrap their own httputils class, or use other official tools. For example, today we will explain the RestTemplate toolkit. It is actually a tool used to send HTTP requests, but it does some extra work on top of that. Let's take a look at its basic usage.

RestTemplate

RestTemplate is a powerful class provided by the Spring Framework , specifically for HTTP access to the synchronization client . It is designed to simplify the complex process of making REST calls using HTTP clients and provides a rich set of methods and functions to handle various HTTP requests and responses.

Creating a RestTemplate instance

First, you need to create an instance of RestTemplate. This can be done either by direct instantiation or by using Spring's auto-assembly.

import ;

RestTemplate restTemplate = new RestTemplate();

Send Request

Use RestTemplate to send a GET request and get the response body.

String url = "/api/resource";
String result = (url, );
(result);

Sends a POST request, usually containing the request body.

String url = "/api/resource";
Map<String, Object> requestMap = new HashMap<>();
("key1", "value1");
("key2", "value2");

HttpHeaders headers = new HttpHeaders();
(MediaType.APPLICATION_JSON);

HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestMap, headers);
String response = (url, entity, );
(response);

Seeing this, you might be thinking, what does this have to do with communication between microservices? Is it more than a simple HTTP call? Let's dig deeper.

LoadBalanced annotation

Registries are often involved in microservice architectures. Today we will focus on communication between microservices without going into depth on registries, such as how Nacos manages the registration of all microservices and how one service node discovers other service nodes. Assuming that we have the IP addresses of the other service nodes, you might be tempted to just replace the domain names in the above example with IP addresses, but when dealing with multi-node microservices that are guaranteed to be highly available, it would be catastrophic to just write the IP addresses into the code. All IP addresses should be managed and selected by a unified component.

Therefore, Ribbon was born. In general, if a Nacos dependency is integrated in a project's pom file, the Ribbon component is usually included by default, so there is no need to configure the pom file separately to introduce the Ribbon dependency.

basic usage

As mentioned earlier, one way is to instantiate the object directly and the other is to manage and inject it through the Spring container.

@Configuration
public class RestConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

This makes it easy to perform service call operations when they are needed.

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping(value = "/findOrderByUserId/{id}")
    public R findOrderByUserId(@PathVariable("id") Integer id) {
        String url = "http://mall-order/order/findOrderByUserId/"+id;
        R result = (url,);
        return result;
    }
}

Note that mall-order is not the domain name of the website we're talking about, but the name of the service we've configured in a registry such as nacos.

After adding the LoadBalanced annotation, RestTemplate will automatically inject the required dependencies, which can be implemented by looking at the source code.

source code analysis

About Spring's autoconfiguration, I've mentioned it many times before when explaining Spring, so I won't expand on it in detail here. We can see that it implements the SmartInitializingSingleton interface, so to use the load balancing feature, you must wait until all the beans are loaded.

image

Okay, we can see here that he added an interceptor to the RestTemplate class. Next, we can explore exactly what this interceptor does. I chose not to show the whole thing step-by-step because it's not necessary. First of all, it's not memorable, and secondly, just like with business, we're only concerned with which data table we end up going to. We only need to remember the intersection he must have passed through, which takes the burden off the brain.

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) throws IOException {
        final URI originalUri = ();
        String serviceName = ();
        (serviceName != null,
                "Request URI does not contain a valid hostname: " + originalUri);
        return (serviceName,
                (request, body, execution));
    }

In fact, it doesn't take much guessing to see this. Once the serviceName is obtained, which is the name of the microservice we defined earlier, it will be replaced with the real IP address in the execute method, and then the HTTP request will eventually be called to complete the process. Let's take a closer look at the source code.

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
            throws IOException {
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        Server server = getServer(loadBalancer, hint);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        }
        RibbonServer ribbonServer = new RibbonServer(serviceId, server,
                isSecure(server, serviceId),
                serverIntrospector(serviceId).getMetadata(server));

        return execute(serviceId, ribbonServer, request);
    }

All the load balancing rules are implemented in the getServer method, which we won't trace in depth. At this step, we have found the microservice to call. If you still have questions, we can look at the specific implementation of the Server class and it will make sense.

image

In the end, it's just a matter of leaving it to the HTTP call. However, this alone is not enough. Wouldn't you want to write this extraneous service call code in every service? It would be very cumbersome and not only add complexity to the business logic, but also inconvenient. Fortunately, this is made much easier with Spring Cloud OpenFeign, which was created to solve this inter-service communication problem by encapsulating these tedious details.

However, note that this encapsulation does not affect the substance of the communication. Next time we will discuss in detail the differences and usage of Spring Cloud OpenFeign and Dubbo invocation components.

summarize

Today we focus on network communication between microservices. It is clear to see that the ultimate goal of frameworks is to enable programmers to focus more on business logic rather than being forced to write all sorts of extraneous code. To summarize, despite our use of frameworks and various abstractions, we still end up making calls over HTTP. The difference is that we introduced an interceptor to load balance the microservice before the actual call. Various balancing algorithms are implemented in this interceptor that ultimately determines the real IP address and port in order to make the access and get the required data.


I'm Rain, a Java server-side coder, studying the mysteries of AI technology. I love technical communication and sharing, and I'm passionate about the open source community. I am also an excellent author of Nuggets, a content co-creator of Tencent Cloud, an expert blogger of Ali Cloud, and an expert of Huawei Cloud.

💡 I won't be shy about sharing my personal explorations and experiences on the path of technology, in the hope that I can bring some inspiration and help to your learning and growth.

🌟 Welcome to the effortless drizzle! 🌟

🚀 Currently, my exploration is focused on AI Ag