美文网首页spring cloud alibaba
nacos-服务注册发现源码分析-启动注册-02

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

作者: 愤怒的奶牛 | 来源:发表于2020-08-06 23:10 被阅读0次

    通过对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<EnableDiscoveryClient> {
    
        @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<String> 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<String, Object> 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 <R> registration meta data
     * @author Spencer Gibb
     * @since 1.2.0
     */
    public interface ServiceRegistry<R extends Registration> {
    
        /**
         * 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 <T> The type of the status.
         * @return The status of the registration.
         * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
         */
        <T> T getStatus(R registration);
    
    }
    

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

    • NacosServiceRegistry
    /**
     * @author xiaojing
     * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
     */
    public class NacosServiceRegistry implements ServiceRegistry<Registration> {
    
        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 <a href="mailto:mercyblitz@gmail.com">Mercy</a>
     */
    @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<R extends Registration>
            implements AutoServiceRegistration, ApplicationContextAware,
            ApplicationListener<WebServerInitializedEvent> { // 监听事件  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<R> 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<Registration> 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

        本文链接:https://www.haomeiwen.com/subject/rxharktx.html