一:注册服务 源码:
先从服务注册开始梳理,Eureka Client启动的时候就去Eureka Server注册服务。通过在启动类上添加@EnableDiscoveryClient这个注解,来声明这是一个Eureka Client。
以下是启动类代码:
package com.example.eurekaclient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
//开启服务注册于发现
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
点击@EnableDiscoveryClient ,进到这个注解中,该注解代码如下:
/*
* Copyright 2013-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.client.discovery;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* 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;
}
这个注解上方有注释,通过翻译,说这个注解是为了开启一个DiscoveryClient的实例。
接着我们搜索 DiscoveryClient 这个类,可以看到以后注释:
package com.netflix.discovery;
/**
* The class that is instrumental for interactions with <tt>Eureka Server</tt>.
*
* <p>
* <tt>Eureka Client</tt> is responsible for a) <em>Registering</em> the
* instance with <tt>Eureka Server</tt> b) <em>Renewal</em>of the lease with
* <tt>Eureka Server</tt> c) <em>Cancellation</em> of the lease from
* <tt>Eureka Server</tt> during shutdown
* <p>
* d) <em>Querying</em> the list of services/instances registered with
* <tt>Eureka Server</tt>
* <p>
*
* <p>
* <tt>Eureka Client</tt> needs a configured list of <tt>Eureka Server</tt>
* {@link java.net.URL}s to talk to.These {@link java.net.URL}s are typically amazon elastic eips
* which do not change. All of the functions defined above fail-over to other
* {@link java.net.URL}s specified in the list in the case of failure.
* </p>
*
* @author Karthik Ranganathan, Greg Kim
* @author Spencer Gibb
*
*/
@Singleton
public class DiscoveryClient implements EurekaClient {
大体意思是:
这个类用于帮助与Eureka Server互相协作。
Eureka C巨ent负责下面的任务:
-向Eureka Server注册服务实例
-向Eureka Server服务租约
- 当服务关闭期间, 向Eureka Server取消租约
-查询Eureka Server中的服务实例列表
Eureka Client还需要配置一个Eureka Server的 URL列表。
我们先看看在哪里对 Eureka Server的URL列表进行配置。就是我们配置文件中的属性名eureka.client.serviceUrl.defaultZone, 这个地址就是Eureka Server的地址,服务注册、服务续约以及其他的操作,都是向这个地址发送请求的。
接着看下这个类的类图关系:
这个类实现了EurekaClient接口,并且它是单例模式,而EurekaClient又继承了LookupService接口。点击进去EurekaClient 这个接口,可以看到:
/**
* Define a simple interface over the current DiscoveryClient implementation.
*
* This interface does NOT try to clean up the current client interface for eureka 1.x. Rather it tries
* to provide an easier transition path from eureka 1.x to eureka 2.x.
*
* EurekaClient API contracts are:
* - provide the ability to get InstanceInfo(s) (in various different ways)
* - provide the ability to get data about the local Client (known regions, own AZ etc)
* - provide the ability to register and access the healthcheck handler for the client
*
* @author David Liu
*/
@ImplementedBy(DiscoveryClient.class)
public interface EurekaClient extends LookupService {
根据注释,我们可以得知这个接口的大体作用
EurekaClient API合同包括:
-提供获取InstanceInfo的能力(以各种不同的方式)
-提供获取本地客户数据的能力(已知地区、自有AZ等)
-提供为客户端注册和访问healthcheck处理程序的能力
看第三条可以知道 DiscoveryClient 类主要就是发现服务的。
因为是单例的,所有Eureka Client需要一开始先初始化DiscoveryClient实例,那就看下DiscoveryClient的构造方法。
DiscoveryClient的构造方法还是挺长的,里面初始化了一大堆的对象,不过可以观察到在new了这么一大堆对象之后,调用了initScheduledTasks();这个方法,所以,点进initScheduledTasks()方法里面看下。
/**
* Initializes all scheduled tasks.
*/
private void initScheduledTasks() {
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);
}
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
//服务续约
// Heartbeat timer
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
// InstanceInfo replicator
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
// log at warn level if DOWN was involved
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
instanceInfoReplicator.onDemandUpdate();
}
};
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
在initScheduledTasks方法中,初始化了几个任务。
一开始有个if判断,判断是否需要从Eureka Server获取数据,如果为真,则初始化一个服务获取的定时任务。
还有有个if (clientConfig.shouldRegisterWithEureka())的判断,所以,当Eureka Client配置这个为true时,就会执行这个if语句里面的逻辑。if语句中,会初始化一个Heartbeat timer和InstanceInfoReplicator。Heartbeat timer就是不断的发送请求来维持心跳的,也就是服务续约的任务。而InstanceInfoReplicator类是注册时客户端给服务端的服务的元数据,它实现了Runnable接口,所以需要看下InstanceInfoReplicator类中的run方法。
public void run() {
try {
discoveryClient.refreshInstanceInfo();
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
discoveryClient.register();
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
} catch (Throwable t) {
logger.warn("There was a problem with the instance info replicator", t);
} finally {
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
可以发现了中discoveryClient.register () ; 这一行,真正触发调用注册的
地方就在这里。 继续查看 register ()的实现内容, 如下所示:
/**
* Register with the eureka service by making the appropriate REST call.
*/
boolean register() throws Throwable {
logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
throw e;
}
if (logger.isInfoEnabled()) {
logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
}
return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
}
register()方法,就会把instanceInfo信息,通过REST请求发送给Eureka Server。instanceInfo就是客服端服务的元数据。
Debug 图:
二:获取服务注册列表 和续约 源码
顺着上面的思路, 我们继续来看 DiscoveryC巨ent 的江itScheduledTasks 函数, 不难发现在其中还有两个定时任务, 分别是 “服务获取 ” registry cache refresh timer 和“服务续约" Heartbeat timer :
private void initScheduledTasks() {
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);
}
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
//服务续约
// Heartbeat timer
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
1.点击CacheRefreshThread(),可以看到:
/**
* The task that fetches the registry information at specified intervals.
*
*/
class CacheRefreshThread implements Runnable {
public void run() {
refreshRegistry();
}
}
从注释可以看出 它的作用是 按指定间隔获取注册表信息的任务。
2.点击HeartbeatThread(),可以看到:
/**
* The heartbeat task that renews the lease in the given intervals.
*/
private class HeartbeatThread implements Runnable {
public void run() {
if (renew()) {
lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
从注释可以看出 它的作用是 续约(心跳)。
另外,服务续约有两个参数是可以配置的 ,即Eureka Client 发送续约心跳的时间参数和Eureka Server 在多长时间内没有收到心跳将实例剔除的时间参数。
在默认情况下,这两个参数分别为30秒和90秒,官方的建议是不要修改,如有需要,只需要在Eureka Client 和 Eureka Server的配置文件中 加以下的配置:
eureka.instance.leaseRenewalIntervalInseconds
eureka.instance.leaseExpirationDurationInseconds
参考:https://blog.csdn.net/chayangdz/article/details/82012937
https://blog.csdn.net/qq_30062125/article/details/83833006