03 analysis of Nacos Client service discovery source code

Learning is not so utilitarian. The second senior brother will take you to easily read the source code from a higher dimension ~

This article takes you through the source code level to analyze the service discovery journey of Nacos Client. The fact may not be as simple as you think.

Nacos service discovery

Intuitively, the service discovery of Nacos client is to encapsulate parameters, call the server interface and obtain the returned instance list.

However, if you refine this process, you will find that it not only includes obtaining the service list through NamingService, but also involves communication protocol (Http or gRPC), subscription process, failover logic, etc. Let's review the relevant processes according to the service discovery.

Let's start with the entry program. You can still see it in NamingTest:

NamingService namingService = NacosFactory.createNamingService(properties);
namingService.registerInstance("nacos.test.1", instance);

ThreadUtils.sleep(5000L);
// Get instance list
List<Instance> list = namingService.getAllInstances("nacos.test.1");

The instantiation and basic functions of NamingService have been described during service registration. Here we can directly look at the method getAllInstances to obtain the instance list. The parameter of this method is the name of the service.

After some columns of overloaded method calls, the real methods to process the core logic are as follows:

@Override
public List<Instance> getAllInstances(String serviceName, String groupName, List<String> clusters,
        boolean subscribe) throws NacosException {
    ServiceInfo serviceInfo;
    String clusterString = StringUtils.join(clusters, ",");
    // Subscribe mode
    if (subscribe) {
        // Get the service information from the client cache first
        serviceInfo = serviceInfoHolder.getServiceInfo(serviceName, groupName, clusterString);
        if (null == serviceInfo) {
            // If the service information does not exist in the local cache, subscribe
            serviceInfo = clientProxy.subscribe(serviceName, groupName, clusterString);
        }
    } else {
        // If you do not subscribe to the service information, you can query it directly from the server
        serviceInfo = clientProxy.queryInstancesOfService(serviceName, groupName, clusterString, 0, false);
    }
    // Get the instance list from the service information
    List<Instance> list;
    if (serviceInfo == null || CollectionUtils.isEmpty(list = serviceInfo.getHosts())) {
        return new ArrayList<Instance>();
    }
    return list;
}

First, look at the overloaded getAllInstances method, which has several more parameters than the entry method. There are not only the service name, but also the group name, clusters and subscribe.

Other parameters in the overloaded method have been set to default values. For example, the default group name is "DEFAULT_GROUP", the default cluster list is an empty array, and whether to subscribe is "subscribe" by default.

The above methods are sorted into the flow chart as follows:

The basic logic of the above process is:

In the subscription mode, the service information (ServiceInfo) is directly obtained from the local cache, and then the instance list is obtained, because the subscription mechanism will automatically synchronize the changes of the server instance to the local cache. If there is no in the local cache, it indicates that it is the first call. The subscription will be made and the service information will be obtained after the subscription is completed.

If it is a non subscription mode, you can directly request the server to obtain service information.

Subscription processing flow

In the above process, the subscription logic is involved. The entry code is the following method to obtain the instance list:

serviceInfo = clientProxy.subscribe(serviceName, groupName, clusterString);

Let's take a look at the internal processing of this method. First, the clientProxy here is the object of the NamingClientProxy class. The corresponding subscribe implementation is as follows:

@Override
public ServiceInfo subscribe(String serviceName, String groupName, String clusters) throws NacosException {
    String serviceNameWithGroup = NamingUtils.getGroupedName(serviceName, groupName);
    String serviceKey = ServiceInfo.getKey(serviceNameWithGroup, clusters);
    // Get ServiceInfo in cache
    ServiceInfo result = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
    if (null == result) {
        // If it is null, the subscription logic is processed based on gRPC protocol
        result = grpcClientProxy.subscribe(serviceName, groupName, clusters);
    }
    // Schedule UpdateTask regularly
    serviceInfoUpdateService.scheduleUpdateIfAbsent(serviceName, groupName, clusters);
    // ServiceInfo local cache processing
    serviceInfoHolder.processServiceInfo(result);
    return result;
}

In the above code, you can see that the subscription logic is also expanded when obtaining the service instance list (especially for the first time). The basic flow chart is as follows:

As can be seen from the process above, the subscription method first judges the local cache through the proxy class. If there is ServiceInfo information in the local cache, it will return directly. If it does not exist, the gRPC protocol is used for subscription by default, and ServiceInfo is returned.

The subscribe subscription method of grpcClientProxy is to directly send a subscription request to the server and return the result, so there is no excessive processing.

After the subscription is completed, a scheduled task will be started through ServiceInfoUpdateService. The main function of this scheduled task is to regularly synchronize the instance list information on the server side and update the local cache.

The last step is ServiceInfo local cache processing. Here, the latest ServiceInfo obtained will be compared with the ServiceInfo in the local memory, updated, published, changed time, disk file storage and other operations. In fact, the operation of this step is also processed in the subscription timing task.

The subscription details and local cache processing involve a lot of content, which will be explained separately later. Just know the overall process here.

Summary

This paper mainly combs the core process of Nacos client service discovery, including:

First, if the subscription mode is not enabled, the service instance list information is obtained directly through the / instance/list interface (gRPC protocol by default);

Second, if the subscription mode is enabled (enabled by default), the instance information will be obtained from the local cache first. If it does not exist, subscribe and obtain the instance information;

Third, when the subscription is started, the scheduled task will be started and the UpdateTask will be executed regularly (obtaining server instance information, updating local cache and publishing events);

Fourth, after obtaining the latest instance information in the second step, the processServiceInfo method will also be executed to update the memory and local instance cache and publish the change time.

Fifth, so far, a loop is formed with step 2. Each time the local cache is obtained, it is updated if it does not exist

We will continue to explain the UpdateTask used to handle subscriptions and the ServiceInfoHolder#processServiceInfo method used to handle local caches in later articles.

About the blogger: the author of the technical book "inside of SpringBoot technology", loves to study technology and write technical dry goods articles.

Posted by gyash on Mon, 06 Dec 2021 23:35:54 -0800