本文主要研究下eureka client的HeartbeatThread

DiscoveryClient.initScheduledTasks

eureka-client-1.8.8-sources.jar!/com/netflix/discovery/DiscoveryClient.java

        //...
        //int DEFAULT_EXECUTOR_THREAD_POOL_BACKOFF_BOUND = 10;
        int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
        scheduler = Executors.newScheduledThreadPool(2,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());

        heartbeatExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),  //DEFAULT_EXECUTOR_THREAD_POOL_SIZE = 5
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff

        //......
        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);
                    //......
        }
DiscoveryClient在构造器里头执行initScheduledTasks方法,里头设置了一个HeartbeatThread的调度,间隔时间是renewalIntervalInSecs秒

HeartbeatThread

eureka-client-1.8.8-sources.jar!/com/netflix/discovery/DiscoveryClient.java

    /**
     * The heartbeat task that renews the lease in the given intervals.
     */
    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(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
            if (httpResponse.getStatusCode() == 404) {
                REREGISTER_COUNTER.increment();
                logger.info(PREFIX + "{} - Re-registering apps/{}", 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(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
            return false;
        }
    }
可以看到renew调用的是sendHeartBeat方法,如果成功的话,则更新lastSuccessfulHeartbeatTimestamp;如果返回404,则表示需要重新注册,首先标记dirty,然后调用register方法,如果成功则重置dirty属性。

RestTemplateEurekaHttpClient.sendHeartBeat

spring-cloud-netflix-eureka-client-2.0.0.RC1-sources.jar!/org/springframework/cloud/netflix/eureka/http/RestTemplateEurekaHttpClient.java

    public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id,
            InstanceInfo info, InstanceStatus overriddenStatus) {
        String urlPath = serviceUrl + "apps/" + appName + '/' + id + "?status="
                + info.getStatus().toString() + "&lastDirtyTimestamp="
                + info.getLastDirtyTimestamp().toString() + (overriddenStatus != null
                        ? "&overriddenstatus=" + overriddenStatus.name() : "");

        ResponseEntity<InstanceInfo> response = restTemplate.exchange(urlPath,
                HttpMethod.PUT, null, InstanceInfo.class);

        EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(
                response.getStatusCodeValue(), InstanceInfo.class)
                        .headers(headersOf(response));

        if (response.hasBody())
            eurekaResponseBuilder.entity(response.getBody());

        return eurekaResponseBuilder.build();
    }
sendHeartBeat方法是一个PUT请求,参数在url中

实例

curl -i -X PUT http://localhost:8761/eureka/apps/test-service/test1?status=UP&lastDirtyTimestamp=1525767406400&overriddenstatus=UP

返回

HTTP/1.1 200
Content-Type: application/xml
Content-Length: 0
Date: Tue, 08 May 2018 08:17:46 GMT

小结

eureka client在实例化的时候注册了一个定时任务,每隔renewalIntervalInSecs,向eureka server发送一次心跳。

doc


codecraft
11.9k 声望2k 粉丝

当一个代码的工匠回首往事时,不因虚度年华而悔恨,也不因碌碌无为而羞愧,这样,当他老的时候,可以很自豪告诉世人,我曾经将代码注入生命去打造互联网的浪潮之巅,那是个很疯狂的时代,我在一波波的浪潮上留下...