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

Time:2022-9-23

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

Nacos is currently 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 own protocol address with the Nacos server
  • The service consumer needs to query the address of the service provider from Nacos Server (according to the service name)
  • Nacos Server needs to perceive the change of service provider’s online and offline
  • Service consumers need to dynamically perceive the change of the 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.

The realization principle of Nacos:

Let’s first understand the implementation principle of Nacos registration center, which is illustrated by the following picture.

Nacos 注册中心的实现原理

The process in the figure is familiar to everyone. The difference is that in Nacos, when the service is registered, the service will be registered locally by polling the registration center cluster node address. On the registration center, that is, Nacos Server uses Map Save instance information. Of course, services configured with persistence will be saved in the database. On the caller of the service, in order to ensure the dynamic perception of the local service instance list, Nacos is different from other registries in that it uses Pull/Push to operate at the same time. The way. Through these, we have a certain understanding of the principle of the Nacos registry. We verify these theoretical knowledge from the source code level.

Source code analysis of Nacos

Combined with spring-cloud-alibaba +dubbo +nacos integration.

“Service registration process:”

In the process of publishing based on Dubbo service, automatic assembly is the event monitoring mechanism. In the DubboServiceRegistrationNonWebApplicationAutoConfiguration class, this class will listen to the ApplicationStartedEvent event, which is newly added by spring boot in 2.0, after the spring boot application is started. Will post this time. After listening to this event at this time, the registered action will be triggered.

Recommend a Spring Boot basic tutorial and practical example:
https://github.com/javastacks/spring-boot-best-practice

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

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

serviceRegistryis the interface implementation provided by spring-cloud (org.springframework.cloud.client.serviceregistry.ServiceRegistry), obviously the injected instance is: NacosServiceRegistry.

NacosServiceRegistry

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;
    }
    //corresponding to the application.name of the current application
    String serviceId = registration.getServiceId();
    //represents the grouping configuration on nacos
    String group = nacosDiscoveryProperties.getGroup();
    //Indicates service instance information
    Instance instance = getNacosInstanceFromRegistration(registration);

    try {
        // register with the 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 the instance, mainly to do two actions

  1. If the currently registered node is a temporary node, build the heartbeat information and build the heartbeat task 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 the 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 call the registration method of NamingProxy to register, the code logic is very simple, construct the request parameters, and initiate the request.

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 params = new HashMap(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 as mentioned above, the service will poll the address of the configured registry when registering:

public String reqAPI(String api, Map params, List 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()) {
            //Get a random server node
            Random random = new Random(System.currentTimeMillis());
            int index = random.nextInt(servers.size());
            // loop through the list of services
            for (int i = 0; i < servers.size(); i++) {
                String server = servers.get(index);//Get the service node of the index position
                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 made through callServer(api, params, server, method), and the call is made through the HttpURLConnection that comes with JSK. We can see the request parameters here by means of breakpoints:

HttpURLConnection 进行发起调用

During the period, there may be multiple GET requests to obtain the service list, which is normal. You will find a request like the above, which will callhttp://192.168.200.1:8848/nacos/v1/ns/instanceThis address. Then the next step is the processing flow of Nacos Server receiving the registration request from the server. You need to download the source code of Nacos Server. For the source code download, you can refer to the official documentation. This article will not elaborate too much.

“Nacos server processing:”

The server provides an InstanceController class, which provides APIs related to service registration. When the server initiates registration, the interface called is: [post]:/nacos/v1/ns/instance, serviceName: represents the project name of the client, 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);
        // Parse the instance 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 Nacos console service list is actually initializing a serviceMap, which is a ConcurrentHashMap collection
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());
        //From serviceMap, get a service object 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 the service object from serviceMap
        Service service = getService(namespaceId, serviceName);
        if (service == null) {//If it is 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 find the Map:

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

Through the annotation, we can know that Nacos maintains services through different namespaces, and each namespace has different groups, and different groups have corresponding services, and then use this serviceName to determine the service instance.

The first time you come in, it will enter initialization, and after initialization, putServiceAndInit will be called

private void putServiceAndInit(Service service) throws NacosException {
    putService(service);//Save the service information to the serviceMap collection
    service.init();//Establish a heartbeat detection mechanism
    //Realize data consistency monitoring, ephemeral (identifies whether the service is a temporary service, the default is persistent, that is, true)=true means using raft protocol, false means using Distro
    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 {
    // assemble key
    String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
    // Get the service just assembled
    Service service = getService(namespaceId, serviceName);

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

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

Then, send a successful registration response to the service registrar to end the service registration process. The above content, I hope everyone has a general understanding, collect it, and read it a few times later, keep it in mind, it will definitely be a bonus item in the interview.

Recommended recent hot articles:

1.1,000+ Java interview questions and answers (2021 latest version)

2.Don’t use the if/else screen full of screens, try the strategy mode, it’s delicious! !

3.What the hell! What is the new syntax for xx ≠ null in Java?

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

5.The latest release of “Java Development Manual (Songshan Edition)”, download quickly!

If you think it’s good, don’t forget to like + retweet!