Eureka源码解析服务离线状态变更

2022-10-22,,,,

环境

  • eureka版本:1.10.11
  • spring cloud : 2020.0.2
  • spring boot :2.4.4
    测试代码:github.com/hsfxuebao/s…

1. 服务离线的方式

服务离线,即某服务不能对外提供服务了。服务离线的原因有两种:服务下架与服务下线。

  • 服务下架:表示这个已经被kill掉了,不能对外提供服务,自己也不能访问
  • 服务下线:只是该服务不能被 eureka server端发现(不能注册),不能被远程访问,但是可以自己访问自己的服务

1.1 基于actuator监控器实现

提交如下post请求,可实现相应的服务离线操作:

  • 服务下架:http://localhost:端口号/actuator/shutdown 无需请求体
  • 服务下线:http://localhost:端口号/actuator/serviceregistry 请求体为(该方法称为服务平滑上下 线)
{
    "status":"out_of_service"  或 "up"
}

注意,从spring cloud 2020.0.0版本开始,服务平滑上下线的监控终端由service-registry变更为 了serviceregistry

1.2 直接向eureka server提交请求

可以通过直接向eureka server提交不同的请求的方式来实现指定服务离线操作:

服务下架:通过向eureka server发送delete请求来删除指定client的服务

http://${server}:${port}/eureka/apps/${servicename}/${instanceid}

服务下线:通过向eureka server发送put请求来修改指定client的status,其中${value}的取值 为:out_of_service或up

http://${server}:${port}/eureka/apps/${servicename}/${instanceid}/stat us?value=${value}

1.3 特殊状态cancel_override

用户提交的状态修改请求中指定的状态,除了instanceinfo的内置枚举类instancestatus中定义的状态 外,还可以是cancel_override状态

若用户提交的状态为cancel_override,则client会通过jersey向server提交一个delete请求,用于 在server端将对应instanceinfooverridenstatus修改为unknwon,即删除了原来的overridenstatus 的状态值。此时,该client发送的心跳server是不接收的。server会向该client返回404

2. 服务下架源码

public class eurekaclientautoconfiguration {
   @configuration(proxybeanmethods = false)
   @conditionalonrefreshscope
   protected static class refreshableeurekaclientconfiguration {
      @bean(destroymethod = "shutdown")
      @conditionalonmissingbean(value = eurekaclient.class, search = searchstrategy.current)
      @org.springframework.cloud.context.config.annotation.refreshscope
      @lazy
      public eurekaclient eurekaclient(applicationinfomanager manager, eurekaclientconfig config,
            eurekainstanceconfig instance, @autowired(required = false) healthcheckhandler healthcheckhandler) {
      }
  }
}

actuator监听到服务下架时,会调用discoveryclient.shutdown()方法:

// 服务下架
@predestroy
@override
public synchronized void shutdown() {
    if (isshutdown.compareandset(false, true)) {
        logger.info("shutting down discoveryclient ...");
        // 注销状态改变监听器
        if (statuschangelistener != null && applicationinfomanager != null) {
            applicationinfomanager.unregisterstatuschangelistener(statuschangelistener.getid());
        }
        // todo 取消定时任务
        cancelscheduledtasks();
        // if appinfo was registered
        if (applicationinfomanager != null
                && clientconfig.shouldregisterwitheureka()
                && clientconfig.shouldunregisteronshutdown()) {
            applicationinfomanager.setinstancestatus(instancestatus.down);
            // todo 服务下架
            unregister();
        }
        if (eurekatransport != null) {
            eurekatransport.shutdown();
        }
        heartbeatstalenessmonitor.shutdown();
        registrystalenessmonitor.shutdown();
        monitors.unregisterobject(this);
        logger.info("completed shut down of discoveryclient");
    }
}

有两个核心方法,我们分别看一下。

2.1 cancelscheduledtasks()

取消定时任务。

private void cancelscheduledtasks() {
    if (instanceinforeplicator != null) {
        instanceinforeplicator.stop();
    }
    if (heartbeatexecutor != null) {
        heartbeatexecutor.shutdownnow();
    }
    if (cacherefreshexecutor != null) {
        cacherefreshexecutor.shutdownnow();
    }
    if (scheduler != null) {
        scheduler.shutdownnow();
    }
    if (cacherefreshtask != null) {
        cacherefreshtask.cancel();
    }
    if (heartbeattask != null) {
        heartbeattask.cancel();
    }
}

2.2 unregister()

发送服务下架请求。

void unregister() {
    // it can be null if shouldregisterwitheureka == false
    if(eurekatransport != null && eurekatransport.registrationclient != null) {
        try {
            logger.info("unregistering ...");
            eurekahttpresponse<void> httpresponse = eurekatransport.registrationclient.cancel(instanceinfo.getappname(), instanceinfo.getid());
            logger.info(prefix + "{} - deregister  status: {}", apppathidentifier, httpresponse.getstatuscode());
        } catch (exception e) {
            logger.error(prefix + "{} - de-registration failed{}", apppathidentifier, e.getmessage(), e);
        }
    }
}
@override
public eurekahttpresponse<void> cancel(string appname, string id) {
    string urlpath = "apps/" + appname + '/' + id;
    clientresponse response = null;
    try {
        builder resourcebuilder = jerseyclient.resource(serviceurl).path(urlpath).getrequestbuilder();
        addextraheaders(resourcebuilder);
        // delete 请求
        response = resourcebuilder.delete(clientresponse.class);
        return aneurekahttpresponse(response.getstatus()).headers(headersof(response)).build();
    } finally {
        if (logger.isdebugenabled()) {
            logger.debug("jersey http delete {}/{}; statuscode={}", serviceurl, urlpath, response == null ? "n/a" : response.getstatus());
        }
        if (response != null) {
            response.close();
        }
    }
}

服务下架请求:delete请求,path:"apps/" + appname + '/' + id;

3. 服务下线源码分析(状态变更)

eureka 整合了 actuator ,可以通过 actuator 变更实例在服务端的状态。spring cloud整合eureka,入口在 spring-cloud-common下的spring.factories:

@configuration(proxybeanmethods = false)
public class serviceregistryautoconfiguration {
   @conditionalonbean(serviceregistry.class)
   @conditionalonclass(endpoint.class)
   protected class serviceregistryendpointconfiguration {
      @autowired(required = false)
      private registration registration;
      @bean
      @conditionalonavailableendpoint
      public serviceregistryendpoint serviceregistryendpoint(serviceregistry serviceregistry) {
         serviceregistryendpoint endpoint = new serviceregistryendpoint(serviceregistry);
         endpoint.setregistration(this.registration);
         return endpoint;
      }
   }
}

serviceregistryautoconfiguration是一个配置类,往容器中注入serviceregistryendpoint

@endpoint(id = "serviceregistry")
public class serviceregistryendpoint {
    ...
   @writeoperation
   public responseentity<?> setstatus(string status) {
      assert.notnull(status, "status may not by null");
      if (this.registration == null) {
         return responseentity.status(httpstatus.not_found).body("no registration found");
      }
      // 变更状态
      this.serviceregistry.setstatus(this.registration, status);
      return responseentity.ok().build();
   }
   @readoperation
   public responseentity getstatus() {
      if (this.registration == null) {
         return responseentity.status(httpstatus.not_found).body("no registration found");
      }
       // 获取状态
      return responseentity.ok().body(this.serviceregistry.getstatus(this.registration));
   }
}

3.1 变更状态

核心方法serviceregistry#setstatus:

@override
public void setstatus(eurekaregistration registration, string status) {
   // 获取实例信息
   instanceinfo info = registration.getapplicationinfomanager().getinfo();
   // todo: howto deal with delete properly?
   if ("cancel_override".equalsignorecase(status)) {
      // 如果变更状态请求传过来 status = "cancel_override",向服务端发起 jersey 删除状态请求
      registration.geteurekaclient().canceloverridestatus(info);
      return;
   }
   // todo: howto deal with status types across discovery systems?
   instanceinfo.instancestatus newstatus = instanceinfo.instancestatus.toenum(status);
   // 如果不是删除状态,则向服务端发起 jersey 变更状态请求
   registration.geteurekaclient().setstatus(newstatus, info);
}

核心流程有2个,分别为

statuscancel_override:

public void canceloverridestatus(instanceinfo info) {
   geteurekahttpclient().deletestatusoverride(info.getappname(), info.getid(), info);
}
@override
public eurekahttpresponse<void> deletestatusoverride(string appname, string id, instanceinfo info) {
    string urlpath = "apps/" + appname + '/' + id + "/status";
    clientresponse response = null;
    try {
        builder requestbuilder = jerseyclient.resource(serviceurl)
                .path(urlpath)
                .queryparam("lastdirtytimestamp", info.getlastdirtytimestamp().tostring())
                .getrequestbuilder();
        addextraheaders(requestbuilder);
        // delete 请求
        response = requestbuilder.delete(clientresponse.class);
        return aneurekahttpresponse(response.getstatus()).headers(headersof(response)).build();
    } finally {
        if (logger.isdebugenabled()) {
            logger.debug("jersey http delete {}/{}; statuscode={}", serviceurl, urlpath, response == null ? "n/a" : response.getstatus());
        }
        if (response != null) {
            response.close();
        }
    }
}

删除deletestatusoverride请求: delete请求 path:"apps/" + appname + '/' + id + "/status"

直接调用setstatus()方法:

@override
public eurekahttpresponse<void> statusupdate(string appname, string id, instancestatus newstatus, instanceinfo info) {
    string urlpath = "apps/" + appname + '/' + id + "/status";
    clientresponse response = null;
    try {
        builder requestbuilder = jerseyclient.resource(serviceurl)
                .path(urlpath)
                .queryparam("value", newstatus.name())
                .queryparam("lastdirtytimestamp", info.getlastdirtytimestamp().tostring())
                .getrequestbuilder();
        addextraheaders(requestbuilder);
        // put 请求
        response = requestbuilder.put(clientresponse.class);
        return aneurekahttpresponse(response.getstatus()).headers(headersof(response)).build();
    } finally {
        if (logger.isdebugenabled()) {
            logger.debug("jersey http put {}/{}; statuscode={}", serviceurl, urlpath, response == null ? "n/a" : response.getstatus());
        }
        if (response != null) {
            response.close();
        }
    }
}

变更状态请求:put请求,path为 :"apps/" + appname + '/' + id + "/status"

3.2 获取状态

// eurekaserviceregistry.class
public object getstatus(eurekaregistration registration) {
    string appname = registration.getapplicationinfomanager().getinfo().getappname();
    string instanceid = registration.getapplicationinfomanager().getinfo().getid();
    // 获取本地实例信息
    instanceinfo info = registration.geteurekaclient().getinstanceinfo(appname,
	    instanceid);
    hashmap<string, object> status = new hashmap<>();
    if (info != null) {
        // 从实例信息取出相应状态返回
	status.put("status", info.getstatus().tostring());
	status.put("overriddenstatus", info.getoverriddenstatus().tostring());
    }
    else {
        // 如果实例信息不存在,则返回 unknown 状态
	status.put("status", unknown.tostring());
    }
    return status;
}

参考文章

eureka-0.10.11源码(注释)

springcloud-source-study学习github地址

以上就是eureka源码解析服务离线状态变更的详细内容,更多关于eureka 服务离线状态变更的资料请关注其它相关文章!

《Eureka源码解析服务离线状态变更.doc》

下载本文的Word格式文档,以方便收藏与打印。