将一个Spring Boot应用注册到Eureka Server或者是从Eureka Server获取服务列表时,主要做两件事: 应用启动类配置 @EnableDiscoveryClient 注解并且在applicaiton.properties 中用eureka.client.serviceUrl.defaultZone参数指定服务注册中心的位置。 本章主要从源代码的角度学学Spring Cloud Eureka 的实现
Eureka Client客户端
1.@EnableDiscoveryClient 注解
从源码中可以看到,@EnableDiscoveryClient注解是一个@Import(EnableDiscoveryClientImportSelector.class)表示引用类EnableDiscoveryClientImportSelector
/**
* 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.
*/
boolean autoRegister() default true;
}
EnableDiscoveryClientImportSelector类继承Spring Cloud的类 SpringFactoryImportSelector,并复写了其ImportSelector#selectImports接口实现, ImportSelector接口是spring中导入外部配置的核心接口,可以通过指定的选择条件来决定哪些类被注册到Spring中。 EnableDiscoveryClientImportSelector重写接口实现,将AutoServiceRegistrationConfiguration 加入import数组列表中,如果这个类没初始化的话,服务注册上去了也不能提供服务的,因为默认启动状态是STARTING
,而如果要进行服务的话,需要是UP ,AutoServiceRegistrationConfiguration
就可以让他变成UP
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector<EnableDiscoveryClient> {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
String[] imports = super.selectImports(metadata);
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(getAnnotationClass().getName(), 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;
}
。。。。
}
2.EurekaAutoServiceRegistration
从源码中可以看到 EurekaAutoServiceRegistration 实现了SmartLifecycle接口start()方法。SmartLifecycle接口的作用为当Spring容器加载所有bean并完成初始化之后,会接着回调实现该接口的类中对应的方法(start()方法)。通过此方法实现应用启动注册服务
//实现接口SmartLifecycle
public class EurekaAutoServiceRegistration implements AutoServiceRegistration, SmartLifecycle, Ordered {
private static final Log log = LogFactory.getLog(EurekaAutoServiceRegistration.class);
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;
public EurekaAutoServiceRegistration(ApplicationContext context, EurekaServiceRegistry serviceRegistry, EurekaRegistration registration) {
this.context = context;
this.serviceRegistry = serviceRegistry;
this.registration = registration;
}
public void start() {
if (this.port.get() != 0 && this.registration.getNonSecurePort() == 0) {
this.registration.setNonSecurePort(this.port.get());
}
if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
//1. 通过EurekaServiceRegistry 对象注册服务
this.serviceRegistry.register(this.registration);
//2. 发布通知事件
this.context.publishEvent(new InstanceRegisteredEvent(this, this.registration.getInstanceConfig()));
//3. 设置启动=true
this.running.set(true);
}
}
//....
}
3.EurekaServiceRegistry
实现注册EurekaRegistration 会有三个操作
- 注册EurekaClient,这里使用的对象是CloudEurekaClient ,此对象继承DiscoveryClient ,而DiscoveryClient对象实现接口EurekaClient
- 设置Eureka 实例的状态为UP ,UP状态表示准备接收流量
- 如果设置了HealthCheckHandler则将其设置到EurekaClient中, HealthCheckHandler 接口可以用于提供比现有的更精细的运行状况检查
public class EurekaServiceRegistry implements ServiceRegistry<EurekaRegistration> {
//...
public void register(EurekaRegistration reg) {
//1.初始化
this.maybeInitializeClient(reg);
if (log.isInfoEnabled()) {
log.info("Registering application " + reg.getInstanceConfig().getAppname() + " with eureka with status " + reg.getInstanceConfig().getInitialStatus());
}
// setInstanceStatus设置状态为UP
reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());
// 设置心跳Handler
if (reg.getHealthCheckHandler() != null) {
reg.getEurekaClient().registerHealthCheck(reg.getHealthCheckHandler());
}
}
private void maybeInitializeClient(EurekaRegistration reg) {
reg.getApplicationInfoManager().getInfo();
//在getEurekaClient方法调用的时候创建DiscoveryClient,
//CloudEurekaClient 集成了DiscoveryClient
reg.getEurekaClient().getApplications();
}
//...
}
reg#getEurekaClient 方法中会先判断如果还没有生成CloudEurekaClient ,则通过getTargetObject以代理的方式生成CloudEurekaClient 并完成其注册
public class EurekaRegistration implements Registration {
public CloudEurekaClient getEurekaClient() {
if (this.cloudEurekaClient.get() == null) {
try {
this.cloudEurekaClient.compareAndSet(null, getTargetObject(eurekaClient, CloudEurekaClient.class));
} catch (Exception e) {
log.error("error getting CloudEurekaClient", e);
}
}
return this.cloudEurekaClient.get();
}
protected <T> T getTargetObject(Object proxy, Class<T> targetClass) throws Exception {
if (AopUtils.isJdkDynamicProxy(proxy)) {
return (T) ((Advised) proxy).getTargetSource().getTarget();
} else {
return (T) proxy; // expected to be cglib proxy then, which is simply a specialized class
}
}
}
4.DiscoveryClient
先看DiscoveryClient的注释,注释中大致意思是
这个类用于帮助与Eureka Server互相协作,主要负责内容为
- 向Eureka Server注册服务实例
- 向Eureka Server服务租约
- 当服务关闭期间,向Eureka Server取消掉租约
- 查询Eureka Server中的服务实例信息列表
DiscoveryClient 实现接口EurekaClient, 它的主要作用是用于与Eureka服务器进行交互的类,DiscoveryClient初始化的时候会实现两个调度 ,详情如下
@Singleton
public class DiscoveryClient implements EurekaClient {
private static final Logger logger = LoggerFactory.getLogger(DiscoveryClient.class);
private final ScheduledExecutorService scheduler;
// additional executors for supervised subtasks
private final ThreadPoolExecutor heartbeatExecutor; //心跳检测的线程池
private final ThreadPoolExecutor cacheRefreshExecutor;//缓存服务的线程池
private final EurekaTransport eurekaTransport;
@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider) {
//略。。
try {
scheduler = Executors.newScheduledThreadPool(3,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
//设置心跳线程池:通知Eureka Server 实现续约
heartbeatExecutor = new ThreadPoolExecutor(
1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
//设置缓存线程池:
cacheRefreshExecutor = new ThreadPoolExecutor(
1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
//..
} catch (Throwable e) {
throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
}
//....
//初始化调度任务
//调度1: CacheRefreshThread线程实现定时执行,实现更新Eureka Server注册的服务
//调度2:HeartbeatThread宣城定时执行,心跳通知Eureka Server实现服务续约
initScheduledTasks();
}
}
4.1服务注册&续约
上面提到的DiscoveryClient构造方法中initScheduledTasks中实现了服务注册,
当配置中配置为需要向Eureka注册服务时候
1. 调度中使用HeartbeatThread 实现定时向 Eureka Server 发送消息,告诉 Eureka Server当前服务还“活着”,防止Eureka Server的提出任务将本服务从服务列表中删除,也就是服务续约。
参数eureka.client.less-renew-interval-in-seconds 配置客户端指定的续订间隔设置(秒),默认为30秒
2. InstanceInfoReplicator实现了Runnable接口, 其run()方法实现刷新DiscoverClinet配置信息,并且调用discoveryClient.register()方法实现向Eureka Server注册
参数eureka.client.instance.info.replication.interval.seconds配置更新受理信息到Eureka服务端的间隔时间(秒),默认为30秒
if (clientConfig.shouldRegisterWithEureka()) {
// 通过HeartbeatThread()对象实现心跳
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
// InstanceInfo replicator
// instanceInfoReplicator对象实现了Runnable
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
//实现服务注册
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
}
4.2 服务获取
调度实现中, 使用CacheRefreshThread 实现定时从Eureka Server获取其上的注册服务列表,
参数eureka.client.registry-fetch-interval-seconds 配置从中获取注册表信息的频率(秒) ,默认为30秒
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
整理时序图
Eureka配置总结
属性 | 属性作用说明 |
eureka.instance.hostname | Eureka Serve服务实例主机名,例如localhost |
eureka.instance.lease-expiration-duration-in-seconds | Eureka Server将超过配置时间没有续约的服务剔除,默认90秒 |
eureka.client.serviceUrl.defaultZon | 指定服务注册中心的地址 |
eureka.client.register-with-eureka | 是否注册中心注册,默认=TRUE |
eureka.client.fetch-registry | 是否需要检索服务,默认=TRUE |
eureka.client.registry-fetch-interval-seconds | Eureka Client配置从中获取注册表信息的频率(秒) ,默认为30秒 |
eureka.instance.less-renew-interval-in-seconds | Eureka Client配置客户端指定的续订间隔设置(秒),默认为30秒 |
eureka.client.instance.info.replication.interval.seconds | 配置更新受理信息到Eureka服务端的间隔时间(秒),默认为30秒 |