spring cloud eureka
代码版本:Dalston.SR4
Lease
public class Lease<T> {
enum Action {
Register, Cancel, Renew
};
public static final int DEFAULT_DURATION_IN_SECS = 90;
private T holder;
private long evictionTimestamp;
private long registrationTimestamp;
private long serviceUpTimestamp;
private volatile long lastUpdateTimestamp;
private long duration;
public Lease(T r, int durationInSecs) {
holder = r;
registrationTimestamp = System.currentTimeMillis();
lastUpdateTimestamp = registrationTimestamp;
//durationInSecs为秒单位, 换算成毫秒
duration = (durationInSecs * 1000);
}
// 客户端续约时,更新最后的更新时间 , 用当前系统加上过期的时间
public void renew() {
lastUpdateTimestamp = System.currentTimeMillis() + duration;
}
// 服务下线时,更新服务下线时间
public void cancel() {
if (evictionTimestamp <= 0) {
evictionTimestamp = System.currentTimeMillis();
}
}
public void serviceUp() {
if (serviceUpTimestamp == 0) {
serviceUpTimestamp = System.currentTimeMillis();
}
}
public void setServiceUpTimestamp(long serviceUpTimestamp) {
this.serviceUpTimestamp = serviceUpTimestamp;
}
public boolean isExpired() {
return isExpired(0l);
}
public boolean isExpired(long additionalLeaseMs) {
return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs));
}
}
- Eureka-Server最终处理注册信息的时候,都会转化为这个对象来处理。
- DEFAULT_DURATION_IN_SECS : 租约过期的时间常量,默认90秒,也就说90秒没有心跳过来,sever将会自动剔除该节点
- holder :这个租约是属于谁的, 目前占用这个属性的是instanceInfo,也就是客户端实例信息。
- evictionTimestamp :租约是啥时候过期的,当服务下线的时候,会过来更新这个时间戳
- registrationTimestamp :租约的注册时间
- serviceUpTimestamp :服务启动时间 ,当客户端在注册的时候,instanceInfo的status 为UP的时候,则更新这个时间戳
- lastUpdateTimestamp :最后更新时间,每次续约的时候,都会更新这个时间戳,在判断实例是否过期时,需要用到这个属性。
- duration:过期时间,毫秒单位
Client
@EnableDiscoveryClient
入口为注解@EnableDiscoveryClient:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EnableDiscoveryClientImportSelector.class})
public @interface EnableDiscoveryClient {
boolean autoRegister() default true;
}
该注解通过import导入了EurekaDiscoveryClientConfiguration实例,该实例内部实例化了一个Marker类。
接下来通过spring boot自动配置会实例EurekaClientAutoConfiguration类,该类就是我们要找的目标,但是该类有个启动条件@ConditionalOnBean({Marker.class})。
现在我们知道了@EnableDiscoveryClient注解是如何开启eureka客户端的。
EurekaClientAutoConfiguration:该类是eureka的核心类,是在netflix-eureka-client包下的。
EurekaClientAutoConfiguration会配置一个DiscoveryClient实例到容器,这个就是我们想要的,该类封装了客户端所有功能。
那么客户端有哪些功能呢?
1、向server注册服务/取消服务
2、向server续约服务
3、向server查询服务列表
DicoveryClient初始化
先看一下DiscoveryClient属性:
@Singleton
public class DiscoveryClient implements EurekaClient {
private static final Logger logger = LoggerFactory.getLogger(com.netflix.discovery.DiscoveryClient.class);
public static final String HTTP_X_DISCOVERY_ALLOW_REDIRECT = "X-Discovery-AllowRedirect";
private static final String VALUE_DELIMITER = ",";
private static final String COMMA_STRING = ",";
/**
* @deprecated
*/
@Deprecated
private static EurekaClientConfig staticClientConfig;
private static final String PREFIX = "DiscoveryClient_";
private final Counter RECONCILE_HASH_CODES_MISMATCH;
private final Timer FETCH_REGISTRY_TIMER;
private final Counter REREGISTER_COUNTER;
private final ScheduledExecutorService scheduler;
//用于心跳续约任务的线程池
private final ThreadPoolExecutor heartbeatExecutor;
//用于获取服务列表任务的线程池
private final ThreadPoolExecutor cacheRefreshExecutor;
private final Provider<HealthCheckHandler> healthCheckHandlerProvider;
private final Provider<HealthCheckCallback> healthCheckCallbackProvider;
private final AtomicReference<Applications> localRegionApps;
private final Lock fetchRegistryUpdateLock;
private final AtomicLong fetchRegistryGeneration;
private final ApplicationInfoManager applicationInfoManager;
private final InstanceInfo instanceInfo;
private final AtomicReference<String> remoteRegionsToFetch;
private final AtomicReference<String[]> remoteRegionsRef;
private final InstanceRegionChecker instanceRegionChecker;
private final EndpointUtils.ServiceUrlRandomizer urlRandomizer;
private final Provider<BackupRegistry> backupRegistryProvider;
//负责发起远程调用
private final com.netflix.discovery.DiscoveryClient.EurekaTransport eurekaTransport;
private volatile HealthCheckHandler healthCheckHandler;
private volatile Map<String, Applications> remoteRegionVsApps;
private volatile InstanceInfo.InstanceStatus lastRemoteInstanceStatus;
private final CopyOnWriteArraySet<EurekaEventListener> eventListeners;
private String appPathIdentifier;
private ApplicationInfoManager.StatusChangeListener statusChangeListener;
private InstanceInfoReplicator instanceInfoReplicator;
private volatile int registrySize;
private volatile long lastSuccessfulRegistryFetchTimestamp;
private volatile long lastSuccessfulHeartbeatTimestamp;
private final ThresholdLevelsMetric heartbeatStalenessMonitor;
private final ThresholdLevelsMetric registryStalenessMonitor;
private final AtomicBoolean isShutdown;
protected final EurekaClientConfig clientConfig;
protected final EurekaTransportConfig transportConfig;
private final long initTimestampMs;
}
DiscoveryClient构造器:
@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager,
EurekaClientConfig config,
AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider) {
// 创建各种Executor 和 eurekaTransport、instanceRegionChecker
try {
// 执行定时任务的定时器,定时线程名为 DiscoveryClient-%d
// 在定时器中用于定时执行TimedSupervisorTask监督任务,监督任务会强制超时 和 记录监控数据
scheduler = Executors.newScheduledThreadPool(3,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
// 执行heartbeat心跳任务的执行器,默认最大线程数=2,线程名为:DiscoveryClient-HeartbeatExecutor-%d
heartbeatExecutor = new ThreadPoolExecutor(
1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build()
);
// 执行服务列表缓存刷新的执行器,默认最大线程数=2,线程名为:DiscoveryClient-CacheRefreshExecutor-%d
cacheRefreshExecutor = new ThreadPoolExecutor(
1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
.setDaemon(true)
.build()
);
eurekaTransport = new EurekaTransport();
// 初始化eurekaTransport在服务注册,获取服务列表时的client
scheduleServerEndpointTask(eurekaTransport, args);
instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());
} catch (Throwable e) {
throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
}
// 如果需要从eureka server获取服务列表,并且尝试fetchRegistry(false)失败,调用BackupRegistry
if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
fetchRegistryFromBackup();
}
// 【重点】初始化所有定时任务
initScheduledTasks();
}
private void initScheduledTasks() {
// 1、如果要从Eureka Server获取服务列表
if (clientConfig.shouldFetchRegistry()) {
// 从eureka服务器获取注册表信息的频率(默认30s)
// 同时也是单次获取服务列表的超时时间
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
// 如果缓存刷新超时,下一次执行的delay最大是registryFetchIntervalSeconds的几倍(默认10),
// 默认每次执行是上一次的2倍
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
/**
* 【#### 执行CacheRefreshThread,服务列表缓存刷新任务 ####】
* 执行TimedSupervisorTask监督任务的定时器,具体执行器为cacheRefreshExecutor,任务为CacheRefreshThread
*/
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh", //监控名
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds, //指定具体任务的超时时间
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
// 2、如果要注册到Eureka Server
if (clientConfig.shouldRegisterWithEureka()) {
// 续租的时间间隔(默认30s)
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
// 如果心跳任务超时,下一次执行的delay最大是renewalIntervalInSecs的几倍(默认10),默认每次执行是上一次的2倍
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
// Heartbeat timer
/**
* 【#### 执行HeartbeatThread,发送心跳数据 ####】
* 执行TimedSupervisorTask监督任务的定时器,具体执行器为heartbeatExecutor,任务为HeartbeatThread
*/
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
// InstanceInfo replicator
/**
* 【#### InstanceInfo复制器 ####】
* 启动后台定时任务scheduler,线程名为 DiscoveryClient-InstanceInfoReplicator-%d
* 默认每30s执行一次定时任务,查看Instance信息(DataCenterInfo、LeaseInfo、InstanceStatus)是否有变化
* 如果有变化,执行 discoveryClient.register()
*/
instanceInfoReplicator = new InstanceInfoReplicator(
this, //当前DiscoveryClient
instanceInfo, //当前实例信息
clientConfig.getInstanceInfoReplicationIntervalSeconds(),//InstanceInfo的复制间隔(默认30s)
2); // burstSize
/**
* 【StatusChangeListener 状态改变监听器】
*/
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);
}
//使用InstanceInfo复制器 scheduler.submit()一个Runnable任务
//后台马上执行 discoveryClient.register()
instanceInfoReplicator.onDemandUpdate();
}
};
/**
* 是否关注Instance状态变化,使用后台线程将状态同步到eureka server(默认true)
* 调用 ApplicationInfoManager#setInstanceStatus(status) 会触发
* 将 StatusChangeListener 注册到 ApplicationInfoManager
*/
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
// 启动InstanceInfo复制器
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
}
// 当前服务实例不注册到Eureka Server
else {
logger.info("Not registering with Eureka server per configuration");
}
}
- 客户端的几种功能都是定时触发的,所以在构造器中初始化了三种定时任务
- InstanceInfoReplicator:更新并复制本地实例状态到Server端 (注册/取消服务)
- TimedSupervisorTask -- heartbeat:向server发送心跳续约 (续约)
- TimedSupervisorTask -- cacheRefresh:刷新服务列表 (获取服务列表)
- 初始化状态改变监听器:当InstanceInfo改变时会监听,
- 初始化EurekaTransport。这个是向server发起远程调用的辅助类
更新本地服务到server
有两种场景会发起注册:
- 当应用启动的时候,如果应用开启了自动注册(默认开启), 那么在自动配置类加载的时候,会通过EurekaAutoServiceRegistration实例化的时候,去改变instance的status, 最终被监听器监听到,执行服务注册的代码
- 主要应用于启动之后,当应用的信息发生改变之后,每40每秒执行一次的线程,检测到了,也会自动去注册一次。
第一种场景代码省略,看下第二种场景的定时任务
// InstanceInfoReplicator#run()
public void run() {
try {
/**
* 刷新 InstanceInfo
* 1、刷新 DataCenterInfo
* 2、刷新 LeaseInfo 租约信息
* 3、根据HealthCheckHandler获取InstanceStatus,并更新,如果状态发生变化会触发所有StatusChangeListener
*/
discoveryClient.refreshInstanceInfo();
// 如果isInstanceInfoDirty=true,返回dirtyTimestamp,否则是null
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
discoveryClient.register(); //发起注册
instanceInfo.unsetIsDirty(dirtyTimestamp); //isInstanceInfoDirty置为false
}
}
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()
boolean register() throws Throwable {
logger.info(PREFIX + appPathIdentifier + ": registering service...");
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e);
throw e;
}
if (logger.isInfoEnabled()) {
logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
}
return httpResponse.getStatusCode() == 204;
}
- 先刷新InstanceInfo,刷新后如果发现有脏数据,即实例发生了变更,还未同步给Server的数据,就通过register方法发起注册
- 注册是通过eurekaTransport实现,eurekaTransport是在eureka中专门负责远程请求的。
先看下EurekaTransport什么样:
//DiscoveryClient.EurekaTransport
private static final class EurekaTransport {
private ClosableResolver bootstrapResolver;
//负责传输消息的客户端工厂(底层用于和Server交互的http框架是 Jersey,此处的工厂就和Jersey相关)
private TransportClientFactory transportClientFactory;
//负责注册、续约相关
private EurekaHttpClient registrationClient;
private EurekaHttpClientFactory registrationClientFactory;
//负责获取Server端服务列表
private EurekaHttpClient queryClient;
private EurekaHttpClientFactory queryClientFactory;
}
1、EurekaTransport是DiscoveryClient的内部类
2、内部包含以下属性:
TransportClientFactory:负责传输消息的客户端工厂(底层用于和Server交互的http框架是Jersey)
registrationClient:负责注册、续约相关
queryClient:负责获取Server端服务列表
3、在DiscoveryClient的构造器中对EurekaTransport进行的初始化,初始化大约实现了下面这个逻辑:
采用工厂模式+代理模式实现了对其所有实现类的逐层调用,由外到内大致如下:
SessionedEurekaHttpClient: 强制在一定时间间隔后重连EurekaHttpClient,防止永远只连接特定Eureka Server,反过来保证了在Server端集群拓扑发生变化时的负载重分配
RetryableEurekaHttpClient: 带有重试功能,默认最多3次,在配置的所有候选Server地址中尝试请求,成功重用,失败会重试另一Server,并维护隔离清单,下次跳过,当隔离数量达到阈值,清空隔离清单,重新开始
RedirectingEurekaHttpClient: Server端返回302重定向时,客户端shutdown原EurekaHttpClient,根据response header中的Location新建EurekaHttpClient
MetricsCollectingEurekaHttpClient: 统计收集Metrics信息
JerseyApplicationClient: AbstractJerseyEurekaHttpClient的子类
AbstractJerseyEurekaHttpClient: 底层实现通过Jersery注册、发心跳等的核心类
jerseyClient: Jersery客户端
也就是发起一次调用,要经过上边这些所有的实现类。
心跳续约
30秒一次
private class HeartbeatThread implements Runnable {
public void run() {
if (renew()) {
// 更新最后一次心跳的时间
lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
// 续约的主方法
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
if (httpResponse.getStatusCode() == 404) {
REREGISTER_COUNTER.increment();
logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName());
return register();
}
return httpResponse.getStatusCode() == 200;
} catch (Throwable e) {
logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e);
return false;
}
}
- 发送数据格式:apps/ + appName + /' + id
获取注册信息
class CacheRefreshThread implements Runnable {
public void run() {
// 刷新注册信息
refreshRegistry();
}
}
void refreshRegistry() {
try {
boolean isFetchingRemoteRegionRegistries = isFetchingRemoteRegionRegistries();
boolean remoteRegionsModified = false;
// 判断是否需要全量获取 , remoteRegionsModified 这个值来决定
String latestRemoteRegions = clientConfig.fetchRegistryForRemoteRegions();
if (null != latestRemoteRegions) {
String currentRemoteRegions = remoteRegionsToFetch.get();
if (!latestRemoteRegions.equals(currentRemoteRegions)) {
// Both remoteRegionsToFetch and AzToRegionMapper.regionsToFetch need to be in sync
synchronized (instanceRegionChecker.getAzToRegionMapper()) {
if (remoteRegionsToFetch.compareAndSet(currentRemoteRegions, latestRemoteRegions)) {
String[] remoteRegions = latestRemoteRegions.split(",");
remoteRegionsRef.set(remoteRegions);
instanceRegionChecker.getAzToRegionMapper().setRegionsToFetch(remoteRegions);
remoteRegionsModified = true;
} else {
logger.info("Remote regions to fetch modified concurrently," +
" ignoring change from {} to {}", currentRemoteRegions, latestRemoteRegions);
}
}
} else {
// Just refresh mapping to reflect any DNS/Property change
instanceRegionChecker.getAzToRegionMapper().refreshMapping();
}
}
// 获取注册信息
boolean success = fetchRegistry(remoteRegionsModified);
if (success) {
registrySize = localRegionApps.get().size();
lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis();
}
// 日志输出 , 省略。。
} catch (Throwable e) {
logger.error("Cannot fetch registry from server", e);
}
}
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
try {
// 获取本地的缓存信息 , 也就是客户端注册信息
Applications applications = getApplications();
// 判断是否需要全量获取
if (clientConfig.shouldDisableDelta()
|| (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
|| forceFullRegistryFetch
|| (applications == null)
|| (applications.getRegisteredApplications().size() == 0)
|| (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
{
logger.info("Disable delta property : {}", clientConfig.shouldDisableDelta());
logger.info("Single vip registry refresh property : {}", clientConfig.getRegistryRefreshSingleVipAddress());
logger.info("Force full registry fetch : {}", forceFullRegistryFetch);
logger.info("Application is null : {}", (applications == null));
logger.info("Registered Applications size is zero : {}",
(applications.getRegisteredApplications().size() == 0));
logger.info("Application version is -1: {}", (applications.getVersion() == -1));
getAndStoreFullRegistry();//入口1
} else {
// 增量获取
getAndUpdateDelta(applications);//入口2
}
applications.setAppsHashCode(applications.getReconcileHashCode());
logTotalInstances();
} catch (Throwable e) {
logger.error(PREFIX + appPathIdentifier + " - was unable to refresh its cache! status = " + e.getMessage(), e);
return false;
} finally {
if (tracer != null) {
tracer.stop();
}
}
// 发布缓存刷新事件。
onCacheRefreshed();
// 更新本地应用的状态
updateInstanceRemoteStatus();
// registry was fetched successfully, so return true
return true;
}
private void getAndStoreFullRegistry() throws Throwable {
long currentUpdateGeneration = fetchRegistryGeneration.get();
logger.info("Getting all instance registry info from the eureka server");
Applications apps = null;
// 发送HTTP请求,去服务端获取注册信息
EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null
? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())
: eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get());
if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
apps = httpResponse.getEntity();
}
logger.info("The response status is {}", httpResponse.getStatusCode());
if (apps == null) {
logger.error("The application is null for some reason. Not storing this information");
} else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
// 设置到本地缓存里面去
localRegionApps.set(this.filterAndShuffle(apps));
logger.debug("Got full registry with apps hashcode {}", apps.getAppsHashCode());
} else {
logger.warn("Not updating applications as another thread is updating it already");
}
}
private void getAndUpdateDelta(Applications applications) throws Throwable {
long currentUpdateGeneration = fetchRegistryGeneration.get();
Applications delta = null;
// 增量获取信息
EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
delta = httpResponse.getEntity();
}
if (delta == null) {
// 增量获取为空,则全量返回
logger.warn("The server does not allow the delta revision to be applied because it is not safe. "
+ "Hence got the full registry.");
getAndStoreFullRegistry();
} else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode());
String reconcileHashCode = "";
//这里设置原子锁的原因是怕某次调度网络请求时间过长,导致同一时间有多线程拉取到增量信息并发修改
if (fetchRegistryUpdateLock.tryLock()) {
try {
// 将获取到的增量信息和本地缓存信息合并。
updateDelta(delta);
reconcileHashCode = getReconcileHashCode(applications);
} finally {
// 释放锁
fetchRegistryUpdateLock.unlock();
}
} else {
logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta");
}
// ( HashCode 不一致|| 打印增量和全量的差异 )= true 重新去全量获取
if (!reconcileHashCode.equals(delta.getAppsHashCode()) || clientConfig.shouldLogDeltaDiff()) {
reconcileAndLogDifference(delta, reconcileHashCode); // this makes a remoteCall
}
} else {
logger.warn("Not updating application delta as another thread is updating it already");
logger.debug("Ignoring delta update with apps hashcode {}, as another thread is updating it already", delta.getAppsHashCode());
}
}
private void updateDelta(Applications delta) {
int deltaCount = 0;
// 循环拉取过来的应用列表
for (Application app : delta.getRegisteredApplications()) {
// 循环这个应用里面的实例(有多个实例代表是集群的。)
for (InstanceInfo instance : app.getInstances()) {
// 获取本地的注册应用列表
Applications applications = getApplications();
String instanceRegion = instanceRegionChecker.getInstanceRegion(instance);
if (!instanceRegionChecker.isLocalRegion(instanceRegion)) {
Applications remoteApps = remoteRegionVsApps.get(instanceRegion);
if (null == remoteApps) {
remoteApps = new Applications();
remoteRegionVsApps.put(instanceRegion, remoteApps);
}
applications = remoteApps;
}
++deltaCount;
if (ActionType.ADDED.equals(instance.getActionType())) {// 添加事件
//根据AppName 获取本地的数据,看这个应用是否存在
Application existingApp = applications.getRegisteredApplications(instance.getAppName());
if (existingApp == null) {
// 不存在,则加到本地的应用里面去
applications.addApplication(app);
}
logger.debug("Added instance {} to the existing apps in region {}", instance.getId(), instanceRegion);
// 为本地这个应用添加这个实例
applications.getRegisteredApplications(instance.getAppName()).addInstance(instance);
} else if (ActionType.MODIFIED.equals(instance.getActionType())) { // 修改事件
//根据AppName 获取本地的数据,看这个应用是否存在
Application existingApp = applications.getRegisteredApplications(instance.getAppName());
if (existingApp == null) {
// 不存在,则加到本地的应用里面去
applications.addApplication(app);
}
logger.debug("Modified instance {} to the existing apps ", instance.getId());
// 为本地这个应用添加这个实例
applications.getRegisteredApplications(instance.getAppName()).addInstance(instance);
} else if (ActionType.DELETED.equals(instance.getActionType())) { // 删除事件
Application existingApp = applications.getRegisteredApplications(instance.getAppName());
if (existingApp == null) {
applications.addApplication(app);
}
logger.debug("Deleted instance {} to the existing apps ", instance.getId());
// 移除这个实例
applications.getRegisteredApplications(instance.getAppName()).removeInstance(instance);
}
}
}
logger.debug("The total number of instances fetched by the delta processor : {}", deltaCount);
getApplications().setVersion(delta.getVersion());
getApplications().shuffleInstances(clientConfig.shouldFilterOnlyUpInstances());
for (Applications applications : remoteRegionVsApps.values()) {
applications.setVersion(delta.getVersion());
applications.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances());
}
}
- 定时任务30秒执行一次
- 判断是否需要全量获取,根据配置判断,根据本地缓存是否为空判断
- 先获取本地缓存的实例信息
- 全量获取
- 发送HTTP请求,去服务端获取注册信息
- 设置到本地缓存里面
- 增量获取
- 先增量获取,如果没获取到,在全量获取
- 将请求过来的增量数据和本地的数据做合并
- 从服务端获取了最近这段时间,新注册新来的客户端信息,有过修改的,被删除的, 这三大类的实例信息然后通过覆盖本地的数据,移除数据,来达到数据合并的需求
- 发布缓存刷新事件
- 更新本地应用的状态
Server
注解@EnableEurekaServer引出自动配置类EurekaServerAutoConfiguration
开启注解@EnableEurekaServer,和客户端作用是一样的,都是通过Marker类开启自动配置类EurekaServerAutoConfiguration。
接下来看下EurekaServerAutoConfiguration
@SpringBootApplication
public class Ads2Application {
public static void main(String[] args) {
SpringApplication.run(Ads2Application.class,args);
}
}
@Configuration
//Eureka Server初始化的配置类
//入口1
@Import({EurekaServerInitializerConfiguration.class})
@ConditionalOnBean({EurekaServerMarkerConfiguration.Marker.class})//Marker类
//实例注册相关属性和仪表盘相关属性
@EnableConfigurationProperties({EurekaDashboardProperties.class, InstanceRegistryProperties.class})
@PropertySource({"classpath:/eureka/server.properties"})
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
private static String[] EUREKA_PACKAGES = new String[]{"com.netflix.discovery", "com.netflix.eureka"};
@Autowired
private ApplicationInfoManager applicationInfoManager;
@Autowired
private EurekaServerConfig eurekaServerConfig;
@Autowired
private EurekaClientConfig eurekaClientConfig;
@Autowired
private EurekaClient eurekaClient;
@Autowired
private InstanceRegistryProperties instanceRegistryProperties;
//在注册实例时会考虑集群情况下其它Node相关操作的注册器
@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(ServerCodecs serverCodecs) {
this.eurekaClient.getApplications();
return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.eurekaClient, this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(), this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
}
@Bean
@ConditionalOnMissingBean
//用来管理PeerEurekaNode的帮助类
public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry, ServerCodecs serverCodecs) {
return new PeerEurekaNodes(registry, this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);
}
@Bean
//入口2
//Eureka Server上下文初始化
//因为netflix设计的EurekaServerContext接口本身包含很多成员变量,
// 如PeerEurekaNodes管理对等节点、PeerAwareInstanceRegistry考虑对等节点的实例注册器等,
// 在Eureka Server上下文初始化时会对这些组件初始化,还会启动一些定时线程
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs, PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs, registry, peerEurekaNodes, this.applicationInfoManager);
}
@Bean
//Eureka Server启动引导,会在Spring容器refresh()完毕时由EurekaServerInitializerConfiguration#run()
// 方法真正调用eurekaServerBootstrap.contextInitialized()初始化,其中initEurekaEnvironment()、
// initEurekaServerContext() Eureka Server启动分析重点
//入口
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry, EurekaServerContext serverContext) {
return new EurekaServerBootstrap(this.applicationInfoManager, this.eurekaClientConfig, this.eurekaServerConfig, registry, serverContext);
}
@Bean
//注册 Jersey filter
//所有/eureka的请求都需要经过Jersery Filter,其处理类是com.sun.jersey.spi.container.servlet.ServletContainer,
// 其既是Filter,也是Servlet,包含Jersey的处理逻辑。
public FilterRegistrationBean jerseyFilterRegistration(Application eurekaJerseyApp) {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new ServletContainer(eurekaJerseyApp));
bean.setOrder(2147483647);
bean.setUrlPatterns(Collections.singletonList("/eureka/*"));
return bean;
}
@Bean
public Application jerseyApplication(Environment environment, ResourceLoader resourceLoader) {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false, environment);
provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
Set<Class<?>> classes = new HashSet();
String[] var5 = EUREKA_PACKAGES;
int var6 = var5.length;
for(int var7 = 0; var7 < var6; ++var7) {
String basePackage = var5[var7];
Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
Iterator var10 = beans.iterator();
while(var10.hasNext()) {
BeanDefinition bd = (BeanDefinition)var10.next();
Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(), resourceLoader.getClassLoader());
classes.add(cls);
}
}
Map<String, Object> propsAndFeatures = new HashMap();
propsAndFeatures.put("com.sun.jersey.config.property.WebPageContentRegex", "/eureka/(fonts|images|css|js)/.*");
DefaultResourceConfig rc = new DefaultResourceConfig(classes);
rc.setPropertiesAndFeatures(propsAndFeatures);
return rc;
}
@Configuration
protected static class EurekaServerConfigBeanConfiguration {
protected EurekaServerConfigBeanConfiguration() {
}
@Bean
@ConditionalOnMissingBean
//注入Eureka Server配置类,
// netflix的默认实现类是DefaultEurekaServerConfig,spring cloud的默认实现类是EurekaServerConfigBean
public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {
EurekaServerConfigBean server = new EurekaServerConfigBean();
if (clientConfig.shouldRegisterWithEureka()) {
server.setRegistrySyncRetries(5);
}
return server;
}
}
}
- 初始化DefaultEurekaServerContext,即Eureka Server上下文
- 初始化EurekaServerBootstrap
接下来分别看一下这两个类的初始化过程
初始化DefaultEurekaServerContext
public interface EurekaServerContext {
void initialize() throws Exception;
void shutdown() throws Exception;
EurekaServerConfig getServerConfig();
PeerEurekaNodes getPeerEurekaNodes();
ServerCodecs getServerCodecs();
PeerAwareInstanceRegistry getRegistry();
ApplicationInfoManager getApplicationInfoManager();
}
//DefaultEurekaServerContext
@PostConstruct
@Override
public void initialize() throws Exception {
logger.info("Initializing ...");
peerEurekaNodes.start();//入口1
registry.init(peerEurekaNodes);//入口2
logger.info("Initialized");
}
public void start() {
// 后台运行的单线程定时任务执行器,定时线程名:Eureka-PeerNodesUpdater
taskExecutor = Executors.newSingleThreadScheduledExecutor(
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");
thread.setDaemon(true);
return thread;
}
}
);
try {
// 解析Eureka Server URL,并更新PeerEurekaNodes列表
updatePeerEurekaNodes(resolvePeerUrls());
// 启动定时执行任务peersUpdateTask(定时默认10min,由peerEurekaNodesUpdateIntervalMs配置)
Runnable peersUpdateTask = new Runnable() {
@Override
public void run() {
try {
// 定时任务中仍然是 解析Eureka Server URL,并更新PeerEurekaNodes列表
updatePeerEurekaNodes(resolvePeerUrls());//入口1+入口2
} catch (Throwable e) {
logger.error("Cannot update the replica Nodes", e);
}
}
};
taskExecutor.scheduleWithFixedDelay(
peersUpdateTask,
serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
TimeUnit.MILLISECONDS
);
} catch (Exception e) {
throw new IllegalStateException(e);
}
// 打印对等体节点(应该没有当前节点自己)
for (PeerEurekaNode node : peerEurekaNodes) {
logger.info("Replica node URL: " + node.getServiceUrl());
}
}
protected List<String> resolvePeerUrls() {
// 当前Eureka Server自己的InstanceInfo信息
InstanceInfo myInfo = applicationInfoManager.getInfo();
// 当前Eureka Server所在的zone,默认是 defaultZone
String zone = InstanceInfo.getZone(clientConfig.getAvailabilityZones(clientConfig.getRegion()), myInfo);
// 获取配置的service-url
List<String> replicaUrls = EndpointUtils
.getDiscoveryServiceUrls(clientConfig, zone, new EndpointUtils.InstanceInfoBasedUrlRandomizer(myInfo));
// 遍历service-url,排除自己
int idx = 0;
while (idx < replicaUrls.size()) {
if (isThisMyUrl(replicaUrls.get(idx))) {//入口
replicaUrls.remove(idx);
} else {
idx++;
}
}
return replicaUrls;
}
public boolean isThisMyUrl(String url) {
return isInstanceURL(url, applicationInfoManager.getInfo());
}
public boolean isInstanceURL(String url, InstanceInfo instance) {
// 根据配置项的url获取host主机信息
String hostName = hostFromUrl(url);
// 根据当前Eureka Server的Instance实例信息获取host主机信息
String myInfoComparator = instance.getHostName();
// 如果eureka.client.transport.applicationsResolverUseIp==true,即按照IP解析URL
// 那么将当前Eureka Server的Instance实例信息转换为IP
if (clientConfig.getTransportConfig().applicationsResolverUseIp()) {
myInfoComparator = instance.getIPAddr();
}
// 比较配置项的hostName 和 当前Eureka Server的Instance实例信息
return hostName != null && hostName.equals(myInfoComparator);
}
// PeerEurekaNodes#updatePeerEurekaNodes()
// newPeerUrls为本次要更新的Eureka对等体URL列表
protected void updatePeerEurekaNodes(List<String> newPeerUrls) {
if (newPeerUrls.isEmpty()) {
logger.warn("The replica size seems to be empty. Check the route 53 DNS Registry");
return;
}
// 计算 原peerEurekaNodeUrls - 新newPeerUrls 的差集,就是多余可shutdown节点
Set<String> toShutdown = new HashSet<>(peerEurekaNodeUrls);
toShutdown.removeAll(newPeerUrls);
// 计算 新newPeerUrls - 原peerEurekaNodeUrls 的差集,就是需要新增节点
Set<String> toAdd = new HashSet<>(newPeerUrls);
toAdd.removeAll(peerEurekaNodeUrls);
if (toShutdown.isEmpty() && toAdd.isEmpty()) { // No change 没有变更
return;
}
// Remove peers no long available
List<PeerEurekaNode> newNodeList = new ArrayList<>(peerEurekaNodes);
// shutDown多余节点
if (!toShutdown.isEmpty()) {
logger.info("Removing no longer available peer nodes {}", toShutdown);
int i = 0;
while (i < newNodeList.size()) {
PeerEurekaNode eurekaNode = newNodeList.get(i);
if (toShutdown.contains(eurekaNode.getServiceUrl())) {
newNodeList.remove(i);
eurekaNode.shutDown();
} else {
i++;
}
}
}
// Add new peers
// 添加新的peerEurekaNode - createPeerEurekaNode()
if (!toAdd.isEmpty()) {
logger.info("Adding new peer nodes {}", toAdd);
for (String peerUrl : toAdd) {
newNodeList.add(createPeerEurekaNode(peerUrl));
}
}
this.peerEurekaNodes = newNodeList;
this.peerEurekaNodeUrls = new HashSet<>(newPeerUrls);
}
// PeerAwareInstanceRegistryImpl#init()
@Override
public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
// 【重要】启动用于统计最后xx毫秒续约情况的定时线程
this.numberOfReplicationsLastMin.start();
this.peerEurekaNodes = peerEurekaNodes;
// 【重要】初始化ResponseCache: 对客户端查询服务列表信息的缓存(所有服务列表、增量修改、单个应用)
// 默认responseCacheUpdateIntervalMs=30s
initializedResponseCache();
// 【重要】定期更新续约阀值的任务,默认900s执行一次
// 调用 PeerAwareInstanceRegistryImpl#updateRenewalThreshold()
scheduleRenewalThresholdUpdateTask();
// 初始化 远程区域注册 相关信息(默认没有远程Region,都是使用us-east-1)
initRemoteRegionRegistry();
try {
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register the JMX monitor for the InstanceRegistry :", e);
}
}
// MeasuredRate#start()
public synchronized void start() {
if (!isActive) {
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
// Zero out the current bucket.
// 将当前的桶的统计数据放到lastBucket,当前桶置为0
lastBucket.set(currentBucket.getAndSet(0));
} catch (Throwable e) {
logger.error("Cannot reset the Measured Rate", e);
}
}
}, sampleInterval, sampleInterval);
isActive = true;
}
}
/**
* Returns the count in the last sample interval.
* 返回上一分钟的统计数
*/
public long getCount() {
return lastBucket.get();
}
/**
* Increments the count in the current sample interval.
* 增加当前桶的计数,在以下2个场景有调用:
* AbstractInstanceRegistry#renew() - 续约
* PeerAwareInstanceRegistryImpl#replicateToPeers() -
*/
public void increment() {
currentBucket.incrementAndGet();
}
// ResponseCacheImpl构造
private final ConcurrentMap<Key, Value> readOnlyCacheMap = new ConcurrentHashMap<Key, Value>();
private final LoadingCache<Key, Value> readWriteCacheMap;
ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {
this.serverConfig = serverConfig;
this.serverCodecs = serverCodecs;
// 根据配置eureka.server.useReadOnlyResponseCache判断,是否使用只读ResponseCache,默认true
// 由于ResponseCache维护这一个可读可写的readWriteCacheMap,还有一个只读的readOnlyCacheMap
// 此配置控制在get()应用数据时,是去只读Map读,还是读写Map读,应该只读Map是定期更新的
this.shouldUseReadOnlyResponseCache = serverConfig.shouldUseReadOnlyResponseCache();
this.registry = registry;
// eureka.server.responseCacheUpdateIntervalMs缓存更新频率,默认30s
long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();
// 创建读写Map,com.google.common.cache.LoadingCache
// 可以设置初始值,数据写入过期时间,删除监听器等
this.readWriteCacheMap =
CacheBuilder.newBuilder().initialCapacity(1000)
.expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS)
.removalListener(new RemovalListener<Key, Value>() {
@Override
public void onRemoval(RemovalNotification<Key, Value> notification) {
Key removedKey = notification.getKey();
if (removedKey.hasRegions()) {
Key cloneWithNoRegions = removedKey.cloneWithoutRegions();
regionSpecificKeys.remove(cloneWithNoRegions, removedKey);
}
}
})
.build(new CacheLoader<Key, Value>() {
@Override
public Value load(Key key) throws Exception {
if (key.hasRegions()) {
Key cloneWithNoRegions = key.cloneWithoutRegions();
regionSpecificKeys.put(cloneWithNoRegions, key);
}
Value value = generatePayload(key);
return value;
}
});
// 如果启用只读缓存,那么每隔responseCacheUpdateIntervalMs=30s,执行getCacheUpdateTask()
if (shouldUseReadOnlyResponseCache) {
timer.schedule(getCacheUpdateTask(),
new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
+ responseCacheUpdateIntervalMs),
responseCacheUpdateIntervalMs);
}
try {
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register the JMX monitor for the InstanceRegistry", e);
}
}
- 初始化对等节点信息,也就是更新集群节点信息。先执行一遍,然后在开启个定时任务定时执行(10分钟一次),具体如何执行:
- 获取server所在zone下所有配置的service-url
- 遍历将自己过滤掉,过滤规则
- 如果eureka.client.transport.applicationsResolverUseIp==true,即按照IP解析URL,那么将当前server的host转换成ip,然后挨个比较
- 如果没配置则按照hostName进行比较
- 根据上面获取到的最新的server列表,和server就的server列表进行差集计算,多余的就是需要添加的。反过来差集则是需要剔除掉的
- 启动用于统计最后xx毫秒续约情况的定时线程
- 初始化ResponseCache
- ResponseCache是对客户端查询服务列表信息的缓存
- 默认responseCacheUpdateIntervalMs=30s,默认30s更新一次
- 定期更新续约阀值的任务,默认900s执行一次
- 初始化远程区域注册相关信息(默认没有远程Region,都是使用us-east-1)
EurekaServerBootstrap初始化
在spring容器初始化完毕后调用
// EurekaServerBootstrap#contextInitialized()
public void contextInitialized(ServletContext context) {
try {
initEurekaEnvironment();
initEurekaServerContext(); //入口
context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
}
catch (Throwable e) {
log.error("Cannot bootstrap eureka server :", e);
throw new RuntimeException("Cannot bootstrap eureka server :", e);
}
}
// EurekaServerBootstrap#initEurekaServerContext()
protected void initEurekaServerContext() throws Exception {
// For backward compatibility
JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
XStream.PRIORITY_VERY_HIGH);
XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
XStream.PRIORITY_VERY_HIGH);
// 是否为AWS环境
if (isAws(this.applicationInfoManager.getInfo())) {
this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig,
this.eurekaClientConfig, this.registry, this.applicationInfoManager);
this.awsBinder.start();
}
// 将serverContext由Holder保管
EurekaServerContextHolder.initialize(this.serverContext);
log.info("Initialized server context");
// Copy registry from neighboring eureka node
// 从相邻的eureka节点拷贝注册列表信息
int registryCount = this.registry.syncUp();//入口1
this.registry.openForTraffic(this.applicationInfoManager, registryCount);入口2
// Register all monitoring statistics.
EurekaMonitors.registerAllStats();
}
public int syncUp() {
// Copy entire entry from neighboring DS node
// 获取到的注册节点数量
int count = 0;
// 如果count==0 , 那么默认重试5次(前提是开启了register-with-eureka = true,否则为0)
for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
if (i > 0) {
try {
// 从第二次开始,每次默认沉睡30秒
Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
} catch (InterruptedException e) {
logger.warn("Interrupted during registry transfer..");
break;
}
}
// 从本地内存里面获取注册实例信息
Applications apps = eurekaClient.getApplications();
for (Application app : apps.getRegisteredApplications()) {
for (InstanceInfo instance : app.getInstances()) {
try {
// 判断是否可以注册
if (isRegisterable(instance)) {
// 注册到当前Eureka Server里面
register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
count++;
}
} catch (Throwable t) {
logger.error("During DS init copy", t);
}
}
}
}
return count;
}
// InstanceRegistry#openForTraffic()
@Override
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// 如果count==0,即没有从相邻eureka节点得到服务列表,如单机启动模式,defaultOpenForTrafficCount=1
super.openForTraffic(applicationInfoManager,
count == 0 ? this.defaultOpenForTrafficCount : count);
}
// PeerAwareInstanceRegistryImpl#openForTraffic()
@Override
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// Renewals happen every 30 seconds and for a minute it should be a factor of 2.
// 每分钟期待的续约数(默认30s续约,60s就是2次)
this.expectedNumberOfRenewsPerMin = count * 2;
// 每分钟续约的阀值:85% * expectedNumberOfRenewsPerMin
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
logger.info("Got " + count + " instances from neighboring DS node");
logger.info("Renew threshold is: " + numberOfRenewsPerMinThreshold);
this.startupTime = System.currentTimeMillis();
if (count > 0) { //可count默认值是1,那么peerInstancesTransferEmptyOnStartup始终不会是true
//在PeerAwareInstanceRegistryImpl#shouldAllowAccess(boolean)方法有用
this.peerInstancesTransferEmptyOnStartup = false;
}
DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
boolean isAws = Name.Amazon == selfName;
if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
logger.info("Priming AWS connections for all replicas..");
primeAwsReplicas(applicationInfoManager);
}
logger.info("Changing status to UP");
applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
// 开启新的【EvictionTask】
super.postInit();
}
protected void postInit() {
renewsLastMin.start(); //统计上一分钟续约数的监控Timer
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
evictionTaskRef.set(new EvictionTask());
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(), //默认60s
serverConfig.getEvictionIntervalTimerInMs());
}
这一步做的事情是集群启动同步
- 从相邻的eureka节点拷贝注册列表信息,循环,最多重试RegistrySyncRetries次(默认 5)
- openForTraffic:允许开始与客户端的数据传输,即开始作为Server服务
Server处理注册请求
服务端注册实现类有两种,单机和集群,直接看集群的。
客户端注册实例到server,首先就是进入下面这个方法
// PeerAwareInstanceRegistryImpl#register()
/**
* 注册有关InstanceInfo信息,并将此信息复制到所有对等的eureka节点
* 如果这是来自其他节点的复制事件,则不会继续复制它
*
* @param info
* the {@link InstanceInfo} to be registered and replicated.
* @param isReplication
* true if this is a replication event from other replica nodes,
* false otherwise.
*/
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
//租约的过期时间,默认90秒,也就是说当服务端超过90秒没有收到客户端的心跳,则主动剔除该节点
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
// 如果当前Instance实例的租约信息中有leaseDuration持续时间,使用实例的leaseDuration(也就是以客户端为准)
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
// 【 当前Eureka Server注册实例信息 】
super.register(info, leaseDuration, isReplication);
// 【 将注册实例信息复制到集群中其它节点 】
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
- 将服务实例注册到当前server
- 将服务实例同步到其他集群节点
将服务实例注册到自身
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
read.lock(); //读锁
// registry是保存所有应用实例信息的Map:ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>
// 从registry中获取当前appName的所有实例信息
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication); //注册统计+1
// 如果当前appName实例信息为空,新建Map
if (gMap == null) {
final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
gMap = gNewMap;
}
}
// 获取实例的Lease租约信息
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
// Retain the last dirty timestamp without overwriting it, if there is already a lease
// 如果已经有租约,则保留最后一个脏时间戳而不覆盖它
// (比较当前请求实例租约 和 已有租约 的LastDirtyTimestamp,选择靠后的)
if (existingLease != null && (existingLease.getHolder() != null)) {
Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
" than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
registrant = existingLease.getHolder();
}
}
else {
// The lease does not exist and hence it is a new registration
// 如果之前不存在实例的租约,说明是新实例注册
// expectedNumberOfRenewsPerMin期待的每分钟续约数+2(因为30s一个)
// 并更新numberOfRenewsPerMinThreshold每分钟续约阀值(85%)
synchronized (lock) {
if (this.expectedNumberOfRenewsPerMin > 0) {
// Since the client wants to cancel it, reduce the threshold
// (1
// for 30 seconds, 2 for a minute)
this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
}
}
logger.debug("No previous lease information found; it is new registration");
}
Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
if (existingLease != null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
gMap.put(registrant.getId(), lease); //当前实例信息放到维护注册信息的Map
// 同步维护最近注册队列
synchronized (recentRegisteredQueue) {
recentRegisteredQueue.add(new Pair<Long, String>(
System.currentTimeMillis(),
registrant.getAppName() + "(" + registrant.getId() + ")"));
}
// This is where the initial state transfer of overridden status happens
// 如果当前实例已经维护了OverriddenStatus,将其也放到此Eureka Server的overriddenInstanceStatusMap中
if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "
+ "overrides", registrant.getOverriddenStatus(), registrant.getId());
if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
logger.info("Not found overridden id {} and hence adding it", registrant.getId());
overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
}
}
InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
if (overriddenStatusFromMap != null) {
logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
registrant.setOverriddenStatus(overriddenStatusFromMap);
}
// Set the status based on the overridden status rules
// 根据overridden status规则,设置状态
InstanceStatus overriddenInstanceStatus
= getOverriddenInstanceStatus(registrant, existingLease, isReplication);
registrant.setStatusWithoutDirty(overriddenInstanceStatus);
// If the lease is registered with UP status, set lease service up timestamp
// 如果租约以UP状态注册,设置租赁服务时间戳
if (InstanceStatus.UP.equals(registrant.getStatus())) {
lease.serviceUp();
}
registrant.setActionType(ActionType.ADDED); //ActionType为 ADD
recentlyChangedQueue.add(new RecentlyChangedItem(lease)); //维护recentlyChangedQueue
registrant.setLastUpdatedTimestamp(); //更新最后更新时间
// 使当前应用的ResponseCache失效
invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
logger.info("Registered instance {}/{} with status {} (replication={})",
registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
} finally {
read.unlock(); //读锁
}
}
- 维护当前Instance实例的Lease租约信息,并且更新注册实例到Map中。Server通过Map来维护注册信息。
- 如果是新注册(有可能是续约和注册),expectedNumberOfRenewsPerMin期待的每分钟续约数+2, 并更新numberOfRenewsPerMinThreshold每分钟续约阀值
- 介绍一下这两个参数的作用:用这两个参数可以实现enrueka的自我保护机制,开启自我保护机制,当发生网络抖动,client向服务端续约少于阀值,触发自我保护机制,server就不会剔除任何实例服务。
expectedNumberOfRenewsPerMin :每分钟最大的续约数量,由于客户端是每30秒续约一次,一分钟就是续约2次, count代表的是客户端数量。
计算出一个总数据: 客户端数量*2
numberOfRenewsPerMinThreshold : 每分钟最小续约数量, 使用expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold()。
serverConfig.getRenewalPercentThreshold()的默认值为0.85 , 也就是说每分钟的续约数量要大于85% 。
Eureka的自我保护机制,都是围绕这两个变量来实现的, 如果每分钟的续约数量小于numberOfRenewsPerMinThreshold , 就会开启自动保护机制。在此期间,不会再主动剔除任何一个客户端。
- 介绍一下这两个参数的作用:用这两个参数可以实现enrueka的自我保护机制,开启自我保护机制,当发生网络抖动,client向服务端续约少于阀值,触发自我保护机制,server就不会剔除任何实例服务。
- 更新ResponseCache。为了并发性能,增加了一个guava的二级缓存。
guava二级缓存:可以理解维护了两个Map,ReadOnlyMap和ReadWriteMap。
当有get请求的时候,先查ReadOnlyMap,没有在查ReadWriteMap,如果ReadWriteMap也没有,ReadWriteMap内部就会取存储注册信息的map去加载。
将服务实例同步到其他集群节点
// PeerAwareInstanceRegistryImpl#replicateToPeers()
/**
* Replicates all eureka actions to peer eureka nodes except for replication
* traffic to this node.
*/
private void replicateToPeers(Action action, String appName, String id,
InstanceInfo info /* optional */,
InstanceStatus newStatus /* optional */, boolean isReplication) {
Stopwatch tracer = action.getTimer().start();
try {
// 如果是复制操作(针对当前节点,false)
if (isReplication) {
numberOfReplicationsLastMin.increment();
}
// If it is a replication already, do not replicate again as this will create a poison replication
// 如果它已经是复制,请不要再次复制,直接return
if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
return;
}
// 遍历集群所有节点(除当前节点外)
for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
// If the url represents this host, do not replicate to yourself.
if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
continue;
}
// 复制Instance实例操作到某个node节点
replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
}
}
finally {
tracer.stop();
}
}
// PeerAwareInstanceRegistryImpl#replicateInstanceActionsToPeers()
/**
* Replicates all instance changes to peer eureka nodes except for
* replication traffic to this node.
*
*/
private void replicateInstanceActionsToPeers(Action action, String appName,
String id, InstanceInfo info, InstanceStatus newStatus,
PeerEurekaNode node) {
try {
InstanceInfo infoFromRegistry = null;
CurrentRequestVersion.set(Version.V2);
switch (action) {
case Cancel: //取消
node.cancel(appName, id);
break;
case Heartbeat: //心跳
InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
break;
case Register: //注册
node.register(info);//入口
break;
case StatusUpdate: //状态更新
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.statusUpdate(appName, id, newStatus, infoFromRegistry);
break;
case DeleteStatusOverride: //删除OverrideStatus
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.deleteStatusOverride(appName, id, infoFromRegistry);
break;
}
} catch (Throwable t) {
logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t);
}
}
// PeerEurekaNode#register()
/**
* Sends the registration information of {@link InstanceInfo} receiving by
* this node to the peer node represented by this class.
*
* @param info
* the instance information {@link InstanceInfo} of any instance
* that is send to this instance.
* @throws Exception
*/
public void register(final InstanceInfo info) throws Exception {
// 当前时间 + 30s后 过期
long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
// 提交相同的操作到批量复制任务处理
batchingDispatcher.process(
taskId("register", info),
new InstanceReplicationTask(targetHost, Action.Register, info, overriddenStatus:null, replicateInstanceInfo:true) {
public EurekaHttpResponse<Void> execute() {
return replicationClient.register(info);
}
},
expiryTime
);
}
- 其实和client注册是一样的,server收集自己的实例信息,然后作为一个client向集群其他server发起注册
server处理续约请求
//InstanceResource
@PUT
public Response renewLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
@QueryParam("overriddenstatus") String overriddenStatus,
@QueryParam("status") String status,
@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
boolean isFromReplicaNode = "true".equals(isReplication);
// 续约
boolean isSuccess = registry.renew(app.getName(), id, isFromReplicaNode);
// 续约失败
// Not found in the registry, immediately ask for a register
if (!isSuccess) {
logger.warn("Not Found (Renew): {} - {}", app.getName(), id);
return Response.status(Status.NOT_FOUND).build();
}
// Check if we need to sync based on dirty time stamp, the client
// instance might have changed some value
Response response = null;
// 比较lastDirtyTimestamp
if (lastDirtyTimestamp != null && serverConfig.shouldSyncWhenTimestampDiffers()) {
// 比较lastDirtyTimestamp的大小,这个还是比较重要的
response = this.validateDirtyTimestamp(Long.valueOf(lastDirtyTimestamp), isFromReplicaNode);
if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()
&& (overriddenStatus != null)
&& !(InstanceStatus.UNKNOWN.name().equals(overriddenStatus))
&& isFromReplicaNode) {
registry.storeOverriddenStatusIfRequired(app.getAppName(), id, InstanceStatus.valueOf(overriddenStatus));
}
} else {
response = Response.ok().build();
}
logger.debug("Found (Renew): {} - {}; reply status={}" + app.getName(), id, response.getStatus());
return response;
}
private Response validateDirtyTimestamp(Long lastDirtyTimestamp,
boolean isReplication) {
// 获取本机的instance实例信息
InstanceInfo appInfo = registry.getInstanceByAppAndId(app.getName(), id, false);
if (appInfo != null) {
//如果lastDirtyTimestamp不为空,并且lastDirtyTimestamp和本地的不相等
if ((lastDirtyTimestamp != null) && (!lastDirtyTimestamp.equals(appInfo.getLastDirtyTimestamp()))) {
Object[] args = {id, appInfo.getLastDirtyTimestamp(), lastDirtyTimestamp, isReplication};
// lastDirtyTimestamp>本地的时间,则认为当前实例是无效的,返回404错误,客户端重新发起注册
if (lastDirtyTimestamp > appInfo.getLastDirtyTimestamp()) {
logger.debug(
"Time to sync, since the last dirty timestamp differs -"
+ " ReplicationInstance id : {},Registry : {} Incoming: {} Replication: {}",
args);
return Response.status(Status.NOT_FOUND).build();
} else if (appInfo.getLastDirtyTimestamp() > lastDirtyTimestamp) {
// 如果是集群同步请求,本地的时间,大于客户端传过来的时间,则返回 “冲突” 这个状态回去,以本地的时间大的为准
if (isReplication) {
logger.debug(
"Time to sync, since the last dirty timestamp differs -"
+ " ReplicationInstance id : {},Registry : {} Incoming: {} Replication: {}",
args);
return Response.status(Status.CONFLICT).entity(appInfo).build();
} else {
return Response.ok().build();
}
}
}
}
return Response.ok().build();
}
//PeerAwareInstanceRegistryImpl.java
public boolean renew(final String appName, final String id, final boolean isReplication) {
// 执行续约操作
if (super.renew(appName, id, isReplication)) {
// 同步Eureka-Server集群
replicateToPeers(Action.Heartbeat, appName, id, null, null, isReplication);
return true;
}
return false;
}
//AbstractInstanceRegistry.java
public boolean renew(String appName, String id, boolean isReplication) {
// 增加续约次数到统计枚举
RENEW.increment(isReplication);
// 从Eureka-Server端本地的CurrentHashMap中,通过appName获取Lease信息
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToRenew = null;
if (gMap != null) {
leaseToRenew = gMap.get(id);
}
// lease为空,lease在第一次注册的时候会创建,为空,则表示从来没有注册过,租约不存在
if (leaseToRenew == null) {
RENEW_NOT_FOUND.increment(isReplication);
logger.warn("DS: Registry: lease doesn't exist, registering resource: {} - {}", appName, id);
return false;
} else {
// 获取lease里面的instance信息
InstanceInfo instanceInfo = leaseToRenew.getHolder();
if (instanceInfo != null) {
// touchASGCache(instanceInfo.getASGName());
// 一系列状态判断,目前还不是很清楚,但是不影响主流程
InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
instanceInfo, leaseToRenew, isReplication);
if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}"
+ "; re-register required", instanceInfo.getId());
RENEW_NOT_FOUND.increment(isReplication);
return false;
}
if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
Object[] args = {
instanceInfo.getStatus().name(),
instanceInfo.getOverriddenStatus().name(),
instanceInfo.getId()
};
logger.info(
"The instance status {} is different from overridden instance status {} for instance {}. "
+ "Hence setting the status to overridden status", args);
instanceInfo.setStatus(overriddenInstanceStatus);
}
}
// 设置每分钟的续约次数
renewsLastMin.increment();
// 续约
leaseToRenew.renew();
return true;
}
}
//Lease.java
public void renew() {
lastUpdateTimestamp = System.currentTimeMillis() + duration;
}
- 首先根据实例名称获取续约信息对象lease,如果lease为null,表示之前没注册过,直接返回false
- lease不为null,更新lease的最后更新时间戳,更新lease的每分钟续约次数。
- 对lastDirtyTimestamp进行验证
- lastDirtyTimestamp是客户端实例信息发生变化的时间,server收到实例变更时要比较当前时间不能小于lastDirtyTimestamp。
实例自动过期
当客户端心跳超时的时候,server会有个定时任务对该类的实例进行下线
protected void initEurekaServerContext() throws Exception {
// ....省略N多代码
// 服务刚刚启动的时候,去其他服务节点同步客户端的数量。
int registryCount = this.registry.syncUp();
// 这个方法里面计算expectedNumberOfRenewsPerMin的值 , 重点在这里面,这里启动了清理任务的定时器
this.registry.openForTraffic(this.applicationInfoManager, registryCount);
// Register all monitoring statistics.
EurekaMonitors.registerAllStats();
}
@Override
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// ...省略N多代码
// 开启定时清理过期客户端的定时器
super.postInit();
}
protected void postInit() {
renewsLastMin.start();//入口
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
evictionTaskRef.set(new EvictionTask());
// 设置定时器
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());//入口2
}
public synchronized void start() {
if (!isActive) {
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
// 进行清0
lastBucket.set(currentBucket.getAndSet(0));
} catch (Throwable e) {
logger.error("Cannot reset the Measured Rate", e);
}
}
}, sampleInterval, sampleInterval);
isActive = true;
}
}
//EvictionTask
class EvictionTask extends TimerTask {
private final AtomicLong lastExecutionNanosRef = new AtomicLong(0l);
@Override
public void run() {
try {
// 获取延迟秒数,就是延迟几秒下线
long compensationTimeMs = getCompensationTimeMs();
logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
evict(compensationTimeMs);
} catch (Throwable e) {
logger.error("Could not run the evict task", e);
}
}
}
public void evict(long additionalLeaseMs) {
logger.debug("Running the evict task");
// 判断是否开启自我保护机制
if (!isLeaseExpirationEnabled()) {
logger.debug("DS: lease expiration is currently disabled.");
return;
}
List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
// 循环遍历本地CurrentHashMap中的实例信息
for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
if (leaseMap != null) {
for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
Lease<InstanceInfo> lease = leaseEntry.getValue();
// 判断是否过期,此处为重点,里面有判断实例过期的依据
if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
expiredLeases.add(lease);
}
}
}
}
// 获取注册的实例数量
int registrySize = (int) getLocalRegistrySize();
// serverConfig.getRenewalPercentThreshold() 为0.85 , 主要是为了避免开启自动保护机制。 所以会逐步过期
int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
// 可以过期的数量
int evictionLimit = registrySize - registrySizeThreshold;
// 取最小值,在过期数量和可以过期的数量中间取最小值。
int toEvict = Math.min(expiredLeases.size(), evictionLimit);
if (toEvict > 0) {
logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);
// 随机过期
Random random = new Random(System.currentTimeMillis());
for (int i = 0; i < toEvict; i++) {
// Pick a random item (Knuth shuffle algorithm)
int next = i + random.nextInt(expiredLeases.size() - i);
Collections.swap(expiredLeases, i, next);
Lease<InstanceInfo> lease = expiredLeases.get(i);
String appName = lease.getHolder().getAppName();
String id = lease.getHolder().getId();
// 写入过期监控
EXPIRED.increment();
// 服务下线
logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
internalCancel(appName, id, false);
}
}
}
- server用lastBucket维护了客户端总续约次数,开启个定时任务,没分钟把这个值清0一次,通过这种方式实现滑动窗口
- 在开启个定时任务来清理过期实例的,serverConfig.getEvictionIntervalTimerInMs() : 默认为60秒 , 可配置
- 获取延迟下线
- 自我保护判断
- 获取map内的所有实例信息,循环进行判断是否过期
- 获取总实例数量、计算出可过期数量(总数量*0.85,即自我保护阀值)、过期数量,在后两个中取个最小值,目的是防止触发自我保护机制。
- 随机进行过期下线
随机下线+分批下线:一个实例有10台服务器,本次需要下线4台,因为自我保护机制,一次不能下线超过百分85,所以本次只能下线2台,所以本次在这4台中随机下线两台,剩下的在下个周期在继续。
接受获取注册信息请求
@GET
public Response getContainers(@PathParam("version") String version,
@HeaderParam(HEADER_ACCEPT) String acceptHeader,
@HeaderParam(HEADER_ACCEPT_ENCODING) String acceptEncoding,
@HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept,
@Context UriInfo uriInfo,
@Nullable @QueryParam("regions") String regionsStr) {
// 获取注册列表的区域
boolean isRemoteRegionRequested = null != regionsStr && !regionsStr.isEmpty();
String[] regions = null;
if (!isRemoteRegionRequested) {
EurekaMonitors.GET_ALL.increment();
} else {
regions = regionsStr.toLowerCase().split(",");
Arrays.sort(regions); // So we don't have different caches for same regions queried in different order.
EurekaMonitors.GET_ALL_WITH_REMOTE_REGIONS.increment();
}
// 判断是否可以访问
if (!registry.shouldAllowAccess(isRemoteRegionRequested)) {
return Response.status(Status.FORBIDDEN).build();
}
// 设置API版本
CurrentRequestVersion.set(Version.toEnum(version));
// 默认key的类型为JSON
KeyType keyType = Key.KeyType.JSON;
// 默认设置返回类型为JSON
String returnMediaType = MediaType.APPLICATION_JSON;
// 如果Accept为空,或者不包含JSON字符串(表示客户端可能不接收JSON类型),则设置返回XML类型的
if (acceptHeader == null || !acceptHeader.contains(HEADER_JSON_VALUE)) {
keyType = Key.KeyType.XML;
returnMediaType = MediaType.APPLICATION_XML;
}
// 构建缓存KEY
Key cacheKey = new Key(Key.EntityType.Application,
ResponseCacheImpl.ALL_APPS,
keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions
);
// 获取缓存信息,返回给客户端
Response response;
// 判断请求接收类型是否是gzip ,如果是,则返回gzip的流出去
if (acceptEncoding != null && acceptEncoding.contains(HEADER_GZIP_VALUE)) {
response = Response.ok(responseCache.getGZIP(cacheKey))
.header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE)
.header(HEADER_CONTENT_TYPE, returnMediaType)
.build();
} else {
response = Response.ok(responseCache.get(cacheKey))
.build();
}
return response;
}
public Applications getApplications() {
boolean disableTransparentFallback = serverConfig.disableTransparentFallbackToOtherRegion();
if (disableTransparentFallback) {
return getApplicationsFromLocalRegionOnly();
} else {
return getApplicationsFromAllRemoteRegions(); // Behavior of falling back to remote region can be disabled.
}
}
public Applications getApplicationsFromAllRemoteRegions() {
return getApplicationsFromMultipleRegions(allKnownRemoteRegions);
}
public Applications getApplicationsFromMultipleRegions(String[] remoteRegions) {
boolean includeRemoteRegion = null != remoteRegions && remoteRegions.length != 0;
logger.debug("Fetching applications registry with remote regions: {}, Regions argument {}",
includeRemoteRegion, Arrays.toString(remoteRegions));
// 默认为false
if (includeRemoteRegion) {
GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS.increment();
} else {
GET_ALL_CACHE_MISS.increment();
}
Applications apps = new Applications();
apps.setVersion(1L);
// 循环该类中的CurrentHashMap, 这个MAP中,存储的是所有的客户端注册的实例信息
// KEY 为客户端的名称,value为客户端的集群机器信息。
for (Entry<String, Map<String, Lease<InstanceInfo>>> entry : registry.entrySet()) {
Application app = null;
//
if (entry.getValue() != null) {
// 获取Lease信息,里面有每个实例的instance信息,分装成Application实体
for (Entry<String, Lease<InstanceInfo>> stringLeaseEntry : entry.getValue().entrySet()) {
Lease<InstanceInfo> lease = stringLeaseEntry.getValue();
if (app == null) {
app = new Application(lease.getHolder().getAppName());
}
app.addInstance(decorateInstanceInfo(lease));
}
}
if (app != null) {
//放入 Applications里面去
apps.addApplication(app);
}
}
// 。。。。省略N多代码
apps.setAppsHashCode(apps.getReconcileHashCode());
return apps;
}
- 把服务端本地的CurrentHashMap里面存储的客户端信息,封装成Application实体,然后返回
@Path("delta")
@GET
public Response getContainerDifferential(
@PathParam("version") String version,
@HeaderParam(HEADER_ACCEPT) String acceptHeader,
@HeaderParam(HEADER_ACCEPT_ENCODING) String acceptEncoding,
@HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept,
@Context UriInfo uriInfo, @Nullable @QueryParam("regions") String regionsStr) {
// ..... 省略N多代码
Key cacheKey = new Key(Key.EntityType.Application,
ResponseCacheImpl.ALL_APPS_DELTA,
keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions
);
// ..... 省略N多代码
if (acceptEncoding != null
&& acceptEncoding.contains(HEADER_GZIP_VALUE)) {
return Response.ok(responseCache.getGZIP(cacheKey))
.header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE)
.header(HEADER_CONTENT_TYPE, returnMediaType)
.build();
} else {
return Response.ok(responseCache.get(cacheKey))
.build();
}
}
public Applications getApplicationDeltas() {
GET_ALL_CACHE_MISS_DELTA.increment();
// 最近变化过的应用,初始化一个实体
Applications apps = new Applications();
// 增量获取的版本号
apps.setVersion(responseCache.getVersionDelta().get());
Map<String, Application> applicationInstancesMap = new HashMap<String, Application>();
try {
// 上写锁
write.lock();
// 最近产生过变化的客户端,都在这个队列里面
Iterator<RecentlyChangedItem> iter = this.recentlyChangedQueue.iterator();
logger.debug("The number of elements in the delta queue is :"
+ this.recentlyChangedQueue.size());
// 循环队列
while (iter.hasNext()) {
// 获取队列中的lease信息,这里面封装的就是客户端的实例信息
Lease<InstanceInfo> lease = iter.next().getLeaseInfo();
InstanceInfo instanceInfo = lease.getHolder();
Object[] args = {instanceInfo.getId(),
instanceInfo.getStatus().name(),
instanceInfo.getActionType().name()};
logger.debug(
"The instance id %s is found with status %s and actiontype %s",
args);
Application app = applicationInstancesMap.get(instanceInfo
.getAppName());
if (app == null) {
// 组装成一个Application实体,同时放入Applications里面去
app = new Application(instanceInfo.getAppName());
applicationInstancesMap.put(instanceInfo.getAppName(), app);
apps.addApplication(app);
}
app.addInstance(decorateInstanceInfo(lease));
}
boolean disableTransparentFallback = serverConfig.disableTransparentFallbackToOtherRegion();
// 暂时没看明白这里的作用(苦笑。。)
if (!disableTransparentFallback) {
Applications allAppsInLocalRegion = getApplications(false);
for (RemoteRegionRegistry remoteRegistry : this.regionNameVSRemoteRegistry.values()) {
Applications applications = remoteRegistry.getApplicationDeltas();
for (Application application : applications.getRegisteredApplications()) {
Application appInLocalRegistry =
allAppsInLocalRegion.getRegisteredApplications(application.getName());
if (appInLocalRegistry == null) {
apps.addApplication(application);
}
}
}
}
// 获取全量的注册信息
Applications allApps = getApplications(!disableTransparentFallback);
// 设置HashCode
apps.setAppsHashCode(allApps.getReconcileHashCode());
return apps;
} finally {
write.unlock();
}
}
private TimerTask getDeltaRetentionTask() {
return new TimerTask() {
@Override
public void run() {
Iterator<RecentlyChangedItem> it = recentlyChangedQueue.iterator();
while (it.hasNext()) {
// 最后更新时间小于当前时间-3分钟,那么就会被移除
if (it.next().getLastUpdateTime() <
System.currentTimeMillis() - serverConfig.getRetentionTimeInMSInDeltaQueue()) {
it.remove();
} else {
break;
}
}
}
};
}
- 上面主要用到了一个租约变化的队列, 这里面在客户端发生变化时,都会在这里面加入一条信息, 如: 注册,下线,过期等操作,
租约变化队列里面的数据默认保存3分钟,会有一个定时器没30秒清理一次。 - retentionTimeInMSInDeltaQueue : 客户端保持增量信息缓存的时间,从而保证不会丢失这些信息,单位为毫秒,默认为3 * 60 * 1000
获取到了这些变化的客户端信息,返回Eureka Clien 之后,通过集合合并,就可以得到最新的缓存数据了。