zoukankan      html  css  js  c++  java
  • @EnableDiscoveryClient 注解如何实现服务注册与发现

    @EnableDiscoveryClient 是如何实现服务注册的?
    我们首先需要了解 Spring-Cloud-Commons 这个模块,Spring-Cloud-Commons 是 Spring-Cloud 官方提供的一套抽象层,类似于 JDBC 一样,提供了一套规范,具体的实现有实现厂商去根据标准实现,在Finchley版中, Spring-Cloud-Commons 共提供了6个模块标准规范。

    actuator
    circuitbreaker
    discovery
    hypermedia
    loadbalancer
    serviceregistry
    在今天的文章中,我们一起来探讨学习一下 discovery、serviceregistry这两个模块,我们使用 alibaba 的 nacos-discovery 实现来进行学习。
    @EnableDiscoveryClient 注解做了什么事?

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(EnableDiscoveryClientImportSelector.class)
    public @interface EnableDiscoveryClient {
    
        /**
         * If true, the ServiceRegistry will automatically register the local server.
         */
        boolean autoRegister() default true;
    }

    从EnableDiscoveryClient源码可以看出该接口有一个autoRegister()方法默认返回值是true,它还做了一件非常重要的事,引用了EnableDiscoveryClientImportSelector类。为什么说这个类非常重要呢?我们来看看就知道了

    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) {
                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;
        }
    
        @Override
        protected boolean isEnabled() {
            return getEnvironment().getProperty(
                    "spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
        }
    
        @Override
        protected boolean hasDefaultFactory() {
            return true;
        }
    
    }

    将焦点聚集到selectImports()方法上,该类获取了autoRegister 的值。

    当autoRegister=true 时,将AutoServiceRegistrationConfiguration类添加到自动装配中,系统就会去自动装配AutoServiceRegistrationConfiguration类,在具体的实现中自动装配类都是在这个AutoServiceRegistrationConfiguration类自动装配完成后才装配的,也就是说autoRegister=true就更够实现服务注册

    当autoRegister=false时,将spring.cloud.service-registry.auto-registration.enabled 设置成了 false,这样跟注册相关的类将不会自动装配,因为自动注册相关的类都有一个条件装配@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true),换句话说如果我们不想该服务注册到注册中心,只是想从注册中心拉取服务,我们只需要引导类上的注解改成@EnableDiscoveryClient(autoRegister = false)

    nacos 是如何根据标准去实现服务注册的?
    我们先看看在org.springframework.cloud.alibaba.nacos包下的NacosDiscoveryAutoConfiguration类

    NacosDiscoveryAutoConfiguration类做了些什么?

    @Configuration
    @EnableConfigurationProperties
    @ConditionalOnNacosDiscoveryEnabled
    @ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
    @AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
            AutoServiceRegistrationAutoConfiguration.class })
    public class NacosDiscoveryAutoConfiguration {
    
        @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);
        }
    }

    该类的自动装配是在AutoServiceRegistrationConfiguration之后完成,当autoRegister设置为false时,NacosDiscoveryAutoConfiguration就不会装配,也就意味着服务不会像注册中心进行注册。好了我们还是来看看NacosDiscoveryAutoConfiguration干了些啥吧,主要是装配了NacosServiceRegistry、NacosRegistration、NacosAutoServiceRegistration三个bean,来看看三个bean干了那些骚操作。

    NacosServiceRegistry类做了些什么?

    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();
    
            Instance instance = new Instance();
            instance.setIp(registration.getHost());
            instance.setPort(registration.getPort());
            instance.setWeight(nacosDiscoveryProperties.getWeight());
            instance.setClusterName(nacosDiscoveryProperties.getClusterName());
            instance.setMetadata(registration.getMetadata());
    
            try {
                namingService.registerInstance(serviceId, instance);
                log.info("nacos registry, {} {}:{} register finished", serviceId,
                        instance.getIp(), instance.getPort());
            }
            catch (Exception e) {
                log.error("nacos registry, {} register failed...{},", serviceId,
                        registration.toString(), e);
            }
        }
    
        @Override
        public void deregister(Registration registration) {
    
            log.info("De-registering from Nacos Server now...");
    
            if (StringUtils.isEmpty(registration.getServiceId())) {
                log.warn("No dom to de-register for nacos client...");
                return;
            }
    
            NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
            String serviceId = registration.getServiceId();
    
            try {
                namingService.deregisterInstance(serviceId, registration.getHost(),
                        registration.getPort(), nacosDiscoveryProperties.getClusterName());
            }
            catch (Exception e) {
                log.error("ERR_NACOS_DEREGISTER, de-register failed...{},",
                        registration.toString(), e);
            }
    
            log.info("De-registration finished.");
        }
    
        @Override
        public void close() {
    
        }
    
        @Override
        public void setStatus(Registration registration, String status) {
            // nacos doesn't support set status of a particular registration.
        }
    
        @Override
        public <T> T getStatus(Registration registration) {
            // nacos doesn't support query status of a particular registration.
            return null;
        }
    
    }

    该类实现了 spring-cloud-commons 提供的 ServiceRegistry 接口,重写了register、deregister两个方法,在register
    方法中主要是将配置文件装换成Instance实例,调用了namingService.registerInstance(serviceId, instance);方法,这个方法应该是服务端提供的服务注册方法。这一顿操作之后,服务就注册好了。

    NacosRegistration类做了些什么?

    public class NacosRegistration implements Registration, ServiceInstance {
    
        public static final String MANAGEMENT_PORT = "management.port";
        public static final String MANAGEMENT_CONTEXT_PATH = "management.context-path";
        public static final String MANAGEMENT_ADDRESS = "management.address";
        public static final String MANAGEMENT_ENDPOINT_BASE_PATH = "management.endpoints.web.base-path";
    
        private NacosDiscoveryProperties nacosDiscoveryProperties;
    
        private ApplicationContext context;
    
        public NacosRegistration(NacosDiscoveryProperties nacosDiscoveryProperties,
                ApplicationContext context) {
            this.nacosDiscoveryProperties = nacosDiscoveryProperties;
            this.context = context;
        }
    
        @PostConstruct
        public void init() {
    
            Map<String, String> metadata = nacosDiscoveryProperties.getMetadata();
            Environment env = context.getEnvironment();
    
            String endpointBasePath = env.getProperty(MANAGEMENT_ENDPOINT_BASE_PATH);
            if (!StringUtils.isEmpty(endpointBasePath)) {
                metadata.put(MANAGEMENT_ENDPOINT_BASE_PATH, endpointBasePath);
            }
    
            Integer managementPort = ManagementServerPortUtils.getPort(context);
            if (null != managementPort) {
                metadata.put(MANAGEMENT_PORT, managementPort.toString());
                String contextPath = env
                        .getProperty("management.server.servlet.context-path");
                String address = env.getProperty("management.server.address");
                if (!StringUtils.isEmpty(contextPath)) {
                    metadata.put(MANAGEMENT_CONTEXT_PATH, contextPath);
                }
                if (!StringUtils.isEmpty(address)) {
                    metadata.put(MANAGEMENT_ADDRESS, address);
                }
            }
        }
    
        @Override
        public String getServiceId() {
            return nacosDiscoveryProperties.getService();
        }
    
        @Override
        public String getHost() {
            return nacosDiscoveryProperties.getIp();
        }
    
        @Override
        public int getPort() {
            return nacosDiscoveryProperties.getPort();
        }
    
        public void setPort(int port) {
            this.nacosDiscoveryProperties.setPort(port);
        }
    
        @Override
        public boolean isSecure() {
            return nacosDiscoveryProperties.isSecure();
        }
    
        @Override
        public URI getUri() {
            return DefaultServiceInstance.getUri(this);
        }
    
        @Override
        public Map<String, String> getMetadata() {
            return nacosDiscoveryProperties.getMetadata();
        }
    
        public boolean isRegisterEnabled() {
            return nacosDiscoveryProperties.isRegisterEnabled();
        }
    
        public String getCluster() {
            return nacosDiscoveryProperties.getClusterName();
        }
    
        public float getRegisterWeight() {
            return nacosDiscoveryProperties.getWeight();
        }
    
        public NacosDiscoveryProperties getNacosDiscoveryProperties() {
            return nacosDiscoveryProperties;
        }
    
        public NamingService getNacosNamingService() {
            return nacosDiscoveryProperties.namingServiceInstance();
        }
    
        @Override
        public String toString() {
            return "NacosRegistration{" + "nacosDiscoveryProperties="
                    + nacosDiscoveryProperties + '}';
        }
    }

    该类主要是装配了一些management管理类的配置信息

    NacosAutoServiceRegistration类做了些什么事情?

    public class NacosAutoServiceRegistration
            extends AbstractAutoServiceRegistration<Registration> {
        private static final Logger log = LoggerFactory
                .getLogger(NacosAutoServiceRegistration.class);
    
        private NacosRegistration registration;
    
        public NacosAutoServiceRegistration(ServiceRegistry<Registration> serviceRegistry,
                AutoServiceRegistrationProperties autoServiceRegistrationProperties,
                NacosRegistration registration) {
            super(serviceRegistry, autoServiceRegistrationProperties);
            this.registration = registration;
        }
    
        @Deprecated
        public void setPort(int port) {
            getPort().set(port);
        }
    
        @Override
        protected NacosRegistration getRegistration() {
            if (this.registration.getPort() < 0 && this.getPort().get() > 0) {
                this.registration.setPort(this.getPort().get());
            }
            Assert.isTrue(this.registration.getPort() > 0, "service.port has not been set");
            return this.registration;
        }
    
        @Override
        protected NacosRegistration getManagementRegistration() {
            return null;
        }
    
        @Override
        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();
        }
    
        @Override
        protected void registerManagement() {
            if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
                return;
            }
            super.registerManagement();
    
        }
    
        @Override
        protected Object getConfiguration() {
            return this.registration.getNacosDiscoveryProperties();
        }
    
        @Override
        protected boolean isEnabled() {
            return this.registration.getNacosDiscoveryProperties().isRegisterEnabled();
        }
    
        @Override
        @SuppressWarnings("deprecation")
        protected String getAppName() {
            String appName = registration.getNacosDiscoveryProperties().getService();
            return StringUtils.isEmpty(appName) ? super.getAppName() : appName;
        }
    }

    这个类主要是调用NacosServiceRegistryregister()方法,我们来关注一下他的父类AbstractAutoServiceRegistrationstart(),这个才是启动方法。

        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()) {
                register();
                if (shouldRegisterManagement()) {
                    registerManagement();
                }
                this.context.publishEvent(
                        new InstanceRegisteredEvent<>(this, getConfiguration()));
                this.running.compareAndSet(false, true);
            }
    
        }
        ......省略
        protected void register() {
            this.serviceRegistry.register(getRegistration());
        }

    在start()方法里调用了NacosAutoServiceRegistration.register方法,NacosAutoServiceRegistration.register的方法里又调用了父类AbstractAutoServiceRegistration.register方法,在父类AbstractAutoServiceRegistration.register方法里调用了NacosServiceRegistry.register方法,实现了服务注册。

    服务注册大概经历了这么多,有兴趣的可以自己出看看源码,相信你肯定比我理解的更好。下面是个人学习 Nacos 服务注册的源码阅读流程图,其他的实现也差不多,主要是要理解 Spring-Cloud-Commons 的规范。

    @EnableDiscoveryClient执行流程.png

    @EnableDiscoveryClient 是如何实现服务发现?

    通过上面我们知道了 Spring-Cloud-Commons 模块实现了一套规范,我们直接去看在服务发现的规范是什么?我们能够找到DiscoveryClient接口。

    public interface DiscoveryClient {
    
        /**
         * A human readable description of the implementation, used in HealthIndicator
         * @return the description
         */
        String description();
    
        /**
         * Get all ServiceInstances associated with a particular serviceId
         * @param serviceId the serviceId to query
         * @return a List of ServiceInstance
         */
        List<ServiceInstance> getInstances(String serviceId);
    
        /**
         * @return all known service ids
         */
        List<String> getServices();
    
    }

    里面就提供了三个接口,我们接下来看看nacos是如何实现的?

    public class NacosDiscoveryClient implements DiscoveryClient {
    
        private static final Logger log = LoggerFactory.getLogger(NacosDiscoveryClient.class);
        public static final String DESCRIPTION = "Spring Cloud Nacos Discovery Client";
    
        private NacosDiscoveryProperties discoveryProperties;
    
        public NacosDiscoveryClient(NacosDiscoveryProperties discoveryProperties) {
            this.discoveryProperties = discoveryProperties;
        }
    
        @Override
        public String description() {
            return DESCRIPTION;
        }
    
        @Override
        public List<ServiceInstance> getInstances(String serviceId) {
            try {
                List<Instance> instances = discoveryProperties.namingServiceInstance()
                        .selectInstances(serviceId, true);
                return hostToServiceInstanceList(instances, serviceId);
            }
            catch (Exception e) {
                throw new RuntimeException(
                        "Can not get hosts from nacos server. serviceId: " + serviceId, e);
            }
        }
    
        private static ServiceInstance hostToServiceInstance(Instance instance,
                String serviceId) {
            NacosServiceInstance nacosServiceInstance = new NacosServiceInstance();
            nacosServiceInstance.setHost(instance.getIp());
            nacosServiceInstance.setPort(instance.getPort());
            nacosServiceInstance.setServiceId(serviceId);
    
            Map<String, String> metadata = new HashMap<>();
            metadata.put("nacos.instanceId", instance.getInstanceId());
            metadata.put("nacos.weight", instance.getWeight() + "");
            metadata.put("nacos.healthy", instance.isHealthy() + "");
            metadata.put("nacos.cluster", instance.getClusterName() + "");
            metadata.putAll(instance.getMetadata());
            nacosServiceInstance.setMetadata(metadata);
    
            if (metadata.containsKey("secure")) {
                boolean secure = Boolean.parseBoolean(metadata.get("secure"));
                nacosServiceInstance.setSecure(secure);
            }
            return nacosServiceInstance;
        }
    
        private static List<ServiceInstance> hostToServiceInstanceList(
                List<Instance> instances, String serviceId) {
            List<ServiceInstance> result = new ArrayList<>(instances.size());
            for (Instance instance : instances) {
                result.add(hostToServiceInstance(instance, serviceId));
            }
            return result;
        }
    
        @Override
        public List<String> getServices() {
    
            try {
                ListView<String> services = discoveryProperties.namingServiceInstance()
                        .getServicesOfServer(1, Integer.MAX_VALUE);
                return services.getData();
            }
            catch (Exception e) {
                log.error("get service name from nacos server fail,", e);
                return Collections.emptyList();
            }
        }
    }

    这里面的逻辑非常简单,就不过多赘述了,有兴趣的小伙伴,可以自行去研究喔。

    整个服务注册与发现差不多就是这样子,因为涉及的内容非常多,在很多地方小弟我确实看不懂,在上述中肯定有非常多的错误还请大神们多多指教。



  • 相关阅读:
    LeetCode 141. Linked List Cycle(判断链表是否有环)
    LeetCode 680. Valid Palindrome II(双指针)
    >/dev/null 2>&1
    18个最佳代码编辑器
    vi和vim常用命令
    搞定Windows连Linux三大件:SecureCRT,FileZilla,NX
    define和typedef的区别
    C++ const,static成员
    C++虚函数练习题
    c++虚函数解析
  • 原文地址:https://www.cnblogs.com/lm970585581/p/13066729.html
Copyright © 2011-2022 走看看