0.本文重点:
-
服务启动注册、心跳、拉取服务流程
-
服务何时向server端更新状态
[DiscoveryClient-InstanceInfoReplicator-0] [com.netflix.discovery.DiscoveryClient]
[register] [879] : DiscoveryClient_xxxx/ xxx:xxx - registration status: 204
服务启动时我们经常可以看到上面这段日志,实则这是在调用 eureka server的api接口
Eureka -Api list
| 请求名称 | 请求方式 | HTTP地址 | 请求描述 |
|---|---|---|---|
| 注册服务 | POST | /eureka/apps/{appID} | 传递JSON或者XML格式的参数内容,HTTP code为204表示成功 |
| 删除服务 | DELETE | /eureka/apps/{appID}/{instanceID} | HTTP code为200时表示成功 |
| 发起心跳 | PUT | /eureka/apps/{appID}/{instanceID} | HTTP code为200时表示成功 |
| 查询服务 | GET | /eureka/apps | HTTP code为200时表示成功,返回XML/JSON数据 |
| 查询指定appID的服务列表 | GET | /eureka/apps/{appID} | HTTP code为200时表示成功,返回XML/JSON数据 |
| 查询指定appID&instanceID的服务 | GET | /eureka/apps/{appID}/{instanceID} | 获取指定appID以及instanceID的服务信息,HTTP code为200时表示成功,返回XML/JSON数据 |
| 查询指定instanceID服务列表 | GET | /eureka/apps/instances/{instanceID} | 获取指定instanceID的服务信息,HTTP code为200时表示成功,返回XML/JSON数据 |
| 变更服务状态 | PUT | /eureka/apps/{appID}/{instanceID}/status?value=DOWN | 服务上线、服务下线等状态改变,HTTP code为200时表示成功 |
① 服务注册(Register): Eureka Client会通过发送REST请求向Eureka Server注册自己的服务,提供自身IP、端口、微服务名称等信息。Eureka Server接收到注册请求后,就会把这些信息存储在一个双层的Map中。
② 服务续约(Renew): 在服务注册后,Eureka Client会维护一个心跳来持续通知Eureka Server,说明服务一直处于可用状态,防止被剔除。Eureka Client在默认的情况下会每隔30秒(eureka.instance.leaseRenewallIntervalInSeconds)发送一次心跳来进行服务续约。
③ 服务同步(Replicate): Eureka Server集群中多个Eureka Server之间会互相进行注册,不同Eureka Server之间会进行服务同步,用来保证Eureka Server集群内的所有实例中的数据一致性
④ 获取服务(Get Registry): 服务消费者(Eureka Client)在启动的时候,会发送一个REST请求给Eureka Server,获取上面注册的服务清单,并且缓存在Eureka Client本地,默认缓存30秒(eureka.client.registryFetchIntervalSeconds)。同时,为了性能考虑,Eureka Server也会维护一份只读的服务清单缓存,该缓存每隔30秒更新一次。
⑤ 服务调用(Make Remote Call): 服务消费者在获取到服务清单后,就可以根据清单中的服务列表信息,查找到其他服务的地址,从而进行远程调用。
⑥ 服务下线(Cancel): 当Eureka Client需要关闭或重启时,就不希望在这个时间段内再有请求进来,所以,就需要提前先发送REST请求给Eureka Server,告诉Eureka Server自己要下线了,Eureka Server在收到请求后,就会把该服务状态置为下线(DOWN),并把该下线事件传播出去。
⑦ 服务剔除(Evict): 服务实例可能会因为网络故障等原因导致不能提供服务,而此时该实例也没有发送请求给Eureka Server来进行服务下线,所以,还需要有服务剔除的机制。Eureka Server在启动的时候会创建一个定时任务,每隔一段时间(默认60秒):eureka.server.eviction-interval-timer-in-ms(毫秒数),从当前服务清单中把超时没有续约(客户端配置 默认90秒,eureka.instance.leaseExpirationDurationInSeconds )的服务剔除。
⑧ 自我保护: 既然Eureka Server会定时剔除超时没有续约的服务,那就有可能出现一种场景,网络一段时间内发生了异常,所有的服务都没能够进行续约,Eureka Server就把所有的服务都剔除了,这样显然不太合理。所以,就有了自我保护机制,当短时间内,统计续约失败的比例,如果达到一定阈值,则会触发自我保护的机制,在该机制下,Eureka Server不会剔除任何的微服务,等到正常后,再退出自我保护机制。自我保护开关(eureka.server.enableself-preservation: false)
服务状态、Applications
服务的状态
public enum InstanceStatus {
UP, // Ready to receive traffic
DOWN, // Do not send traffic- healthcheck callback failed
STARTING, // Just about starting- initializations to be done - do not
// send traffic
OUT_OF_SERVICE, // Intentionally shutdown for traffic
UNKNOWN;
客户端维护的服务关系,双层map,第一层是服务名与服务列表的关系,第二层是指定服务下,instanceId与服务实例的关系。
public class DiscoveryClient implements EurekaClient {
private final AtomicReference<Applications> localRegionApps = new AtomicReference<Applications>();
}
public class Applications {
private final AbstractQueue<Application> applications;
private final Map<String, Application> appNameApplicationMap;
private final Map<String, VipIndexSupport> virtualHostNameAppMap;
private final Map<String, VipIndexSupport> secureVirtualHostNameAppMap;
}
public class Application {
private volatile boolean isDirty = false;
private final Set<InstanceInfo> instances;
private final AtomicReference<List<InstanceInfo>> shuffledInstances;
private final Map<String, InstanceInfo> instancesMap;
}
DiscoveryClient
DiscoveryClient由 EurekaAutoServiceRegistration辅助注册,改类实现了SmartLiftcycle接口,在所有非lazy bean都实例化完成后,容器会自动回调其start方法,从而实例化DiscoveryClient 。应用进程即将结束时回调其stop方法,向eureka server通知自己即将下线,也就是将DOWN的状态告诉server
EurekaServiceRegistry#register
public void register(EurekaRegistration reg) {
//在此处初始化DiscoveryClient
maybeInitializeClient(reg);
reg.getApplicationInfoManager()
//更新状态 此出由 starting -> up 会触发StatusChangeEvent 立即向服务端更新自己的状态
.setInstanceStatus(reg.getInstanceConfig().getInitialStatus());
reg.getHealthCheckHandler().ifAvailable(healthCheckHandler -> reg
.getEurekaClient().registerHealthCheck(healthCheckHandler));
}
EurekaServiceRegistry#deregister
public void deregister(EurekaRegistration reg) {
if (reg.getApplicationInfoManager().getInfo() != null) {
//更新状态 立即向服务端更新自己的状态为DOWN
reg.getApplicationInfoManager()
.setInstanceStatus(InstanceInfo.InstanceStatus.DOWN);
}
}
构造方法
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
..
this.applicationInfoManager = applicationInfoManager;
//本服务信息 服务名 ip 端口 ,健康检查路径等
InstanceInfo myInfo = applicationInfoManager.getInfo();
...
if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
//不注册 也不拉取服务
...
return;
}
try {
// default size of 2 - 1 each for heartbeat and cacheRefresh
//注释写的很清楚了 2个线程 1个是心跳 1个是缓存刷新
scheduler = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
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);
}
if (clientConfig.shouldFetchRegistry()) {
try {
//fetchRegistry 这里调用是拉取全量,后续的定时任务是增量拉取
boolean primaryFetchRegistryResult = fetchRegistry(false);
...
} catch (Throwable th) {
logger.error("Fetch registry error at startup: {}", th.getMessage());
throw new IllegalStateException(th);
}
}
...
// 初始化 心跳 注册 定时任务
initScheduledTasks();
...
}
initScheduledTasks 初始化定时任务
private void initScheduledTasks() {
//拉取服务
if (clientConfig.shouldFetchRegistry()) {
//默认30s
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
cacheRefreshTask = new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
//runnable 调用下方 DiscoveryClient#refreshRegistry
new CacheRefreshThread()
);
scheduler.schedule(
cacheRefreshTask,
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
//注册服务
if (clientConfig.shouldRegisterWithEureka()) {
//默认30s
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
// Heartbeat timer
heartbeatTask = new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
//runnable 调用下方 DiscoveryClient#renew
new HeartbeatThread()
);
scheduler.schedule(
heartbeatTask,
renewalIntervalInSecs, TimeUnit.SECONDS);
...
//注册状态变化事件监听器
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
//状态变化回调事件 调用下方InstanceInfoReplicator#onDemandUpdate,
//本质上就是不再等待定时任务执行,立即更新状态相关变化
instanceInfoReplicator.onDemandUpdate();
}
};
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
//默认注册 代表是不仅仅只在定时任务中向服务端更新自己的状态 只要状态发生了变化,
//会立即向server更新自己的状态
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
//默认40s后执行 下方InstanceInfoReplicator#run
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
DiscoveryClient#refreshRegistry
void refreshRegistry() {
try {
...
//调用下方 DiscoveryClient#fetchRegistry 拉取全量/增量数据
boolean success = fetchRegistry(remoteRegionsModified);
if (success) {
registrySize = localRegionApps.get().size();
lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis();
}
...
} catch (Throwable e) {
logger.error("Cannot fetch registry from server", e);
}
}
DiscoveryClient#fetchRegistry 拉取服务
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
try {
Applications applications = getApplications();
if (clientConfig.shouldDisableDelta()
|| (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
|| forceFullRegistryFetch
//首次全量拉取后 只有拉取到了 applications就不会为空,后续就会增量拉取
|| (applications == null)
|| (applications.getRegisteredApplications().size() == 0)
|| (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
{
...
//默认会去掉状态非UP的服务
getAndStoreFullRegistry();
} else {
//获取增量 分别就 ADDED、MODIFIED、DELETED 三种事件机制 更新维护的 appNameApplicationMap,
//更新localRegionApps\applications的版本号,用于下次继续拉取增量
getAndUpdateDelta(applications);
}
applications.setAppsHashCode(applications.getReconcileHashCode());
logTotalInstances();
} catch (Throwable e) {
....
}
//发布 CacheRefreshedEvent 此事件是EurekaEvent 是eureka自己实现的发布订阅机制;
//增量拉取时,cacheRefreshedCount已完成初始化, 还会发布一个HeartbeatEvent事件(spring的applicationEvent)
(spring gateway的路由刷新有用到这个事件)
onCacheRefreshed();
//拉取到的服务列表 更新到本地缓存,从本地缓存的服务列表,根据自身服务的instanceId找到相关的状态
第一次是获取不到的
//获取不到的情况下将状态置为UNKNOWN,同时与上次维护自身服务状态对比,如果不一致就发布StatusChangeEvent事件
//由于默认上次状态为 UNKNOWN 本次不处理。当然如果你是服务重启的话,有可能拉到DOWN的状态
//由于首次拉取的时候 statusChangeListner还未注册,所以首次拉取的动作不会向server更新自己的状态
updateInstanceRemoteStatus();
// registry was fetched successfully, so return true
return true;
}
DiscoveryClient#renew
就是简单的30s一次请求
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
...
return httpResponse.getStatusCode() == Status.OK.getStatusCode();
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
return false;
}
}
InstanceInfoReplicator#onDemandUpdate
public boolean onDemandUpdate() {
if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {
if (!scheduler.isShutdown()) {
scheduler.submit(new Runnable() {
@Override
public void run() {
Future latestPeriodic = scheduledPeriodicRef.get();
if (latestPeriodic != null && !latestPeriodic.isDone()) {
//取消定时任务状态检查
latestPeriodic.cancel(false);
}
//意思就是不等定时任务了,因为已经感知到状态发生变化了 ,马上更新
InstanceInfoReplicator.this.run();
}
});
return true;
} else {
logger.warn("Ignoring onDemand update due to stopped scheduler");
return false;
}
} else {
logger.warn("Ignoring onDemand update due to rate limiter");
return false;
}
}
InstanceInfoReplicator#run
public void run() {
try {
discoveryClient.refreshInstanceInfo();
//判断是否存在dirtyTime dirtyTime就是当状态发生变化时 记录下的时间戳
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 {
//相当于30s一次校验一下自己的状态 如果状态变化就向服务端更新状态 ,服务启动时默认是40s后,
//但是会被listener感知到变化 提前触发
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
总结
通过源码的分析,我们可以知道DiscoverClient初始化时做了一下几件事
1.向eureka server拉取服务列表,更新到本地缓存中
2.初始化以下定时任务
- 初始化refreshRegistry 拉取服务更新缓存定时任务 默认30s执行一次
- 初始化renew 心跳定时任务 默认30s执行一次
- 初始化run 服务自检查定时任务 首次是40s执行,后续30s执行一次。
3.初始化任务结束,将自身状态由STARTING->UP,由于状态发生变化,记录dirtyTime(也就是状态发生变化而又未通知server的时间点)发布StatusChangeEvent,被StatusChangeListener监听到,中断第3个定时任务,不再等待,直接向server端更新自己的状态,这个体现在日志层面就是服务启动之初的204注册响应吗。然后更新第三个定时任务的时间周期为30s一次。
参考: