在Java微服务越来越火的今天。几乎什么公司都在搞微服务。而使用的比较多的就是Spring Cloud技术栈。今天就来研究一下Spring Cloud中服务注册与发现的基本原理。
今天就要研究service registry模块。大致流程如下:
对于服务注册与发现Spring Cloud官方也给出了标准的接口DiscoveryClient(服务发现) ,ServiceRegistry(服务注册),要是实现服务注册与发现,第三方必须实现这两个接口。
public interface ServiceRegistry<R extends Registration> {
/**
* 服务注册接口
* Registers the registration. A registration typically has information about an
* instance, such as its hostname and port.
* @param registration registration meta data
*/
void register(R registration);
/**
* 注销服务注册信息
* Deregisters the registration.
* @param registration registration meta data
*/
void deregister(R registration);
/**
* Closes the ServiceRegistry. This is a lifecycle method.
*/
void close();
/**
* 设置注册的状态
* Sets the status of the registration. The status values are determined by the
* individual implementations.
* @param registration The registration to update.
* @param status The status to set.
* @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
*/
void setStatus(R registration, String status);
/**
* 获取注册的状态
* Gets the status of a particular registration.
* @param registration The registration to query.
* @param The type of the status.
* @return The status of the registration.
* @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
*/
<T> T getStatus(R registration);
}
可以看到这个接口类提供了注册的几个基本行为。而这些操作都是围绕Registration 来进行的。
可以看到Registration里面没有任何方法。只是扩展了一下ServiceInstance。Registration接口是一个典型的标记接口。实际接口方法都在ServiceInstance中。
因此对于Registration接口,不同的注册中心也应该有不同的实现。下面就以传统的Eureka注册中心为例。来看实现服务注册和发现的详细过程。
Eureka中Registration的实现是EurekaRegistration。结构如下:
可以看到。这里使用到了建造者设计模式来构建EurekaRegistration实例。而构建它需要几个必须参数:
① CloudEurekaInstanceConfig
Eureka实例配置,要注册的服务的信息配置。唯一子类是EurekaInstanceConfigBean,专门封装spring boot配置文件中eureka.instance.*相关的配置信息。
② EurekaClient
EurekaClient客户端。用于与Eureka进行数据交互的工具类。
可以看到,真正实现注册逻辑在com.netflix.discovery.DiscoveryClient中。而将服务信息注册到Eureka服务器的工具类就是EurekaHttpClient , 它实现了与Eureka进行数据操作的所有接口。
③ HealthCheckHandler 健康检查处理器
一个Eureka运行状况检查器,将应用程序状态映射到InstanceInfo.InstanceStatus,将传播到Eureka注册表
而正在聚合这些组件,来在Eruka上进行注册的核心类是EurekaServiceRegistry ,这是Eureka对Spring Cloud服务注册与发现标准接口ServiceRegistry的实现。
下面来看源码:
public class EurekaServiceRegistry implements ServiceRegistry<EurekaRegistration> {
private static final Log log = LogFactory.getLog(EurekaServiceRegistry.class);
//服务注册逻辑
@Override
public void register(EurekaRegistration reg) {
//初始化CloudEurekaClient实例
maybeInitializeClient(reg);
if (log.isInfoEnabled()) {
log.info("Registering application "
+ reg.getApplicationInfoManager().getInfo().getAppName()
+ " with eureka with status "
+ reg.getInstanceConfig().getInitialStatus());
}
//将当前注册的服务的状态设置为初始化状态
reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());
//注册健康检查器
reg.getHealthCheckHandler().ifAvailable(healthCheckHandler -> reg
.getEurekaClient().registerHealthCheck(healthCheckHandler));
}
private void maybeInitializeClient(EurekaRegistration reg) {
// force initialization of possibly scoped proxies
reg.getApplicationInfoManager().getInfo();
reg.getEurekaClient().getApplications();
}
//取消注册
@Override
public void deregister(EurekaRegistration reg) {
//如果注册信息本地管理器中存在。就将状态设置为DOWN(下线)
if (reg.getApplicationInfoManager().getInfo() != null) {
if (log.isInfoEnabled()) {
log.info("Unregistering application "
+ reg.getApplicationInfoManager().getInfo().getAppName()
+ " with eureka with status DOWN");
}
//将服务状态设置为DOWN后,会触发相应的监听器。将状态跟新到Eureka服务器中
reg.getApplicationInfoManager()
.setInstanceStatus(InstanceInfo.InstanceStatus.DOWN);
// shutdown of eureka client should happen with EurekaRegistration.close()
// auto registration will create a bean which will be properly disposed
// manual registrations will need to call close()
}
}
@Override
public void setStatus(EurekaRegistration registration, String status) {
InstanceInfo info = registration.getApplicationInfoManager().getInfo();
// TODO: howto deal with delete properly?
if ("CANCEL_OVERRIDE".equalsIgnoreCase(status)) {
registration.getEurekaClient().cancelOverrideStatus(info);
return;
}
// TODO: howto deal with status types across discovery systems?
InstanceInfo.InstanceStatus newStatus = InstanceInfo.InstanceStatus.toEnum(status);
registration.getEurekaClient().setStatus(newStatus, info);
}
//从Eureka服务注册中心查询当前服务的详细信息
@Override
public Object getStatus(EurekaRegistration registration) {
//获取app名称
String appname = registration.getApplicationInfoManager().getInfo().getAppName();
//获取应用ID
String instanceId = registration.getApplicationInfoManager().getInfo().getId();
//从Eureka服务器查询服务实例的详细信息
InstanceInfo info = registration.getEurekaClient().getInstanceInfo(appname,instanceId);
HashMap<String, Object> status = new HashMap<>();
if (info != null) {
status.put("status", info.getStatus().toString());
status.put("overriddenStatus", info.getOverriddenStatus().toString());
}
else {
status.put("status", UNKNOWN.toString());
}
return status;
}
public void close() {
}
}
以上把服务注册的实现逻辑关系理清楚了。现在的问题是服务启动的时候怎么去进行注册的?
懂Spring Boot应用事件的小伙伴肯定会想到。这个整合与Spring Boot事件有关。毕竟一般框架与spring整合都会使用到事件的原理。Eureka也是一样。在触发RefreshScopeRefreshedEvent事件后。会将应用的信息注册到Eureka服务注册中心。
可以看到,使用了ApplicationListener的方式来进行服务的注册。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RefreshScopeRefreshedEvent.class)
protected static class EurekaClientConfigurationRefresher
implements ApplicationListener<RefreshScopeRefreshedEvent> {
@Autowired(required = false)
private EurekaClient eurekaClient;
@Autowired(required = false)
private EurekaAutoServiceRegistration autoRegistration;
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
// This will force the creation of the EurkaClient bean if not already created
// to make sure the client will be reregistered after a refresh event
if (eurekaClient != null) {
eurekaClient.getApplications();
}
if (autoRegistration != null) {
// register in case meta data changed
this.autoRegistration.stop();
this.autoRegistration.start();
}
}
}
注册的调用客户端是EurekaAutoServiceRegistration实例。源码如下:
可以看到。EurekaAutoServiceRegistration本身也是一个监听器。实现的是SmartApplicationListener。SmartApplicationListener实现了ApplicationListener。能提供更精确的事件触发控制。
因此可以通过两种事件触发服务注册。因此这里执行注册之前会先stop再start .防止重复注册。
EurekaAutoServiceRegistration#start()逻辑如下:
private AtomicBoolean running = new AtomicBoolean(false);
private int order = 0;
private AtomicInteger port = new AtomicInteger(0);
private ApplicationContext context;
private EurekaServiceRegistry serviceRegistry;
private EurekaRegistration registration;
@Override
public void start() {
// only set the port if the nonSecurePort or securePort is 0 and this.port != 0
if (this.port.get() != 0) {
if (this.registration.getNonSecurePort() == 0) {
this.registration.setNonSecurePort(this.port.get());
}
if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
this.registration.setSecurePort(this.port.get());
}
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
//注册
this.serviceRegistry.register(this.registration);
//发布InstanceRegisteredEvent事件
this.context.publishEvent(new InstanceRegisteredEvent<>(this,this.registration.getInstanceConfig()));
this.running.set(true);
}
}
开头说到了,实现Spring Cloud的服务发现。需要自定义实现org.springframework.cloud.client.discovery.DiscoveryClient接口。
还是以Eureka为例,它的实现如下:
在这里,CompositeDiscoveryClient是Spring Cloud自己的实现。使用了委托设计模式。用来管理其他的DiscoveryClient实现。使用服务发现时。也是调用的这个类。
官方解释: A {@link DiscoveryClient} that is composed of other discovery clients and delegates calls to each of them in order.
当Eureka实现了DiscoveryClient接口后。就会按照order的顺序。去调用对应的实现。Eureka获取服务的具体实现也是在EurekaClient实例中。