在前面的文章介绍了,如何使用服务注册发现组件: Eureka,并给出使用示例。本文在此基础上,将会讲解 Eureka 客户端实现的内幕,结合源码深入实现的细节,知其所以然。客户端需要重点关注以下几点:
- 从Eureka Server中拉取注册表信息
- 全量拉取注册表信息
- 增量式拉取注册表信息
- 注册表缓存刷新定时器与续租(心跳)定时器
- 服务注册与服务按需注册
- 服务实例的下线
本文摘录于笔者出版的书籍 《Spring Cloud 微服务架构进阶》
Eureka Client 结构
在Finchley版本的SpringCloud中,不需要添加任何的额外的注解就可以登记为Eureka Client,只需要在pom文件中添加spring-cloud-starter-netflix-eureka-client
的依赖。
为了跟踪Eureka的运行机制,读者可以打开SpringBoot的Debug模式来查看更多的输出日志:
logging:
level:
org.springframework: DEBUG
查看spring-cloud-netflix-eureka-client
的src/main/resource.META-INF/spring.factories
,查看Eureka Client有哪些自动配置类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
排除掉与配置中心相关的自动配置类,从中可以找到三个与Eureka Client密切相关的自动配置类:
- EurekaClientAutoConfiguration
- RibbonEurekaAutoConfiguration
- EurekaDiscoveryClientConfiguration
下面将对这些类进行分析,看看一个正常的Eureka Client需要做哪一些初始化配置。
EurekaClientAutoConfiguration
Eureke Client的自动配置类,负责了Eureka Client中关键的bean的配置和初始化,以下是其内比较重要的bean的介绍与作用。
EurekaClientConfig
提供了Eureka Client注册到Eureka Server所需要的配置信息,SpringCloud为其提供了一个默认配置类的EurekaClientConfigBean
,可以在配置文件中通过前缀eureka.client
+属性名进行覆盖。
ApplicationInfoManager
该类管理了服务实例的信息类InstanceInfo
,其内包括Eureka Server上的注册表所需要的信息,代表了每个Eureka Client提交到注册中心的数据,用以供服务发现。同时管理了实例的配置信息EurekaInstanceConfig
,SpringCloud提供了一个EurekaInstanceConfigBean
的配置类进行默认配置,也可以在配置文件application.yml
中通过eureka.instance
+属性名进行自定义配置。
EurekaInstanceConfigBean
继承了EurekaInstanceConfig
接口,是Eureka Client注册到服务器上需要提交的关于服务实例自身的相关信息,主要用于服务发现:
通常这些信息在配置文件中的eureka.instance
前缀下进行设置,SpringCloud通过EurekaInstanceConfigBean
配置类提供了相关的默认配置。以下是一些比较关键的属性,这些信息都将注册到注册中心上。
public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, EnvironmentAware {
// 服务实例的应用名
private String appname;
// 服务实例的Id,通常和appname共同唯一标记一个服务实例
private String instanceId;
// 自定义添加的元数据,由用户使用以适配扩展业务需求
private Map<String, String> metadataMap;
// 如果服务实例部署在AWS上,该类将持有服务实例部署所在的数据中心的准确信息
private DataCenterInfo dataCenterInfo;
// 服务实例的Ip地址
private String ipAddress;
// 服务实例主页地址
private String homePageUrl;
// 服务实例健康检查地址
private String healthCheckUrlPath;
// 服务实例的状态地址
private String statusPageUrlPath
.....
}
DiscoveryClient
这是SpringCloud定义的用来服务发现的顶级接口,在Netflix Eureka或者consul都有相应的具体实现类,提供的方法如下:
public interface DiscoveryClient {
// 获取实现类的描述
String description();
// 通过服务Id获取服务实例的信息
List<ServiceInstance> getInstances(String serviceId);
// 获取所有的服务实例的Id
List<String> getServices();
}
其在Eureka方面的实现的相关的类结构图:
EurekaDiscoveryClient
继承了DiscoveryClient
,但是通过查看EurekaDiscoveryClient
中的代码,会发现它是通过组合类EurekaClient
实现接口的功能,如下的getInstance
接口:
@Override
public List<ServiceInstance> getInstances(String serviceId) {
List<InstanceInfo> infos = this.eurekaClient.getInstancesByVipAddress(serviceId,false);
List<ServiceInstance> instances = new ArrayList<>();
for (InstanceInfo info : infos) {
instances.add(new EurekaServiceInstance(info));
}
return instances;
}
EurekaClient
来自于com.netflix.discovery
包中,其默认实现为com.netflix.discovery.DiscoveryClient
,这属于eureka-client的源代码,它提供了Eureka Client注册到Server上、续租,下线以及获取Server中注册表信息等诸多关键功能。SpringCloud通过组合方式调用了Eureka中的的服务发现方法,关于EurekaClient
的详细代码分析将放在客户端核心代码中介绍。为了适配spring-cloud
,spring提供了一个CloudEurekaClient
继承了com.netflix.discovery.DiscoveryClient
,同时覆盖了onCacheRefreshed
防止在spring-boot
还没初始化时调用该接口出现NullPointException
。
上述的几个配置类之间的关系非常紧密,数据之间存在一定的耦合,所以下面介绍一下它们之间的关系
首先是EurekaInstanceConfig
,代码位于EurekaClientAutoConfiguration
@Bean
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils, ManagementMetadataProvider managementMetadataProvider) {
// 从配置文件中读取属性
String hostname = getProperty("eureka.instance.hostname");
boolean preferIpAddress = Boolean.parseBoolean(getProperty("eureka.instance.prefer-ip-address"));
String ipAddress = getProperty("eureka.instance.ipAddress");
boolean isSecurePortEnabled = Boolean.parseBoolean(getProperty("eureka.instance.secure-port-enabled"));
String serverContextPath = env.getProperty("server.context-path", "/");
int serverPort = Integer.valueOf(env.getProperty("server.port", env.getProperty("port", "8080")));
Integer managementPort = env.getProperty("management.server.port", Integer.class);// nullable. should be wrapped into optional
String managementContextPath = env.getProperty("management.server.context-path");// nullable. should be wrapped into optional
Integer jmxPort = env.getProperty("com.sun.management.jmxremote.port", Integer.class);//nullable
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
// 设置非空属性
instance.setNonSecurePort(serverPort);
instance.setInstanceId(getDefaultInstanceId(env));
instance.setPreferIpAddress(preferIpAddress);
if (StringUtils.hasText(ipAddress)) {
instance.setIpAddress(ipAddress);
}
if(isSecurePortEnabled) {
instance.setSecurePort(serverPort);
}
if (StringUtils.hasText(hostname)) {
instance.setHostname(hostname);
}
String statusPageUrlPath = getProperty("eureka.instance.status-page-url-path");
String healthCheckUrlPath = getProperty("eureka.instance.health-check-url-path")