Nacos源码1:Nacos服务注册基本原理

277 阅读3分钟

Nacos是我们常用的技术栈,在微服务架构中承担着服务注册发现、配置中心等重要功能。除了使用Nacos之外,我们还需要了解Nacos的基本原理。

我们本章主要熟悉Nacos的服务注册的基本流程。现在的后端项目工程一般都是SpringCloud或者SpringCloudAlibaba微服务架构,在这种架构方式下启动服务提供者的时候,会注册服务到NacosServer。

service注册基本流程:

注意:本章我们只熟悉基本的service注册基本流程,其他的非注册主流程例如:心跳处理、service推送、service订阅等等,我们暂时先忽略,后期我们在回头熟悉。每章的流程尽量简单明了,突出重心。

一、自动加载NacosAutoServiceRegistration这个bean

我们日常开发中,在服务提供者会添加下图中Nacos依赖。nacos依赖里面的spring.factories配置中定义了service注册自动配置类NacosServiceRegistryAutoConfiguration。

这个类在SpringBoot启动的时候会自动加载NacosAutoServiceRegistration这个bean

@Configuration
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
		AutoServiceRegistrationAutoConfiguration.class,
		NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {
   ...

   @Bean
   @ConditionalOnBean(AutoServiceRegistrationProperties.class)
   public NacosAutoServiceRegistration nacosAutoServiceRegistration(
			NacosServiceRegistry registry,
			AutoServiceRegistrationProperties autoServiceRegistrationProperties,
			NacosRegistration registration) {
		return new NacosAutoServiceRegistration(registry,
				autoServiceRegistrationProperties, registration);
   }
}

二、监听web初始化事件

NacosAutoServiceRegistration得到继承关系图如下。

service注册类实现事件监听器接口

我们看到此类继承了抽象类AbstractAutoServiceRegistration,而AbstractAutoServiceRegistration实现了ApplicationListener事件监听器接口。

AbstractAutoServiceRegistration类中监听到web服务初始化完成事件,执行这里。

public abstract class AbstractAutoServiceRegistration<R extends Registration>
		implements AutoServiceRegistration, ApplicationContextAware,
		ApplicationListener<WebServerInitializedEvent> {
   // 监听到web服务初始化完成事件,执行这里
   public void onApplicationEvent(WebServerInitializedEvent event) {
		bind(event);
   }
   
   public void bind(WebServerInitializedEvent event) {
      ...
      this.start();
   }
   
   public void start() {
      ... 
      // 只要运行标记为false,则执行下面逻辑,防止重复执行下面的注册
      if (!this.running.get()) {
         ...
         // 注册逻辑
         register();
         // 把运行标记置为true
         this.running.compareAndSet(false, true);
      }
   }
}

执行register()方法具体的逻辑,由NacosAutoServiceRegistration执行。

这是一种模板方法设计模式,由抽象类定义通用模板流程,由具体子类负责实现子流程逻辑。

public class NacosAutoServiceRegistration
		extends AbstractAutoServiceRegistration<Registration> {
   protected void register() {
               // 进行一些配置判断
		if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
			log.debug("Registration disabled.");
			return;
		}
		if (this.registration.getPort() < 0) {
			this.registration.setPort(getPort().get());
		}
		super.register();
   }
}

回到抽象类AbstractAutoServiceRegistration来执行register方法。

protected void register() {
	this.serviceRegistry.register(getRegistration());
}

三、调用NacosServer的接口注册service实例

由NacosServiceRegistry 执行register方法。

public class NacosServiceRegistry implements ServiceRegistry<Registration> {
   public void register(Registration registration) {
      ...
      // 封装服务实例对象
      Instance instance = getNacosInstanceFromRegistration(registration);
      // 注册服务实例
      namingService.registerInstance(serviceId, group, instance);
   }
}

3.1 封装的服务实例对象

主要是服务提供者的ip、端口等信息。

封装一个服务实例对象

3.2 调用NacosServer接口注册service实例

调用NacosServer的注册接口/nacos/v1/ns/instance去注册服务实例。

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        final Map<String, String> params = new HashMap<String, String>(16);
        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", JacksonUtils.toJson(instance.getMetadata()));
        // 调用接口
        reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);

这个是传递的参数

四、NacosServer接口处理

@PostMapping
public String register(HttpServletRequest request) throws Exception {
   ...
   getInstanceOperator().registerInstance(namespaceId, serviceName, instance);
}

4.1 生成service对象

由namespaceId, serviceName, ephemeral等信息生成service对象。

@Override
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
   ...
   String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), ephemeral);
   ...
   // 生成service对象
   Service service = getService(namespaceId, serviceName, ephemeral);
   // 注册服务实例
   clientOperationService.registerInstance(service, instance, clientId);
}

registerInstance方法:

 public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
        ...
        Service singleton = ServiceManager.getInstance().getSingleton(service);
        ...
        InstancePublishInfo instanceInfo = getPublishInfo(instance);
        client.addServiceInstance(singleton, instanceInfo);

4.2 获取单例service

从单例缓存中获取service对象,没有则创建保存到map缓存并返回。

注意:为什么这里是获取单例的service对象???是为了保证相同namespaceId, serviceName只有一个service对象,这样在注册service实例的时候,才能保证service实例关联的是同一个service对象。所以必须保证service对象的唯一性。

public Service getSingleton(Service service) {
        Service result = singletonRepository.computeIfAbsent(service, key -> {
            ...
            return service;
        });
// 这里的singleton其实是上面的service对象 
client.addServiceInstance(singleton, instanceInfo);

4.3 缓存service实例到map

public boolean addServiceInstance(Service service, InstancePublishInfo instancePublishInfo) {
   ...
   publishers.put(service, instancePublishInfo)
}

把service对象->instance实例的映射关系保存到ConcurrentHashMap中。publishers实际上是ConcurrentHashMap