At present, the hottest service registration center + configuration center, Ali open source, really fragrant!!

Keywords: Java

Source: cnblogs.com/wuzhenzhao/p/13625491.html

Nacos is a very popular middleware for service registration and discovery in China. Many companies are using Nacos, so the probability of being asked in the interview is also very high!

Capabilities required for Nacos service registration:

  • The service provider registers its protocol address with the Nacos server
  • The service consumer needs to query the address of the service provider (according to the service name) from the Nacos Server
  • Nacos Server needs to be aware of the changes of service providers' online and offline
  • Service consumers need to dynamically perceive the change of service address on the Nacos Server side

Most of the capabilities required as a registry are like this. What we need to do is to understand the unique characteristics of various registries and summarize their commonalities.

Implementation principle of Nacos:

Let's first understand the implementation principle of the Nacos registry, which is illustrated by the following figure.

The process in the figure is familiar to everyone. The difference is that in Nacos, when registering a service, the server will register the service locally by polling the cluster node address of the registration center. Map is used to save the instance information in the registration center, that is, on the Nacos Server. Of course, the service configured with persistence will be saved to the database, and the caller of the service, In order to ensure the dynamic perception of the local service instance list, unlike other registries, Nacos adopts the mode of Pull/Push operation at the same time. Through these, we have a certain understanding of the principle of the Nacos registry. We verify these theoretical knowledge from the source level.

Source code analysis of Nacos

Combined with the integration of spring cloud Alibaba + Dubbo + Nacos.

"Service registration process:"

In the process of publishing services based on Dubbo, automatic assembly is an event listening mechanism. In the DubboServiceRegistrationNonWebApplicationAutoConfiguration class, this class will listen for the ApplicationStartedEvent event. This event is added by spring boot in 2.0, that is, it will be published after the spring boot application is started. At this time, after listening to this event, the registration action will be triggered.

Recommend a basic Spring Boot tutorial and practical example:
https://github.com/javastacks...

@EventListener(ApplicationStartedEvent.class)
public void onApplicationStarted() {
    setServerPort();
    register();
}

private void register() {
    if (registered) {
        return;
    }
    serviceRegistry.register(registration);
    registered = true;
}

serviceRegistry is the interface implementation provided by spring cloud (org.springframework.cloud.client.serviceregistry.ServiceRegistry). Obviously, the injected instance is: Nacos serviceRegistry.

Then enter the registration method of the implementation class:

@Override
public void register(Registration registration) {

    if (StringUtils.isEmpty(registration.getServiceId())) {
        log.warn("No service to register for nacos client...");
        return;
    }
    //application.name corresponding to the current application
    String serviceId = registration.getServiceId();
    //Represents the grouping configuration on nacos
    String group = nacosDiscoveryProperties.getGroup();
    //Represents service instance information
    Instance instance = getNacosInstanceFromRegistration(registration);

    try {
        //Register via naming service
        namingService.registerInstance(serviceId, group, instance);
        log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
                instance.getIp(), instance.getPort());
    }
    catch (Exception e) {
        log.error("nacos registry, {} register failed...{},", serviceId,
                registration.toString(), e);
        // rethrow a RuntimeException if the registration is failed.
        // issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
        rethrowRuntimeException(e);
    }
}

The next step is to start registering instances, mainly doing two actions

  1. If the temporary node is currently registered, the heartbeat information is built, and the heartbeat task is built through the beat reactor
  2. Call registerService to initiate service registration
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        ////Whether it is a temporary node. If it is a temporary node, build heartbeat information
        if (instance.isEphemeral()) {
            BeatInfo beatInfo = new BeatInfo();
            beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
            beatInfo.setIp(instance.getIp());
            beatInfo.setPort(instance.getPort());
            beatInfo.setCluster(instance.getClusterName());
            beatInfo.setWeight(instance.getWeight());
            beatInfo.setMetadata(instance.getMetadata());
            beatInfo.setScheduled(false);

            //beatReactor, add heartbeat information for processing
        beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
        }
          //Call the service proxy class to register
          serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}

Then the registration method of NamingProxy is invoked, the code logic is very simple, the request parameters are set up and the request is initiated.

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {

    NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}",
        namespaceId, serviceName, instance);

    final Map<String, String> params = new HashMap<String, String>(8);
    params.put(CommonParams.NAMESPACE_ID, namespaceId);
    params.put(CommonParams.SERVICE_NAME, serviceName);
    params.put(CommonParams.GROUP_NAME, groupName);
    params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
    params.put("ip", instance.getIp());
    params.put("port", String.valueOf(instance.getPort()));
    params.put("weight", String.valueOf(instance.getWeight()));
    params.put("enable", String.valueOf(instance.isEnabled()));
    params.put("healthy", String.valueOf(instance.isHealthy()));
    params.put("ephemeral", String.valueOf(instance.isEphemeral()));
    params.put("metadata", JSON.toJSONString(instance.getMetadata()));

    reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);

}

Going down, we will find that the service will poll the address of the configured registry when registering:

public String reqAPI(String api, Map<String, String> params, List<String> servers, String method) {

        params.put(CommonParams.NAMESPACE_ID, getNamespaceId());

        if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(nacosDomain)) {
            throw new IllegalArgumentException("no server available");
        }

        Exception exception = new Exception();
        //If the service address is not empty
        if (servers != null && !servers.isEmpty()) {
            //Random acquisition of a server node
            Random random = new Random(System.currentTimeMillis());
            int index = random.nextInt(servers.size());
            // Traverse service list
            for (int i = 0; i < servers.size(); i++) {
                String server = servers.get(index);//Get the service node of the index location
                try {//Call the specified service
                    return callServer(api, params, server, method);
                } catch (NacosException e) {
                    exception = e;
                    NAMING_LOGGER.error("request {} failed.", server, e);
                } catch (Exception e) {
                    exception = e;
                    NAMING_LOGGER.error("request {} failed.", server, e);
                }
               //polling 
                index = (index + 1) % servers.size();
            }
       // ..........
}

Finally, the call is initiated through CallServer (API, parameters, server, method). Here, the call is initiated through the HttpURLConnection provided by JSK. We can see the request parameters here through breakpoints:

During this period, there may be multiple GET requests to obtain the service list. It is normal. You will find the above request and call http://192.168.200.1:8848/nac... This address. The next step is the processing flow of the registration request received by the Nacos Server from the server. You need to download the source code of Nacos Server. For source code download, please refer to the official documents. This article will not elaborate too much.

"Nacos server processing:"

The server provides an InstanceController class, which provides API s related to service registration. When the server initiates registration, the called interface is: [post]: /nacos/v1/ns/instance, serviceName: represents the project name of the client, and namespace: the namespace of Nacos.

@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {

        final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        final String namespaceId = WebUtils
                .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        // Resolve the instance from the request
        final Instance instance = parseInstance(request);

        serviceManager.registerInstance(namespaceId, serviceName, instance);
        return "ok";
}

Then call ServiceManager to register the service.

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        //Create an empty service. The service information displayed in the service list on the Nacos console is actually initializing a serviceMap, which is a concurrent HashMap collection
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());
        //Get a service object from serviceMap according to namespaceId and serviceName
        Service service = getService(namespaceId, serviceName);

        if (service == null) {
            throw new NacosException(NacosException.INVALID_PARAM,
                    "service not found, namespace: " + namespaceId + ", service: " + serviceName);
        }
        //Call addInstance to create a service instance
        addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}

When creating an empty service instance, we found the map storing the instance:

public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
            throws NacosException {
        //Get service object from serviceMap
        Service service = getService(namespaceId, serviceName);
        if (service == null) {//If empty. Then initialize
      Loggers.SRV\_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
      service = new Service();
      service.setName(serviceName);
      service.setNamespaceId(namespaceId);
      service.setGroupName(NamingUtils.getGroupName(serviceName));
      // now validate the service. if failed, exception will be thrown
      service.setLastModifiedMillis(System.currentTimeMillis());
      service.recalculateChecksum();
      if (cluster != null) {
          cluster.setService(service);
          service.getClusterMap().put(cluster.getName(), cluster);
      }
      service.validate();
      putServiceAndInit(service);
      if (!local) {
          addOrReplaceService(service);
      }
}

In the getService method, we found the Map:

/*
* Map(namespace, Map(group::serviceName, Service)).
*/
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

Through comments, we can know that Nacos maintains services through different namespaces, and each namespace has different groups, and there are corresponding services under different groups. Then, use this serviceName to determine the Service instance.

The first time you come in, you will enter initialization. After initialization, you will call putServiceAndInit

private void putServiceAndInit(Service service) throws NacosException {
    putService(service);//Save the service information to the serviceMap collection
    service.init();//Establish heartbeat detection mechanism
    //Realize data consistency monitoring. Ephemeral (identifies whether the service is temporary and persistent by default, that is, true)=true means raft protocol is adopted, and false means Distro protocol is adopted
    consistencyService
            .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
    consistencyService
            .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
    Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJson());
}

After obtaining the service, add the service instance to the collection, and then synchronize the data based on the consistency protocol. Then call addInstance.

public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
        throws NacosException {
    // Assembly key
    String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
    // Get the service just assembled
    Service service = getService(namespaceId, serviceName);

    synchronized (service) {
        List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);

        Instances instances = new Instances();
        instances.setInstanceList(instanceList);
        // That is, add a registration service to the class that implements listening in the previous step
        consistencyService.put(key, instances);
    }
}

Then send a successful registration response to the service registrar to end the service registration process. I hope you can have a general understanding of the above contents, collect them, read them more slowly later, and keep them in mind. There must be extra points in the interview.

Recent hot article recommendations:

1.1000 + Java interview questions and answers (2021 latest version)

2.Stop playing if/ else on the full screen. Try the strategy mode. It's really fragrant!!

3.what the fuck! What is the new syntax of xx ≠ null in Java?

4.Spring Boot 2.6 was officially released, a wave of new features..

5.Java development manual (Songshan version) is the latest release. Download it quickly!

Feel good, don't forget to like + forward!

Posted by Gondwana on Tue, 30 Nov 2021 03:48:37 -0800