Spring Cloud源码分析之Eureka篇第七章:续约

在文章《Spring Cloud源码分析之Eureka篇第四章:服务注册是如何发起的 》的分析中,我们知道了作为Eureka Client的应用启动时,在com.netflix.discovery.DiscoveryClient类的initScheduledTasks方法中,会做以下几件事:

  1. 周期性更新服务列表;
  2. 周期性服务续约;
  3. 服务注册逻辑;

本章学习的是周期性服务续约的相关代码,对应用如何将自身信息注册到Eureka进行深入了解,原文地址:https://blog.csdn.net/boling_cavalry/article/details/82861618

概览

以下图片来自Netflix官方,图中显示Eureka Client会发起Renew向注册中心做周期性续约,这样其他Eureka client通过Get Registry请求就能获取到新注册应用的相关信息:
《Spring Cloud源码分析之Eureka篇第七章:续约》

关于源码版本

本次分析的Spring Cloud版本为Edgware.RELEASE,对应的eureka-client版本为1.7.0;

来自官方文档的指导信息

最准确的说明信息来自Netflix的官方文档,地址:https://github.com/Netflix/eureka/wiki/Understanding-eureka-client-server-communication#renew
学习源码之前先看文档可以确定大方向,不会因为陷入源码细节导致偏离学习目标,如下图所示:
《Spring Cloud源码分析之Eureka篇第七章:续约》

我的理解:

  1. Eureka client每隔三十秒发送一次心跳到Eureka server,这就是续约;
  2. Eureka client续约的目的是告诉Eureka server自己还活着;
  3. Eureka server若90秒内未收到心跳,就从自己的服务列表中剔除该Eureka client;
  4. 建议不要改变心跳间隔,因为Eureka server是通过心跳来判断Eureka client是否正常;

源码分析

  1. 首先回顾com.netflix.discovery.DiscoveryClient类的initScheduledTasks方法,Eureka client在启动的时侯都会执行此方法,如下方所示,已经略去了周期性更新服务列表相关的代码:
private void initScheduledTasks() { //略去获取服务列表相关代码 ... if (clientConfig.shouldRegisterWithEureka()) { //心跳间隔 int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs(); //周期性任务执行超时的时候,会将超时时间翻倍,但不会超过最大时间,这个最大时间是由expBackOffBound决定的 int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound(); logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs); //启动心跳的周期性任务 scheduler.schedule( new TimedSupervisorTask( "heartbeat", scheduler, heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new HeartbeatThread() ), renewalIntervalInSecs, TimeUnit.SECONDS); //略去服务注册相关代码 ... 
  1. 上述代码可以看出,续租逻辑在HeartbeatThread实例中,交给TimedSupervisorTask实例进行周期性调用,有关TimedSupervisorTask的功能和细节,请参考《Eureka的TimedSupervisorTask类(自动调节间隔的周期性任务)》

  2. HeartbeatThread类中,通过调用renew方法实现续租,如下代码所示,方法注释已说明是Restfult请求来实现的,对应Eureka server的返回信息httpResponse,除了检查返回码是否等于200就没有任何作用了,想想也是如此,30秒一次的心跳,不论是请求还是响应都应该尽量简洁,降低服务器和网络的压力:

/** * Renew with the eureka service by making the appropriate REST call */ boolean renew() { EurekaHttpResponse<InstanceInfo> httpResponse; try { //发Restful请求,即心跳 httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null); logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode()); //404错误会触发注册逻辑 if (httpResponse.getStatusCode() == 404) { REREGISTER_COUNTER.increment(); logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName()); return register(); } //返回码200表示心跳成功 return httpResponse.getStatusCode() == 200; } catch (Throwable e) { logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e); return false; } } 
  1. 继续展开上面代码段中的 eurekaTransport.registrationClient.sendHeartBeat方法,源码在EurekaHttpClientDecorator类中:
@Override public EurekaHttpResponse<InstanceInfo> sendHeartBeat(final String appName, final String id, final InstanceInfo info, final InstanceStatus overriddenStatus) { return execute(new RequestExecutor<InstanceInfo>() { @Override public EurekaHttpResponse<InstanceInfo> execute(EurekaHttpClient delegate) { //网络处理委托给代理类完成 return delegate.sendHeartBeat(appName, id, info, overriddenStatus); } @Override public RequestType getRequestType() { //请求类型为心跳 return RequestType.SendHeartBeat; } }); } 
  1. 继续展开delegate.sendHeartBeat,多层调用一路展开,最终由JerseyApplicationClient类来完成操作,对应源码在父类AbstractJerseyEurekaHttpClient中,如下所示,主要工作是利用jersey库的Restful Api将自身的信息PUT到Eureka server,注意:这里不是POST,也不是GET,而是PUT:
 @Override public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) { String urlPath = "apps/" + appName + '/' + id; ClientResponse response = null; try { //请求参数有两个:Eureka client自身状态、自身关键信息(状态、元数据等)最后一次变化的时间 WebResource webResource = jerseyClient.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); //注意:这里不是POST,也不是GET,而是PUT response = requestBuilder.put(ClientResponse.class); EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response)); if (response.hasEntity()) { eurekaResponseBuilder.entity(response.getEntity(InstanceInfo.class)); } return eurekaResponseBuilder.build(); } finally { if (logger.isDebugEnabled()) { logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus()); } if (response != null) { response.close(); } } } 

至此,Eureka client向服务续租的源码就分析完毕了,过程相对简单,DiscoveryClient、TimedSupervisorTask、JerseyApplicationClient等实例各司其职,定时发送PUT请求到Eureka server;

    原文作者:Spring Cloud
    原文地址: https://blog.csdn.net/boling_cavalry/article/details/82915355
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞