服务注册与发现
Spring Cloud 统一服务注册/发现编程模型
Spring Cloud 统一服务注册和发现编程模型,其中代码在spring-cloud-commons模块
接口 | 作用 |
---|---|
org.springframework.cloud.client.discovery.DiscoveryClient | 代表服务发现常见的读取操作 |
org.springframework.cloud.client.discovery.EnableDiscoveryClient | 使用该注解表示开启服务发现功能 |
org.springframework.cloud.client.discovery.ReactiveDiscoveryClient | 基于响应式的代表服务发现常见的读取操作 |
org.springframework.cloud.client.serviceregistry.ServiceRegistry | 注册与销毁服务的操作封装 |
org.springframework.cloud.client.ServiceInstance | 代表服务的一个实例 |
统一编程模型优点
- 无须关注底层服务的实现注册和发现的细节,只需要了解上层统一的抽象
- 更换注册中心非常简单,只需要改maven依赖和对应的注册中心配置信息
DiscoveryClient 和ReactiveDiscoveryClient
两个都是从注册中心发现provider的服务操作,ReactiveDiscoveryClient是在Hoxton M3版本加入的响应式的注册发现接口。
1 | public interface DiscoveryClient extends Ordered { |
1 | public interface ReactiveDiscoveryClient extends Ordered { |
ServiceInstance和Registration
Spring Cloud 提供的ServiceInstance和Registration的作用就是抽象实例在各种注册中心的数据模型,无论Zookeeper的Service Instance、Eureka的InstanceInfo,还是Nacos的Instance。在SpringCloud都会被统一抽象成Service Instance和Registration。其中ServiceInstance表示客户端从注册中心获取实例数据结构,Registration表示客户端注册到注册中心的实例数据结构。
1 | public interface ServiceInstance { // 代表服务的一个实例 |
使用org.springframework.cloud.client.discovery.DiscoveryClient#getInstances可以基于服务名获取到这个服务下的所有ServiceInstance集合(List<ServiceInstance>
)。比如,provider 服务在注册中心可能会存在两个Service Instance,即Service- instance1:x.x.x.x:8080和Service- instance2:x.x.x.x:8081.
1 | public interface Registration extends ServiceInstance { |
Registration接口继承了ServiceInstance,并且没有额外的方法定义。因为在注册中心获取实例信息和把实例信息注册到注册中心这个两个过程实例信息的储存结构完全可以相通。未来可能会在Registration接口中添加一些方法。
ServiceRegistration
ServiceRegistration使用了Registration接口,用于服务信息的注册(register)和注销(deregister)。
1 | public interface ServiceRegistry<R extends Registration> { |
服务注册和服务销毁的说明
org.springframework.cloud.client.serviceregistry.AutoServiceRegistration
是一个空接口实现,表示自动完成服务注册过程。接口默认实现是org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration
抽象类。
我工程引入eureka注册中心,可以看到有org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration
实现类,然而会在org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration
自动化装配类中被自动构造。
AbstractAutoServiceRegistration
抽象实现了AutoServiceRegistration
接口,同时也实现了Application Listener接口并且监听WebServerInitializedEvent。收到事件后,使用org.springframework.cloud.client.serviceregistry.ServiceRegistry#register
完成服务的注册。应用程序关闭时出发@PreDestroy注解使用org.springframework.cloud.client.serviceregistry.ServiceRegistry
完成服务注销。
Spring Cloud 2.2.0 之后通过WebServer InitializedEvent事件监听完成服务注册已经被声明为过期方法。SpringCloud 把注册时机的决定权交给了各个注册中心去现实。
ServiceRegistryEndpoint
Service RegistryEndpoint是Spring Cloud服务注册/发现功能对外报漏的Endpoint,其中ID是service-registry,用于获取和设置服务实例的状态。获取和设置的动作是由ServiceRegister的getStatus和setStatus方法完成。
在服务添加actuator
依赖。配置management.endpoints.web.exposure.include=*
后启动,然后执行下线操作。http://localhost:8080/actuator/service-registry?status=down
从注册中心就会看到服务下线状态。
ServiceRegistryEndPoint非常实用,可以完成应用无损下线。
无损下线:比如你有五台机器,某天向滚动更新发布新版的服务,你要下线一部分旧版本的服务,部署新版本服务。旧版本下线的时候,服务本地的client存旧服务下线服务地址,会导致调用失败。
因为SpringCloud 默认的服务实例更新机制是30s去注册中心获取服务对应的实例列表信息覆盖内存的实例信息。如果这个旧服务在30s就完成下线操作,但是调用的客户端也没有达到30s刷新时间,内存存储的服务已经被下线,这样就会调用接口就会发生超时异常。
使用ServiceRegistryEndpoint让应用无损下线思路:
- 调用ServiceRegistryEndpoint,将需要下线的实例服务下线
- 服务下线之后,等待客户端达到30s刷新时间,通过刷新实例列表信息删除已经下线实例(只是注册中心服务下线,实例服务并为下线)
- 实例服务下线(服务发现已经不再添加该实例,可以放心下线实例)
双注册双订阅模式
双注册双订阅表示一个Provider应用可以将自身的实例信息注册到多个注册中心上,一个Consumer应用可以订阅到多个注册中心上的服务实例信息。
如图所示,Provider可以把自身的服务实例信息注册到Nacos和Eureka集群上,Consumer发起服务订阅的时候可以从Nacos和Eureka上订阅服务。
双注册双订阅模式分析
Spring Cloud自身的编程模型是支持双注册双订阅模式的。在服务注册侧,Spring Cloud各个注册中心都有AutoServiceRegistration的实现类,比如,NacosAutoServiceRegistration和EurekaAutoServiceRegistration实现在类内部完成服务的注册。这些AutoServiceRegistration的实现类都实现了 Lifecycle接口,在start过程中完成服务注册操作。
在服务订阅侧,DiscoveryClient 统一了Spring Cloud服务发现的操作。其中,CompositeDiscoveryClient是一个特殊的DiscoveryClient实现:
1 | package org.springframework.cloud.client.discovery.composite; |
在 getInstances 方法中,会聚合所有的 DiscoveryClient 实现类找到的服务名,也会遍历每个DiscoveryClient查询服务名对应的实例信息。
下面在一个应用里分别加上 Nacos (com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery)和 Eureka(org.springframework.cloud:spring-cloud-starter-netfix-eureka-client)依赖,用来完成双注册双订阅。
应用启动后,会出现以下报错信息:
1 | *************************** |
从这个报错信息可以很明显地看出,ServiceRegistryAutoConfiguration 自动化配置类的内部类ServiceRegistryEndpointConfiguration内部依赖一个RegistrationBean,但是在Nacos和Eureka依赖内部分别会构造 NacosRegistration 和EurekaRegistration,这样会出现 ServiceRegistry-EndpointConfiguration 并不知道要注入哪个 Registration Bean 的问题。同理,AutoService-RegistrationAutoConfiguration内部的AutoServiceRegistration Bean也会引起一样的问题。
为了解决这个问题,可以在配置文件里过滤这两个自动化配置类:
1 | spring.autoconfigure.exclude=org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration |
加上该配置之后,还需要通过@EnableConfigurationProperties 注解让AutoServiceRegistration-Properties 配置类生效。这是因为所有的AutoServiceRegistration 实现类在构造过程中都需要这个配置类Bean。
有了这两个条件之后,即可享受双注册双订阅模式。
案例:使用双注册双订阅模式将Eureka注册中心迁移到Nacos注册中心
假设某公司原先使用 Eureka 作为注册中心,Nacos 开源之后,该公司想把Eureka替换成Nacos注册中心,要求在这个过程中对客户没有任何影响,也不能造成业务损失。
对于这个场景,可以使用双注册双订阅方案来完成任务。如图所示,这是一个3个阶段的过程图。
第1阶段:Eureka作为注册中心,Provider完成服务注册,Consumer完成服务发现。
第2阶段:双注册双订阅的核心阶段,该阶段内部包括以下4个操作。
上线新的Provider(拥有双注册能力),这时Eureka 注册中心的Provider有两个实例。
下线旧的 Provider,下线之后由于新 Provider 也会注册到 Eureka 上,这时旧的Consumer可以找到新Provider的实例。
上线新的 Consumer(拥有双订阅能力),新 Consumer 可以订阅 Nacos 和Eureka 集群的服务实例,这时可以订阅到Nacos上的服务实例。
下线旧的Consumer。
第3阶段:Eureka下线,使用Nacos替换Eureka作为新的注册中心,Provider和Consumer的服务注册和服务发现操作只与Nacos交互。