EurekaClient心跳机制与EurekaServer服务过期原理

·  阅读 626
EurekaClient心跳机制与EurekaServer服务过期原理

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 事件,调用父类 PeerAwareInstanceRegistryImplrenew 方法进行真正地处理。

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 ......
}
复制代码

小结

  1. EurekaClient 发送心跳包是通过 DiscoveryClientheartbeatExecutor 执行 HeartbeatThread 的线程方法,默认时间是 30 秒一次。
  2. EurekaServer 接收心跳请求是通过 InstanceResourcerenewLease 方法,先自己接收并处理来自 EurekaClient 的心跳,后将该心跳同步至 EurekaServer 集群中的其它节点。
  3. EurekaServer 在初始化时会额外启动一个 EvictionTask 定时任务,默认每分钟检查一次心跳收集情况,并对长时间没有心跳的服务实例执行过期处理。
分类:
后端
标签: