前言
- 什么是服务?
- 如何注册与发现?
接触 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 这个接口就行了。
二、寻找自动注册机制
@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里了。