@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 { @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) { ListimportsList = 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; LinkedHashMapmap = 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 { 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 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() { Mapmetadata = 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 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 { private static final Logger log = LoggerFactory .getLogger(NacosAutoServiceRegistration.class); private NacosRegistration registration; public NacosAutoServiceRegistration(ServiceRegistry 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; } }
这个类主要是调用NacosServiceRegistry的register()方法,我们来关注一下他的父类AbstractAutoServiceRegistration的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()) { 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 的规范。
通过上面我们知道了 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 getInstances(String serviceId); /** * @return all known service ids */ List 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 getInstances(String serviceId) { try { Listinstances = 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); Mapmetadata = 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 hostToServiceInstanceList( List instances, String serviceId) { Listresult = new ArrayList<>(instances.size()); for (Instance instance : instances) { result.add(hostToServiceInstance(instance, serviceId)); } return result; } @Override public List getServices() { try { ListViewservices = 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(); } } }
这里面的逻辑非常简单,就不过多赘述了,有兴趣的小伙伴,可以自行去研究喔。
整个服务注册与发现差不多就是这样子,因为涉及的内容非常多,在很多地方小弟我确实看不懂,在上述中肯定有非常多的错误还请大神们多多指教。