EurekaClient发送心跳
DiscoveryClient类构造时会初始化一个定时任务,专门维护和EurekaServer的心跳。
private void initScheduledTasks() {
// .......
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);
// ......
}
EurekaServer接收心跳请求
InstanceResource ,它是接收微服务实例请求的一个基于 Jersey 的 Restful WebService 接口,在这里面有一个 renewLease 方法,它就是接收心跳请求。
@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);
// ......
}
InstanceRegistry#renew
public boolean renew(final String appName, final String serverId,
boolean isReplication) {
log("renew " + appName + " serverId " + serverId + ", isReplication {}"
+ isReplication);
List<Application> applications = getSortedApplications();
// 遍历所有的服务
for (Application input : applications) {
if (input.getName().equals(appName)) {
InstanceInfo instance = null;
// 获取对应的服务实例
for (InstanceInfo info : input.getInstances()) {
if (info.getId().equals(serverId)) {
instance = info;
break;
}
}
// 发布事件
publishEvent(new EurekaInstanceRenewedEvent(this, appName, serverId,
instance, isReplication));
break;
}
}
// 调用父类处理
return super.renew(appName, serverId, isReplication);
}
循环所有的服务,先把当前心跳的服务找出来,再从服务中找出对应的实例,之后发布
EurekaInstanceRenewedEvent事件,调用父类PeerAwareInstanceRegistryImpl的renew方法进行真正地处理。
PeerAwareInstanceRegistryImpl#renew
// 父类处理逻辑
public boolean renew(final String appName, final String id, final boolean isReplication) {
if (super.renew(appName, id, isReplication)) {
replicateToPeers(Action.Heartbeat, appName, id, null, null, isReplication);
return true;
}
return false;
}
AbstractInstanceRegistry#renew
public boolean renew(String appName, String id, boolean isReplication) {
RENEW.increment(isReplication);
// 先从本地的所有租约中查看是否有当前服务
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToRenew = null;
if (gMap != null) {
leaseToRenew = gMap.get(id);
}
// 如果服务不存在,说明出现异常,给对应的RENEW_NOT_FOUND计数器添加计数
if (leaseToRenew == null) {
RENEW_NOT_FOUND.increment(isReplication);
return false;
} else {
// 租约存在,取出服务实例信息
InstanceInfo instanceInfo = leaseToRenew.getHolder();
if (instanceInfo != null) {
// 刷新服务实例的状态,并在正常状态下设置到服务实例中
// touchASGCache(instanceInfo.getASGName());
InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
instanceInfo, leaseToRenew, isReplication);
if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
RENEW_NOT_FOUND.increment(isReplication);
return false;
}
if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);
}
}
// 计数、记录下一次租约应到的时间
renewsLastMin.increment();
leaseToRenew.renew();
return true;
}
}
replicateToPeers
EurekaServer本地处理心跳以后,同步至其他EurekaServer节点
private void replicateToPeers(Action action, String appName, String id,
InstanceInfo info /* optional */,
InstanceStatus newStatus /* optional */, boolean isReplication) {
Stopwatch tracer = action.getTimer().start();
try {
if (isReplication) {
numberOfReplicationsLastMin.increment();
}
// If it is a replication already, do not replicate again as this will create a poison replication
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;
}
// 核心方法
replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
}
} finally {
tracer.stop();
}
}
PeerAwareInstanceRegistryImpl#replicateInstanceActionsToPeers
try {
InstanceInfo infoFromRegistry = null;
CurrentRequestVersion.set(Version.V2);
switch (action) {
case Heartbeat:
InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
break;
}
} catch (Throwable t) {
...
}
EurekaServer处理服务过期
EurekaServer 启动时的那个核心方法 initEurekaServerContext :
protected void initEurekaServerContext() throws Exception {
// ......
// Copy registry from neighboring eureka node
// Eureka复制集群节点注册表
int registryCount = this.registry.syncUp();
// 计算最少续租次数等指标、初始化服务剔除定时器
this.registry.openForTraffic(this.applicationInfoManager, registryCount);
// ......
}
openForTraffic
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// Renewals happen every 30 seconds and for a minute it should be a factor of 2.
// 默认情况每个服务实例每分钟需要发生两次心跳动作
this.expectedNumberOfClientsSendingRenews = count;
updateRenewsPerMinThreshold();
// 省略部分不太重要的源码 ......
applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
// 父类AbstractInstanceRegistry初始化定时任务
super.postInit();
}
AbstractInstanceRegistry#postInit
protected void postInit() {
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
// 创建定时任务,并设置定时周期(默认1分钟)
evictionTaskRef.set(new EvictionTask());
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}
EvictionTask服务过期
public void run() {
try {
long compensationTimeMs = getCompensationTimeMs();
logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
// 服务过期
evict(compensationTimeMs);
} // catch ......
}
小结
- EurekaClient 发送心跳包是通过
DiscoveryClient的heartbeatExecutor执行HeartbeatThread的线程方法,默认时间是 30 秒一次。 - EurekaServer 接收心跳请求是通过
InstanceResource的renewLease方法,先自己接收并处理来自 EurekaClient 的心跳,后将该心跳同步至 EurekaServer 集群中的其它节点。 - EurekaServer 在初始化时会额外启动一个
EvictionTask定时任务,默认每分钟检查一次心跳收集情况,并对长时间没有心跳的服务实例执行过期处理。