nacos-服务注册发现源码分析-启动注册-02

通过对nacos 作为注册中心的简单使用,我们观察到一个现象,就是服务启动后就会注册到nacos服务端。于是我们就有个疑问了,nacos 是怎么做到的,容器启动后就立刻注册服务上去呢?是通过什么方式或者机制完成注册的?熟悉spring 的都知道spring 有个事件机制,编程时我们可以通过监听容器启动从而做一些事情,nacos 启动注册是否也是基于事件机制呢?本片文章我们将解开上述问题的奥秘。

  • 问题抛出
  1. nacos 如何做到服务启动便注册服务的。
  • 寻找源码分析入口

笔者在看nacos 源码之前,阅读过eureka 的源码,知道一个注册中心的一些基本规范,所以在阅读nacos 的源码会比较轻松。如果你在这之前没有阅读任何有关注册中心的任何源码也没有关系,注册中心必须实现的两个基本功能:1.服务注册,2.服务发现。服务注册接口 一般都是 xxxxServiceRegistry。spring 的命名一般都很规范。

注意:spring cloud 体系都是基于springboot 的自动装配机制做的整合,如果对springboot的自动装配机制不熟悉的话,建议先学习springboot的自动装配机制。
在寻找分析入口之前我们先看一下代码:

@EnableDiscoveryClient // 服务注册
@SpringBootApplication
public class RoleServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(RoleServiceApplication.class, args);
    }

}

上面是我们开发时的代码,开启了服务注册,我们的入口就只有两个:@EnableDiscoveryClient,SpringApplication.run(RoleServiceApplication.class, args); 除了这个我们其他的啥也不知道,如果你知道springboot的自动装配机制的话,就会从@EnableDiscoveryClient 入手,不知道也没有关系,我们就依次看一下,SpringApplication.run(RoleServiceApplication.class, args); 这个里面你好像不会看到直接 和nacos 相关的东西,因为是启动spring 容器和tomcat 服务。那么我们就尝试着先看看@EnableDiscoveryClient

  • 入口 @EnableDiscoveryClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class) // 引入ImportSelector,自动装配机制中的一部分
public @interface EnableDiscoveryClient {

    /**
     * If true, the ServiceRegistry will automatically register the local server.
     * @return - {@code true} if you want to automatically register.
     */
    boolean autoRegister() default true;

}

上面引入了EnableDiscoveryClientImportSelector.class 自然我们就要看一看究竟。

  • EnableDiscoveryClientImportSelector
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
        extends SpringFactoryImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        String[] imports = super.selectImports(metadata); // 这里是关键

        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));

        boolean autoRegister = attributes.getBoolean("autoRegister");

        if (autoRegister) { // boolean autoRegister() default true;
            List importsList = new ArrayList<>(Arrays.asList(imports)); // 添加进去
            importsList.add(
                    "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
            imports = importsList.toArray(new String[0]); // 返回数组
        }
        else {
            Environment env = getEnvironment();
            if (ConfigurableEnvironment.class.isInstance(env)) {
                ConfigurableEnvironment configEnv = (ConfigurableEnvironment) env;
                LinkedHashMap map = new LinkedHashMap<>();
                map.put("spring.cloud.service-registry.auto-registration.enabled", false);
                MapPropertySource propertySource = new MapPropertySource(
                        "springCloudDiscoveryClient", map);
                configEnv.getPropertySources().addLast(propertySource);
            }

        }

        return imports;
    }
........省略部分

String[] imports = super.selectImports(metadata); 我们看到了这个,这个也是springboot自装配机制中的核心,这里不再解析,直接看加载的文件META-INF/spring.factories

  • META-INF/spring.factories (spring-cloud-commons-2.2.0.RELEASE.jar)
# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.client.CommonsClientAutoConfiguration,\
org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration,\
org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancerAutoConfiguration,\
org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\
org.springframework.cloud.commons.httpclient.HttpClientConfiguration,\
org.springframework.cloud.commons.util.UtilAutoConfiguration,\
org.springframework.cloud.configuration.CompatibilityVerifierAutoConfiguration,\
org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration

org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration 被自动装配了。ok 我们自然要进去看一下。

  • ServiceRegistryAutoConfiguration
@Configuration(proxyBeanMethods = false)
public class ServiceRegistryAutoConfiguration {

    @ConditionalOnBean(ServiceRegistry.class)
    @ConditionalOnClass(Endpoint.class)
    protected class ServiceRegistryEndpointConfiguration {

        @Autowired(required = false)
        private Registration registration;

        @Bean
        @ConditionalOnEnabledEndpoint
        public ServiceRegistryEndpoint serviceRegistryEndpoint(
                ServiceRegistry serviceRegistry) {
            ServiceRegistryEndpoint endpoint = new ServiceRegistryEndpoint(
                    serviceRegistry);
            endpoint.setRegistration(this.registration);
            return endpoint;
        }

    }

}

到这里我们只看到在这里 创建了一个 ServiceRegistryEndpoint 并且依赖 ServiceRegistry 其他的都看不出来了,到这里好像就断了,不知道是否要走下去。ok,自然如果我们继续往下看,就关注两个对象嘛:ServiceRegistry, ServiceRegistryEndpoint 这个两个对象好像都和服务注册有点关系,看名称也是知道的。既然ServiceRegistryEndpoint 依赖 ServiceRegistry 我们就从ServiceRegistry 开始。

  • ServiceRegistry
/**
 * Contract to register and deregister instances with a Service Registry.
 *
 * @param  registration meta data
 * @author Spencer Gibb
 * @since 1.2.0
 */
public interface ServiceRegistry {

    /**
     * Registers the registration. A registration typically has information about an
     * instance, such as its hostname and port.
     * @param registration registration meta data
     */
    void register(R registration);

    /**
     * Deregisters the registration.
     * @param registration registration meta data
     */
    void deregister(R registration);

    /**
     * Closes the ServiceRegistry. This is a lifecycle method.
     */
    void close();

    /**
     * Sets the status of the registration. The status values are determined by the
     * individual implementations.
     * @param registration The registration to update.
     * @param status The status to set.
     * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
     */
    void setStatus(R registration, String status);

    /**
     * Gets the status of a particular registration.
     * @param registration The registration to query.
     * @param  The type of the status.
     * @return The status of the registration.
     * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
     */
     T getStatus(R registration);

}

我们到这里就看到一个接口的定义,自然我们要关注他的实现类,我们发现就只有一个实现类:NacosServiceRegistry,自然要去看一看。

  • NacosServiceRegistry
/**
 * @author xiaojing
 * @author Mercy
 */
public class NacosServiceRegistry implements ServiceRegistry {

    private static final Logger log = LoggerFactory.getLogger(NacosServiceRegistry.class);

    private final NacosDiscoveryProperties nacosDiscoveryProperties;

    private final NamingService namingService;

    public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
        this.nacosDiscoveryProperties = nacosDiscoveryProperties;
        this.namingService = nacosDiscoveryProperties.namingServiceInstance();
    }

// 服务注册
    @Override
    public void register(Registration registration) {

        if (StringUtils.isEmpty(registration.getServiceId())) {
            log.warn("No service to register for nacos client...");
            return;
        }

        String serviceId = registration.getServiceId();
        String group = nacosDiscoveryProperties.getGroup();

        Instance instance = getNacosInstanceFromRegistration(registration); // 服务实例信息,如port,host等信息

        try {  
            // 在这里注册
            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);
        }
    }
// 服务实例信息,就是传递数据而已
private Instance getNacosInstanceFromRegistration(Registration registration) {
        Instance instance = new Instance();
        instance.setIp(registration.getHost());
        instance.setPort(registration.getPort());
        instance.setWeight(nacosDiscoveryProperties.getWeight());
        instance.setClusterName(nacosDiscoveryProperties.getClusterName());
        instance.setMetadata(registration.getMetadata());

        return instance;
    }
......省略部分

ok, 上面我们找到了服务注册的 逻辑,算是找到了服务注册的接口,到这里我们只成功了一半,因为我们只找到了服务注册的方法register(Registration registration) ,我们并没有找到在容器启动时,是如何调用到这个方法的。接下来我们就来寻找答案。如果对spring或者springboot启动源码非常熟悉的同学,能很快找到答案,那么我们不熟悉或者以前压根没有看过容器启动源码的咋搞呢?别忘了,我们知道容器肯定会调用register(Registration registration) ,搞个断点,debug 一下,在idea 中看一下方法的调用链就ok。

  • 方法调用链
调用链.PNG

ok.我们知道这个调用链以后,就可以开始分析springboot 在启动的时候时如何调用到register(Registration registration) 方法的。

  • NacosServiceRegistry 对象创建
    在分析调用过程之前,我们先解决一个问题,NacosServiceRegistry 对象是如何创建的?
    NacosServiceRegistryAutoConfiguration 找到这个配置对象
  • NacosServiceRegistryAutoConfiguration
/**
 * @author xiaojing
 * @author Mercy
 */
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
        matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
        AutoServiceRegistrationAutoConfiguration.class,
        NacosDiscoveryAutoConfiguration.class }) // 在这些对象后面创建
public class NacosServiceRegistryAutoConfiguration {

    @Bean // 创建对象
    public NacosServiceRegistry nacosServiceRegistry(
            NacosDiscoveryProperties nacosDiscoveryProperties) {
        return new NacosServiceRegistry(nacosDiscoveryProperties);
    }

ok 这里我们找到了NacosServiceRegistry 对象的创建。我们现在开始分析启动注册过程。

  • 根据上图的调用链可以看出来,容器启动后会到ServletWebServerApplicationContext.finishRefresh()
  • finishRefresh()
    @Override
    protected void finishRefresh() {
        super.finishRefresh();
        WebServer webServer = startWebServer();
        if (webServer != null) { // 发布一个启动完成的事件
            publishEvent(new ServletWebServerInitializedEvent(webServer, this)); // 基础至父类 `AbstractApplicationContext`
        }
    }
  • AbstractApplicationContext
@Override
    public void publishEvent(ApplicationEvent event) {
        publishEvent(event, null); // 调用下面这个方法
    }

/**
     * Publish the given event to all listeners.
     * @param event the event to publish (may be an {@link ApplicationEvent}
     * or a payload object to be turned into a {@link PayloadApplicationEvent})
     * @param eventType the resolved event type, if known
     * @since 4.2
     */
    protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");

        // Decorate event as an ApplicationEvent if necessary
        ApplicationEvent applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent) event;
        }
        else {
            applicationEvent = new PayloadApplicationEvent<>(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
            }
        }

        // Multicast right now if possible - or lazily once the multicaster is initialized
        if (this.earlyApplicationEvents != null) {
            this.earlyApplicationEvents.add(applicationEvent);
        }
        else {
// 把事件广播出去
            getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        }

        // Publish event via parent context as well...
        if (this.parent != null) {
            if (this.parent instanceof AbstractApplicationContext) {
                ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
            }
            else {
                this.parent.publishEvent(event);
            }
        }
    }
  • SimpleApplicationEventMulticaster#multicastEvent()方法
@Override
    public void multicastEvent(ApplicationEvent event) {
        multicastEvent(event, resolveDefaultEventType(event)); // 调用下面的方法
    }

@Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        Executor executor = getTaskExecutor();
        for (ApplicationListener listener : getApplicationListeners(event, type)) {
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

上面的方法都会调一个方法 invokeListener(listener, event); ok,我们就要去看一下里面是,从名称看就调用监听器,处理事件。

/**
     * Invoke the given listener with the given event.
     * @param listener the ApplicationListener to invoke
     * @param event the current event to propagate
     * @since 4.1
     */
    protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
        if (errorHandler != null) {
            try {
                doInvokeListener(listener, event); 
            }
            catch (Throwable err) {
                errorHandler.handleError(err);
            }
        }
        else {
            doInvokeListener(listener, event);
        }
    }

doInvokeListener(listener, event); 不管条件是否满足,都会调用doInvokeListener(listener, event);

@SuppressWarnings({"rawtypes", "unchecked"})
    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            listener.onApplicationEvent(event); // 处理事件
        }
        catch (ClassCastException ex) {
            String msg = ex.getMessage();
            if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
                // Possibly a lambda-defined listener which we could not resolve the generic event type for
                // -> let's suppress the exception and just log a debug message.
                Log logger = LogFactory.getLog(getClass());
                if (logger.isTraceEnabled()) {
                    logger.trace("Non-matching event type for listener: " + listener, ex);
                }
            }
            else {
                throw ex;
            }
        }
    }

回去看一些调用链 会到AbstractAutoServiceRegistration 的 onApplicationEvent()

  • AbstractAutoServiceRegistration
public abstract class AbstractAutoServiceRegistration
        implements AutoServiceRegistration, ApplicationContextAware,
        ApplicationListener { // 监听事件  WebServerInitializedEvent ,ServletWebServerInitializedEvent 是 WebServerInitializedEvent的子类
........
@Override
    @SuppressWarnings("deprecation") // 处理事件
    public void onApplicationEvent(WebServerInitializedEvent event) {
        bind(event);
    }

    @Deprecated
    public void bind(WebServerInitializedEvent event) {
        ApplicationContext context = event.getApplicationContext();
        if (context instanceof ConfigurableWebServerApplicationContext) {
            if ("management".equals(((ConfigurableWebServerApplicationContext) context)
                    .getServerNamespace())) {
                return;
            }
        }
        this.port.compareAndSet(0, event.getWebServer().getPort());
        this.start(); // 看这个方法
    }
}

    public void start() {
        if (!isEnabled()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Discovery Lifecycle disabled. Not starting");
            }
            return;
        }

        // only initialize if nonSecurePort is greater than 0 and it isn't already running
        // because of containerPortInitializer below
        if (!this.running.get()) {
            this.context.publishEvent(
                    new InstancePreRegisteredEvent(this, getRegistration()));
            register(); // 注册
            if (shouldRegisterManagement()) {
                registerManagement();
            }
            this.context.publishEvent(
                    new InstanceRegisteredEvent<>(this, getConfiguration()));
            this.running.compareAndSet(false, true);
        }

    }
  • register() 调用注册方法
private final ServiceRegistry serviceRegistry;
/**
     * Register the local service with the {@link ServiceRegistry}.
     */
    protected void register() {
        this.serviceRegistry.register(getRegistration());
    }

ok ,我们到这里就看到了,在容器启动后,发送事件,由WebServerInitializedEvent 事件监听者AbstractAutoServiceRegistration 处理事件,在onApplicationEvent 方法中注册服务。

  • 问题:serviceRegistry 何时初始化的?
    上面我们知道ServiceRegistry 对象的初始化,在NacosServiceRegistryAutoConfiguration d对象中也初始化了 NacosAutoServiceRegistration ,该对象继承AbstractAutoServiceRegistration
   @Bean
    public NacosServiceRegistry nacosServiceRegistry(
            NacosDiscoveryProperties nacosDiscoveryProperties) {
        return new NacosServiceRegistry(nacosDiscoveryProperties);
    }
    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public NacosRegistration nacosRegistration(
            NacosDiscoveryProperties nacosDiscoveryProperties,
            ApplicationContext context) {
        return new NacosRegistration(nacosDiscoveryProperties, context);
    }

    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public NacosAutoServiceRegistration nacosAutoServiceRegistration(
            NacosServiceRegistry registry,
            AutoServiceRegistrationProperties autoServiceRegistrationProperties,
            NacosRegistration registration) {
        return new NacosAutoServiceRegistration(registry,
                autoServiceRegistrationProperties, registration); // 初始化的时候传入 NacosServiceRegistry
    }
  • NacosAutoServiceRegistration
public NacosAutoServiceRegistration(ServiceRegistry serviceRegistry,
            AutoServiceRegistrationProperties autoServiceRegistrationProperties,
            NacosRegistration registration) {
        super(serviceRegistry, autoServiceRegistrationProperties); // 父级构造器
        this.registration = registration;
    }

ok,我们现在也解决了 NacosAutoServiceRegistration 实例化 serviceRegistry。现在我们回过头了看下面的代码:

/**
     * Register the local service with the {@link ServiceRegistry}.
     */
    protected void register() {
        this.serviceRegistry.register(getRegistration()); // serviceRegistry = > NacosServiceRegistry
    }
  • NacosServiceRegistry
@Override
    public void register(Registration registration) {

        if (StringUtils.isEmpty(registration.getServiceId())) {
            log.warn("No service to register for nacos client...");
            return;
        }

        String serviceId = registration.getServiceId();
        String group = nacosDiscoveryProperties.getGroup();

        Instance instance = getNacosInstanceFromRegistration(registration);

        try {
            namingService.registerInstance(serviceId, group, instance); // 调用 namingService 的注册方法。
            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);
        }
    }

ok,我们现在终于知道了 在spring 容器启动后,用事件的方式,通知到监听者,监听者的事件处理,实现启动注册服务的逻辑。后续,会继续分析namingService.registerInstance(serviceId, group, instance);

你可能感兴趣的:(nacos-服务注册发现源码分析-启动注册-02)