服务获取和服务续约
Eureka 客户端核心机制:服务获取与服务续约的实现解析
Eureka 客户端(Eureka Client)通过两个核心定时任务与 Eureka Server 交互:服务获取(拉取服务注册表)和服务续约(发送心跳维持存活状态)。这两个任务在DiscoveryClient类中初始化,是保证服务发现准确性和服务状态有效性的关键。
定时任务初始化:initScheduledTasks 方法
initScheduledTasks是 Eureka 客户端初始化时的核心方法,用于启动服务获取和服务续约的定时任务。其核心逻辑是:根据配置判断是否需要拉取注册表和注册服务,若需要则创建对应的定时任务。
private void initScheduledTasks() {
// 1. 服务获取定时任务(拉取服务注册表)
if (clientConfig.shouldFetchRegistry()) {
// 拉取间隔(默认30秒,可通过eureka.client.registry-fetch-interval-seconds配置)
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
// 指数退避边界(任务失败时重试间隔的最大倍数)
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
// 启动定时任务:每registryFetchIntervalSeconds秒执行一次CacheRefreshThread
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh", // 任务名称
scheduler,
cacheRefreshExecutor, // 执行线程池
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread() // 服务获取线程
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
// 2. 服务续约定时任务(发送心跳)
if (clientConfig.shouldRegisterWithEureka()) {
// 续约间隔(默认30秒,由eureka.instance.lease-renewal-interval-in-seconds配置)
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
// 启动定时任务:每renewalIntervalInSecs秒执行一次HeartbeatThread
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat", // 任务名称
scheduler,
heartbeatExecutor, // 执行线程池
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread() // 服务续约线程
),
renewalIntervalInSecs, TimeUnit.SECONDS);
// 省略实例信息同步相关逻辑...
}
}
核心设计:
- 两个任务独立线程池执行(
cacheRefreshExecutor和heartbeatExecutor),避免相互影响; - 使用
TimedSupervisorTask包装,实现任务失败时的指数退避重试(避免频繁失败占用资源); - 执行间隔可通过配置自定义,平衡实时性与性能。
服务获取:CacheRefreshThread
服务获取的核心目标是:定期从 Eureka Server 拉取服务注册表并更新本地缓存,确保服务消费者能获取最新的服务实例列表。
1. 核心逻辑:fetchRegistry 方法
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
try {
// 获取本地已缓存的服务注册表
Applications applications = getApplications();
// 触发全量拉取的条件:
// 1. 禁用增量更新(eureka.client.disable-delta=true)
// 2. 首次拉取(本地缓存为空)
// 3. 强制全量拉取(forceFullRegistryFetch=true)
// 4. 本地缓存版本无效(version=-1)
if (clientConfig.shouldDisableDelta() || forceFullRegistryFetch || applications == null) {
// 全量拉取注册表
getAndStoreFullRegistry();
} else {
// 增量拉取(仅获取上次更新后的变化)
getAndUpdateDelta(applications);
}
// 刷新缓存并更新服务实例状态
onCacheRefreshed();
updateInstanceRemoteStatus();
return true;
} catch (Throwable e) {
logger.error("无法刷新服务缓存!", e);
return false;
}
}
拉取策略:
- 全量拉取(getAndStoreFullRegistry):首次启动或配置禁用增量时,拉取完整的服务注册表(所有服务 + 所有实例);
- 增量拉取(getAndUpdateDelta):后续拉取仅获取上次同步后新增 / 修改 / 删除的服务实例,减少网络传输开销。
2. 缓存刷新:onCacheRefreshed
拉取注册表后,需要更新本地缓存,并通知负载均衡器(如 Ribbon)刷新实例列表:
private void onCacheRefreshed() {
// 发布缓存刷新事件(CacheRefreshedEvent)
if (cacheRefreshedListener != null) {
cacheRefreshedListener.onCacheRefreshed();
}
// 通知所有注册的监听器
for (EurekaEventListener listener : eventListeners) {
listener.onEvent(new CacheRefreshedEvent());
}
}
负载均衡器更新:
缓存刷新事件被EurekaNotificationServerListUpdater捕获后,会触发负载均衡器的实例列表更新:
// 简化逻辑:从缓存中获取最新实例列表并更新负载均衡器
updateAction.doUpdate(); // 更新负载均衡器中的服务实例映射
lastUpdated.set(System.currentTimeMillis()); // 记录最后更新时间
这样,服务消费者在调用服务时,就能使用最新的实例列表进行负载均衡。
服务续约:HeartbeatThread
服务续约的核心目标是:定期向 Eureka Server 发送心跳,证明服务实例存活,避免被 Eureka Server 剔除。
1. 核心逻辑:renew 方法
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
// 发送心跳请求:PUT /eureka/apps/{服务名}/{实例ID}
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(
instanceInfo.getAppName(), // 服务名
instanceInfo.getId(), // 实例ID
instanceInfo, // 实例信息
null
);
// 处理响应:
// 1. 若返回404(NOT_FOUND):说明实例已从Server删除,需重新注册
if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
REREGISTER_COUNTER.increment(); // 重新注册计数器+1
logger.info("实例已不存在,重新注册服务...");
// 标记实例信息为"脏数据"(需要同步)
long timestamp = instanceInfo.setIsDirtyWithTime();
// 执行重新注册
boolean success = register();
if (success) {
instanceInfo.unsetIsDirty(timestamp); // 清除"脏数据"标记
}
return success;
}
// 2. 若返回200(OK):续约成功
return httpResponse.getStatusCode() == Status.OK.getStatusCode();
} catch (Throwable e) {
logger.error("发送心跳失败!", e);
return false;
}
}
核心机制:
- 心跳请求格式:
PUT /eureka/apps/{服务名}/{实例ID},携带实例当前状态; - 异常处理:若心跳失败(如网络波动),
TimedSupervisorTask会触发重试(指数退避策略); - 重新注册:若 Server 返回 404(实例已被删除),客户端会自动重新注册,保证服务可用性。
2. 续约配置参数
| 配置参数 | 默认值 | 说明 |
|---|---|---|
eureka.instance.lease-renewal-interval-in-seconds |
30 | 心跳间隔(秒),即服务续约的定时任务间隔 |
eureka.instance.lease-expiration-duration-in-seconds |
90 | 服务过期时间(秒):Server 多久未收到心跳则标记实例为失效 |
总结:服务获取与续约的核心价值
- 服务获取:
通过 “全量拉取 + 增量更新” 的策略,在减少网络开销的同时,保证客户端缓存的服务注册表尽可能新,为服务调用提供准确的实例列表。 - 服务续约:
通过定时心跳机制,让 Eureka Server 实时感知服务实例的存活状态,结合 “自动重新注册” 逻辑,最大程度避免服务因临时故障被误剔除。 - 整体设计思想:
Eureka 客户端通过 “定时任务 + 容错重试 + 增量更新” 的组合策略,在保证服务发现实时性的同时,兼顾了网络开销和系统稳定性,这也是 Eureka 作为 AP 系统高可用特性的重要体现