Eureka源码剖析之四:服务续约

Eureka源码剖析之一:初始化-启动

Eureka源码剖析之二:服务注册

Eureka源码剖析之三:服务拉取

这里主要看下Eureka关于服务续约的源码,其实大致跟服务注册流程类似,不过更多细节和逻辑还是很大不同的。

〓Eureka Client

// 在DiscoveryClient里有renew方法,大概知道其是服务续约的入口。renew使用http的方式发送心跳给服务端,如果服务端返回404,说明是某些原因造成服务是没有注册成功,那么就会再次调用register注册接口进行注册。 @Singleton public class DiscoveryClient implements EurekaClient {

    ...
    boolean renew() {
        EurekaHttpResponse 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;
        }
    }
    ...
} // 这里使用Jersey put的方式发送心跳sendHeartBeat public class JerseyReplicationClient extends AbstractJerseyEurekaHttpClient implements HttpReplicationClient {
    ...
    /**
     * Compared to regular heartbeat, in the replication channel the server may return a more up to date
     * instance copy.
     */     @Override     public EurekaHttpResponse sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
        String urlPath = "apps/" + appName + '/' + id;
        ClientResponse response = null;
        try {
            WebResource webResource = jerseyClient.getClient().resource(serviceUrl)
                    .path(urlPath)
                    .queryParam("status", info.getStatus().toString())
                    .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());
            if (overriddenStatus != null) {
                webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name());
            }
            Builder requestBuilder = webResource.getRequestBuilder();
            addExtraHeaders(requestBuilder);
            response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).put(ClientResponse.class);
            InstanceInfo infoFromPeer = null;
            if (response.getStatus() == Status.CONFLICT.getStatusCode() && response.hasEntity()) {
                infoFromPeer = response.getEntity(InstanceInfo.class);
            }
            return anEurekaHttpResponse(response.getStatus(), infoFromPeer).type(MediaType.APPLICATION_JSON_TYPE).build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("[heartbeat] Jersey HTTP PUT {}; statusCode={}", urlPath, response == null ? "N/A" : response.getStatus());
            }
            if (response != null) {
                response.close();
            }
        }
    }
    ...
} 

接下来再看哪里调用了它:

     /**
     * The heartbeat task that renews the lease in the given intervals.
     * 初始化定时任务时,定时调度心跳线程进行续约服务的调用
     */     private class HeartbeatThread implements Runnable {

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

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

     // default size of 2 - 1 each for heartbeat and cacheRefresh     scheduler = Executors.newScheduledThreadPool(2,
        new ThreadFactoryBuilder()
                .setNameFormat("DiscoveryClient-%d")
                .setDaemon(true)
                .build()); 

跟踪了上面的源码我们知道:服务续约是用定时任务做心跳HeartbeatThread来实现,scheduler定时器使用了两个线程,1个是给刷新缓存服务,1个是给服务续约。LEASE_RENEWAL_INTERVAL_SECONDS = 30,续约服务是定时30秒进行服务续约。
接下来,再看下服务端源码:
〓Eureka Server

@Singleton public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
    ...
    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;
    }
    ...
} // 从这里看出服务端接收客户端的参数一致,也说明这里是接收客户端put请求的续约服务。服务端校验了dirty时间戳,并且将服务端的状态进行覆盖重写。 @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;
    }
    ...
} 

总结

1)Eureka client启动定时任务作为心跳服务即续约服务,每30秒发送心跳http put请求给服务端;
2)Eureka server接收到客户端的请求会校验dirty时间戳(其实这个应该是为了避免时间钟回拨等问题造成时间不一致),之后会将服务端的状态进行覆盖重写进而更新。而服务端在启动的时候同时也有剔除任务,在客户端没有定时续约超过LEASE_EXPIRATION_DURATION_SECONDS = 90秒,则会进行自动剔除下线。

你可能感兴趣的:(Eureka源码剖析之四:服务续约)