Netflix Eureka源码分析(15)——服务实例与注册中心之间的心跳机制(服务续约)源码剖析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/A_Story_Donkey/article/details/82969601

心跳,eureka client每隔一定的时间,会给eureka server发送心跳,保持心跳,让eureka server知道自己还活着

(1)DiscoveryClient初始化的时候,会去调度一堆定时任务,其中有一个就是HeartbeatThread,心跳线程

@Singleton
public class DiscoveryClient implements EurekaClient {  

    //构造函数
    @Inject
    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider) { 
        //初始化调度任务
        initScheduledTasks();
    }

     private void initScheduledTasks() {

            // Heartbeat timer
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "heartbeat",
                            scheduler,
                            heartbeatExecutor,
                            renewalIntervalInSecs,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new HeartbeatThread()
                    ),
                    renewalIntervalInSecs, TimeUnit.SECONDS);
    }

}

(2)在这里可以看到,默认是每隔30秒去发送一次心跳,每隔30秒执行一次HeartbeatTHread线程的逻辑,发送心跳

@JsonRootName("leaseInfo")
public class LeaseInfo {

    public static final int DEFAULT_LEASE_RENEWAL_INTERVAL = 30;

}

(3)这边的话就是去发送这个心跳,走的是EurekaHttpClient的sendHeartbeat()方法,http://localhost:8080/v2/apps/ServiceA/i-000000-1,走的是put请求

    /**
     * The heartbeat task that renews the lease in the given intervals.
     * 在给定间隔更新租约的心跳任务。
     * HeartbeatThread线程类
     */
    private class HeartbeatThread implements Runnable {

        public void run() {
            if (renew()) {
                lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
            }
        }
    }

    /**
     * Renew with the eureka service by making the appropriate REST call
     */
    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());
                long timestamp = instanceInfo.setIsDirtyWithTime();
                boolean success = register();
                if (success) {
                    instanceInfo.unsetIsDirty(timestamp);
                }
                return success;
            }
            return httpResponse.getStatusCode() == 200;
        } catch (Throwable e) {
            logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e);
            return false;
        }
    }
public class Jersey2ReplicationClient extends AbstractJersey2EurekaHttpClient implements HttpReplicationClient {   

    @Override
    public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
        String urlPath = "apps/" + appName + '/' + id;
    }
}

(4)负责承接服务实例的心跳相关的这些操作的,是ApplicationsResource,服务相关的controller,eureka用的是jersey的mvc框架。找到ApplicationResource,再次找到InstanceResource,通过PUT请求,可以找到renewLease方法。

@Produces({"application/xml", "application/json"})
public class 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;
        if (lastDirtyTimestamp != null && serverConfig.shouldSyncWhenTimestampDiffers()) {
            response = this.validateDirtyTimestamp(Long.valueOf(lastDirtyTimestamp), isFromReplicaNode);
            // Store the overridden status since the validation found out the node that replicates wins
            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;
    }
}

(5)通过注册表的renew()方法,进去完成服务续约,实际进入AbstractInstanceRegistry的renew()方法

public abstract class AbstractInstanceRegistry implements InstanceRegistry {

    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);
        }
        if (leaseToRenew == null) {
            RENEW_NOT_FOUND.increment(isReplication);
            logger.warn("DS: Registry: lease doesn't exist, registering resource: {} - {}", appName, id);
            return false;
        } else {
            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.setStatusWithoutDirty(overriddenInstanceStatus);
                }
            }
            renewsLastMin.increment();
            //调用Lease类的renew方法更新lastUpdateTimestamp时间戳
            leaseToRenew.renew();
            return true;
        }
    }
}

(6)从注册表的map中,根据服务名和实例id,获取一个Lease<InstanceInfo>。实际的服务续约的逻辑,其实就是在Lease对象中,更新一下lastUpdateTimestamp这个时间戳,每次续约,就更新一下这个时间戳就ok了。

public class Lease<T> {

    public void renew() {
        lastUpdateTimestamp = System.currentTimeMillis() + duration;
    }
}

总结:

    所谓发送心跳,其实就是client端请求了server端一个类似于http://localhost:8080/v2/apps/ServiceA/i-000000-1的restful接口,然后根据appName和appId找到server端注册表中需要更新的服务实例(其实就是发送心跳的服务对应的服务实例),然后更新一下它的lastUpdateTimestamp为当前时间即可。

猜你喜欢

转载自blog.csdn.net/A_Story_Donkey/article/details/82969601