Spring Cloud:服务注册与发现机制、源码分析

前言

  1. 什么是服务?
  2. 如何注册与发现?

        接触 Spring久 了,就会发现 Spring 最擅长的事情就是抽象和封装。所以我们听到最多的就是今天整合这个功能、明天整合那个中间件,把流行的好用的全部都整合进来。

        其实 Spring Cloud 现在已经是一块主板了,上面插满了各种组件,它用自己的“电源”和“总线”为大家“供电”和“传输数据”,保证整体的良好、平稳运行即可。

        下面来解说下抽象过程,其实很容易理解。假如有一个和用户相关的工程叫user-manager吧。把它运行起来,可以对外提供服务啦。但是任何东西如果只有一个的话,都存在单点问题。

        这很好解决,那就多运行几个呗。此时这个工程只有一个,就像是一个“类”(class),但它可以运行多份(IP和端口不同而已),就像是这个“类”new出来的多个实例(instance)。

        类运行起来后通常称为对象。那工程运行起来后叫什么呢?上面刚刚说过,工程其实就是个服务,所以工程运行起来就叫服务实例。同一个工程同时运行多份,就表示同一个服务同时存在多个服务实例。

        所以服务是一个静态的概念,服务实例是一个运行时的概念。因为只有运行起来后才能提供服务,否则代码再好,不让运行,毛用都没有。

        

一、Spring Cloud 的服务公共抽象

        在 SpringCloud,它只关注运行起来的服务,于是就有了服务实例的抽象,如图所示:

在这里插入图片描述
        接口名字就叫ServiceInstance。Host和Port就是IP和端口,ServiceId就是服务的标识,其实就是指的工程本身,InstanceId就是服务实例的标识。

        在不严格的情况下,可以把服务与服务实例当作是一回事儿,只要根据语境能分开就行。所以服务注册与发现里的服务就是服务实例。其实只需把服务实例注册上就可以啦,但是为了概念统一,Spring Cloud 还是抽象出了一个注册(Registration)

在这里插入图片描述
        可以看到它只是单纯的继承服务实例接口,只是一个标记接口,就是为了概念上的统一。

        上面这个接口表示的是被注册的内容,是名词语义的。还应该有一个表示注册动作的动词语义接口,是的,那就是 ServiceRegistry,如图所示:

在这里插入图片描述
        可以看出服务注册接口可以注册一个 Registration 或取消注册一个 Registration。

        整天讲的服务注册其实就是两个接口而已,使用 ServiceRegistry 接口来注册 Registration 接口。这就是 Spring Cloud 提供的服务注册的抽象,一般般吧,不过够用就行了。

        那么思考下,这些服务实例信息都注册到哪里了呢?答案自然是注册中心了。这个注册中心不是SpringCloud里的内容,是第三方组件,常见的有Eureka, Consul,还有阿里的Nacos。

        所以SpringCloud既不管注册中心是谁家的,也不管服务是怎么被注册上的,它只有一个要求,那就是只要实现我提供的这两个接口就行了。这样就可以被我管理了。

        服务被注册上后,自然要有发现机制,要能找到它们。于是就又有了一个抽象,DiscoveryClient 接口,如图所示:

在这里插入图片描述
        这个接口比较核心的功能是获取所有的 serviceId,即都注册上了哪些工程。还有就是获取某个 serviceId 对应的所有服务实例,即某个工程的多份运行实例。Spring Cloud 还是不管具体实现细节,只要实现了 DiscoveryClient 这个接口就行了。
        

二、寻找自动注册机制

        总所周知,Spring 里的所有功能几乎都是通过注解开启的,而 Spring Cloud 又构建与 Spring Boot 之上,应该会使用自动配置机制,很快找到了 @EnableDiscoveryClient 注解,

/**
 * Annotation to enable a DiscoveryClient implementation.
 * @author Spencer Gibb
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
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;

}

        autoRegister 属性默认为true,就是可以自动注册,这个注解引入了一个类EnableDiscoveryClientImportSelector,如图所示:

在这里插入图片描述

org.springframework.cloud.client.discovery.EnableDiscoveryClientImportSelector#selectImports 代码片段
@Override
public String[] selectImports(AnnotationMetadata metadata) {
	//忽略部分代码...
	
	//获取自动注册标识,默认为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]);
	}
	
	return imports;
}

        通过上述流程,Spring Cloud 向容器导入了一个配置类,我们接着看看该类提供了哪些配置?

@Configuration(proxyBeanMethods = false)
@Import(AutoServiceRegistrationConfiguration.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
		matchIfMissing = true)
public class AutoServiceRegistrationAutoConfiguration {

	@Autowired(required = false)
	private AutoServiceRegistration autoServiceRegistration;

	@Autowired
	private AutoServiceRegistrationProperties properties;

	@PostConstruct
	protected void init() {
		if (this.autoServiceRegistration == null && this.properties.isFailFast()) {
			throw new IllegalStateException("Auto Service Registration has "
					+ "been requested, but there is no AutoServiceRegistration bean");
		}
	}
}

        导入的 AutoServiceRegistrationConfiguration 是自动配置中用到的属性类,默认是开启自动注册的,除此之外没有其它有价值信息。

        除此之外,注入了两个实例和一个初始化校验,咋看有点苍白呢…

        深入 AutoServiceRegistration 看一看,发现它是个空的标记接口,连注释都没有。看看它的实现类吧,AbstractAutoServiceRegistration

在这里插入图片描述
        从上述结构图看,该类提供了注册、注销等操作,结合该类的注释 “生命周期方法或许非常有用,通常用于服务注册的实现”。而且它包含了 ServiceRegistry 接口,它不就是用来注册服务实例的嘛。

        该类是如何实现服务注册的呢?查看结构图,实现了 事件监听器 接口,那应该是由事件驱动的。

org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration 代码片段
@Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
	bind(event);
}

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);
	}
}

        看这个if语句,大意是,如果当前没有正在运行的话,先发布一个开始注册事件,然后注册服务实例,接着再发布一个注册完成事件,最后设置为已经正在运行。

        方向还是对的,再看注册register方法,如图:

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

        调用服务注册接口注册服务实例,完全是正确的,只可惜获取服务实例的方法是抽象的。

        回想上述提到的,实现服务注册是通过事件监听器来驱动的,那么所监听的事件 WebServerInitializedEvent 是何时、何处发出的呢?通过跟踪发现:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#finishRefresh 代码片段
@Override
protected void finishRefresh() {
	super.finishRefresh();
	WebServer webServer = startWebServer();
	if (webServer != null) {
		publishEvent(new ServletWebServerInitializedEvent(webServer, this));
	}
}

        

三、实现,Nacos 整合 Spring Cloud 的源码

        在上述,我们分析到基于事件自动注册,但其获取服务实例的方法是抽象的,而且也说到 Spring Cloud 只提供相应的接口,并不管具体的注册中心是哪家的。

        现在,我们来看看使用具体的注册中心是如何注册服务的。

        按照 Spring Boot 自动配置加载思想,找到 NacosServiceRegistryAutoConfiguration 自动配置类,

@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);
	}

	@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);
	}
}

        可以看到 NacosRegistration 是对 SpringCloud 的注册接口 Registration 的实现,

public class NacosRegistration implements Registration, ServiceInstance {
	//忽略部分代码...
	
	@Override
	public String getServiceId() {
		return nacosDiscoveryProperties.getService();
	}

	@Override
	public String getHost() {
		return nacosDiscoveryProperties.getIp();
	}

	@Override
	public int getPort() {
		return nacosDiscoveryProperties.getPort();
	}
}

        第一是它没有重新实现getInstanceId()方法,而是采用接口中的默认实现。第二是服务Id、IP和端口都是从属性类中获取。

        接着看 NacosServiceRegistry 是对 Spring Cloud 的注册接口 ServiceRegistry 的实现,如图:

public class NacosServiceRegistry implements ServiceRegistry<Registration> {
	//忽略部分代码...
	
	@Override
	public void register(Registration registration) {
		//获取服务注册接口实例
		NamingService namingService = namingService();
		String serviceId = registration.getServiceId();
		String group = nacosDiscoveryProperties.getGroup();
		
		//获取注册的实例
		Instance instance = getNacosInstanceFromRegistration(registration);
		
		//注册服务
		namingService.registerInstance(serviceId, group, instance);
	}

	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.setEnabled(nacosDiscoveryProperties.isInstanceEnabled());
		instance.setMetadata(registration.getMetadata());
		return instance;
	}

	private NamingService namingService() {
		return nacosDiscoveryProperties.namingServiceInstance();
	}
}

        可以看到对注册方法的实现,Instance 和 namingService 都是Nacos里的类,最后调用 namingService.registerInstance(serviceId, instance) 方法把服务实例注册到Nacos里了。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值