Eureka client of SpringCloud source code study notes-service renewal

1. Service renewal entrance

  In "Eureka Client-Initialization" , we know that in the initScheduledTasks() method in the constructor of the DiscoveryClient object, the service renewal is realized. The specific implementation is as follows:

 // 创建心跳服务线程,同时进行服务续约
 heartbeatTask = new TimedSupervisorTask(
          "heartbeat",
          scheduler,
          heartbeatExecutor,
          renewalIntervalInSecs,
          TimeUnit.SECONDS,
          expBackOffBound,
          new HeartbeatThread()
  );
  //执行定时任务
  scheduler.schedule(
          heartbeatTask,
          renewalIntervalInSecs, TimeUnit.SECONDS);

  In the above method, a TimedSupervisorTask task is created first, and the task actually executed is realized by HeartbeatThread, and then the scheduler.schedule() method is called to execute the contract renewal task regularly.

2, TimedSupervisorTask class TimerTask

  In fact, the logic of service renewal is similar to service discovery, but when learning about service renewal, I didn't have a deep understanding of the TimedSupervisorTask class. Let's learn about this class first. The TimedSupervisorTask class inherits the TimerTask class, so when the scheduler executes the schedule() method, the run() method of the TimedSupervisorTask object will be executed. This class is mainly used to supervise the execution of subtasks, that is, the tasks of the HeartbeatThread object, while enhancing timeout processing and thread safety.

2.1, the constructor
public TimedSupervisorTask(String name, ScheduledExecutorService scheduler, ThreadPoolExecutor executor,int timeout, TimeUnit timeUnit, int expBackOffBound, Runnable task) {
    
    
    //线程名称               
    this.name = name;
    //线程调度器
    this.scheduler = scheduler;
    //心跳线程池执行器,ThreadPoolExecutor对象
    this.executor = executor;
    //使用续约间隔,作为超期时间
    this.timeoutMillis = timeUnit.toMillis(timeout);
    //实际续约需要执行的任务,HeartbeatThread对象
    this.task = task;
    //使用续约间隔,作为延期执行时间
    this.delay = new AtomicLong(timeoutMillis);
    //超期重试的最大时间,其中expBackOffBound表示心跳超时重试延迟时间的最大乘数值
    this.maxDelay = timeoutMillis * expBackOffBound;

    // 心跳结果的计数器
    successCounter = Monitors.newCounter("success");
    timeoutCounter = Monitors.newCounter("timeouts");
    rejectedCounter = Monitors.newCounter("rejectedExecutions");
    throwableCounter = Monitors.newCounter("throwables");
    threadPoolLevelGauge = new LongGauge(MonitorConfig.builder("threadPoolUsed").build());
    Monitors.registerObject(name, this);
}
2.2, run() method
@Override
public void run() {
    
    
	//保存异步执行结果的变量
    Future<?> future = null;
    try {
    
    
    	//提交任务,并把结果保存到future变量中
        future = executor.submit(task);
        threadPoolLevelGauge.set((long) executor.getActiveCount());
        //设置超时时间
        future.get(timeoutMillis, TimeUnit.MILLISECONDS);  // block until done or timeout
        delay.set(timeoutMillis);
        threadPoolLevelGauge.set((long) executor.getActiveCount());
        //计数
        successCounter.increment();
    } 
    
	//省略各类续约结构的计数和日志 逻辑 ……
	
	 finally {
    
    
        if (future != null) {
    
    //取消
            future.cancel(true);
        }

        if (!scheduler.isShutdown()) {
    
    //执行下一次调用,通过这种方式实现心跳的定期发送
            scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS);
        }
    }
}

3、HeartbeatThread类

  Through the previous study, we know that the HeartbeatThread class is the core logic for heartbeat or contract renewal. This class is an internal class defined in the DiscoveryClient class. This class is also an implementation class of the Runnable interface, so when it is executed, the run() method will be called. The specific implementation is as follows:

private class HeartbeatThread implements Runnable {
    
    

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

  Through the definition of the HeartbeatThread class, we can know that in the run() method, the renew() method of the DiscoveryClient class is first called to renew the contract, and then if the renewal is successful, the value of the lastSuccessfulHeartbeatTimestamp variable will be modified, that is, the current The time is the time when the last contract renewal was successful.

4. The renew() method of the DiscoveryClient class

  In this method, similar to service registration, heartbeat data is sent by calling the sendHeartBeat() method of the AbstractJerseyEurekaHttpClient class, and then the next step is processed according to the returned result. If the heartbeat renewal returns to the NOT_FOUND state, call the register() method to renew the contract. The specific implementation is as follows:

boolean renew() {
    
    
    EurekaHttpResponse<InstanceInfo> httpResponse;
    try {
    
    
    	//通过调用AbstractJerseyEurekaHttpClient类的sendHeartBeat()方法发送心跳数据
        httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
        logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
        //如果心跳续约返回NOT_FOUND状态,则调用register()方法重新进行续约
        if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
    
    
            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() == Status.OK.getStatusCode();
    } catch (Throwable e) {
    
    
        logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
        return false;
    }
}

5. The sendHeartBeat() method of the AbstractJerseyEurekaHttpClient class

  Similar to the register() method of the AbstractJerseyEurekaHttpClient class, an http request is still sent.

@Override
public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
    
    
   String urlPath = "apps/" + appName + '/' + id;
   ClientResponse response = null;
   try {
    
    
       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);
       response = requestBuilder.put(ClientResponse.class);
       EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));
       if (response.hasEntity() &&
               !HTML.equals(response.getType().getSubtype())) {
    
     //don't try and deserialize random html errors from the server
           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();
       }
   }
}

Guess you like

Origin blog.csdn.net/hou_ge/article/details/111996000