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

目录
  • 环境
  • 1. 服务离线的方式
    • 1.1 基于Actuator监控器实现
    • 1.2 直接向Eureka Server提交请求
    • 1.3 特殊状态CANCEL_OVERRIDE
  • 2. 服务下架源码
    • 2.1 cancelScheduledTasks()
    • 2.2 unregister()
  • 3. 服务下线源码分析(状态变更)
    • 3.1 变更状态
    • 3.2 获取状态

环境

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 服务离线状态变更的资料请关注我们其它相关文章!

(0)

相关推荐

  • SpringCloud eureka(server)微服务集群搭建过程

    目录 工作原理: eureka 高可用集群 项目创建: Maven 依赖 本地hosts文件修改 启动服务测试 工作原理: Spring Cloud框架下的服务发现Eureka包含两个组件 分别是: Eureka Server与Eureka ClientEureka Server,也称为服务注册中心.各个服务启动后,会在Eureka Server中进行注册,这样Eureka Server的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到.Eureka Client

  • spring cloud-给Eureka Server加上安全的用户认证详解

    前言 在前面的一篇文章中spring cloud中启动Eureka Server我们启动了Eureka Server,然后在浏览器中输入http://localhost:8761/后,直接回车,就进入了spring cloud的服务治理页面,这么做在生产环境是极不安全的,下面,我们就给Eureka Server加上安全的用户认证. 一.添加spring-security支持 <dependency> <groupId>org.springframework.boot</gro

  • spring cloud将spring boot服务注册到Eureka Server上的方法

    开篇: 我们将前面的springboot整合H2内存数据库,实现单元测试与数据库无关性提供的Restful服务注册到spring cloud的Eureka Server上. 一.引入Eureka的Client </dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</ar

  • Eureka源码阅读Client启动入口注册续约及定时任务

    目录 引言 1.环境 2. Spring Cloud整合Eureka Client 启动入口 2.1 封装配置文件的类 2.1.1 EurekaClientConfigBean 2.1.2 EurekaInstanceConfigBean 2.2 EurekaClient 2.2.1 ApplicationInfoManager 2.2.2 EurekaClient 2.3 小结 3. DiscoveryClient类的解析 3.1 DiscoveryClient 作用 3.2 Discover

  • Eureka源码阅读解析Server服务端启动流程实例

    目录 环境 1.spring cloud整合eureka server demo 1.1 新建spring boot项目 pom.xml文件添加 配置文件 1.2 启动类 1.3 启动 2. spring cloud自动装配eureka server源码解析 2.1 @EnableEurekaServer注解 2.2 EurekaServerAutoConfiguration 2.2.1 查找starter 自动装配类的技巧 2.2.2 EurekaServerAutoConfiguration

  • Eureka源码阅读之环境搭建及工程结构

    目录 1. 源码阅读环境搭建 1.1 源码下载: 2. 工程结构速览 3. 调试须知 1. 源码阅读环境搭建 ide:IntelliJ IDEA 2020.1 包管理:gradle eureka版本:1.10.11 Spring Cloud : 2020.0.2 Spring Boot :2.4.4 1.1 源码下载: 下载完源码之后,需要更改一下几个地方: build.gradle增加阿里云镜像仓库,将如下插件版本改一下,否则导入idea会报错: maven { url 'https://ma

  • spring-cloud入门之eureka-server(服务发现)

    前言 Eureka是一个服务发现和注册框架,细的来说,我们可以分为eureka-server(服务发现)和eureka-client(服务注册)两个,本次我们对eureka-server(服务发现)做一个项目搭建,作为spring-cloud的开篇. 开源地址:https://github.com/bigbeef 项目结构 maven结构大家应该都清楚(不清楚的需要补一补,百度关于maven的文章不计其数),下面我们来看一看这些关键文件的配置 代码编写 cppba-spring-cloud >

  • Eureka源码核心类预备知识

    目录 1. 前言 1.1 Eureka的异地多活 1.2 Region和Zone 1.3 Region和AZ需求 2.核心类 2.1 客户端核心类 2.1.1 InstanceInfo-实例信息类 2.1.2 Application 2.1.3 Applications 2.2 服务端 2.2.1 AbstractInstanceRegistry 2.2.2 PeerAwareInstanceRegistryImpl 3. Jersey通信框架 1. 前言 1.1 Eureka的异地多活 异地多

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

    目录 环境 1. 服务离线的方式 1.1 基于Actuator监控器实现 1.2 直接向Eureka Server提交请求 1.3 特殊状态CANCEL_OVERRIDE 2. 服务下架源码 2.1 cancelScheduledTasks() 2.2 unregister() 3. 服务下线源码分析(状态变更) 3.1 变更状态 3.2 获取状态 环境 eureka版本:1.10.11 Spring Cloud : 2020.0.2 Spring Boot :2.4.4测试代码:github.

  • Flink状态和容错源码解析

    目录 引言 概述 State Keyed State 状态实例管理及数据存储 HeapKeyedStateBackend RocksDBKeyedStateBackend OperatorState 上层封装 总结 引言 计算模型 DataStream基础框架 事件时间和窗口 状态和容错 部署&调度 存储体系 底层支撑 Flink中提供了State(状态)这个概念来保存中间计算结果和缓存数据,按照不同的场景,Flink提供了多种不同类型的State,同时为了实现Exactly once的语义,F

  • Idea导入eureka源码实现过程解析

    通过GitHub获取Eureka源码 进入git bash命令行,自己找个目录,用来存放eureka源码,然后在目录里面,执行git clone  https://github.com/Netflix/eureka.git,就可以了,这个是需要点时间的,稍微有点慢,你等一会儿好了. git clone https://github.com/Netflix/eureka.git 获取eureka项目依赖 然后在eureka目录中,直接双击gradlew.bat就可以,这个是人家给你提供的命令,直接

  • vue loadmore 组件滑动加载更多源码解析

    上一篇讲到在项目中使用上拉加载更多组件,但是由于实际项目开发中由于需求变更或者说在webview中上拉加载有些机型在上拉时候会把webview也一起上拉导致上拉加载不灵敏等问题,我们有时候也会换成滑动到底部自动加载的功能. 既然都是加载更多,很多代码思想势必相似,主要区别在于上拉和滑动到底部这个操作上,所以,我们需要注意: 上拉加载是point指针touch触摸事件,现在因为是滑动加载,需要添加scroll事件去监听然后执行相应回调 上拉加载主要计算触摸滚动距离,滑动加载主要计算containe

  • Android源码解析之截屏事件流程

    今天这篇文章我们主要讲一下Android系统中的截屏事件处理流程.用过android系统手机的同学应该都知道,一般的android手机按下音量减少键和电源按键就会触发截屏事件(国内定制机做个修改的这里就不做考虑了).那么这里的截屏事件是如何触发的呢?触发之后android系统是如何实现截屏操作的呢?带着这两个问题,开始我们的源码阅读流程. 我们知道这里的截屏事件是通过我们的按键操作触发的,所以这里就需要我们从android系统的按键触发模块开始看起,由于我们在不同的App页面,操作音量减少键和电

  • python wsgiref源码解析

    python web开发中http请求的处理流程通常是: web-browser , web-server , wsgi 和 web-application四个环节, 我们学习过基于bottle实现的web-application,也学习了http.server.再完成python3源码中自带的wsgiref的库,就可以拼接最后一个环节wsgi.本文会分下面几个部分: wsgi相关概念 cgi示例 wsgiref源码 wsgi小结 小技巧 wsgi 相关概念 CGI CGI(Common Gat

  • skywalking源码解析javaAgent工具ByteBuddy应用

    目录 前言 Agent模块源码分析 第一步,加载配置信息: 第二步,加载需要被Agent的插件: 第三步,加载Agent端所需要的服务: 第四步,使用ByteBuddy增强插件定义的所有class: javaAgent的应用 BYTEBUDDY应用 通过委托实现Instrumentation 实现方法级别的安全性 实现安全功能的JAVAAGENT 前言 关于skywalking请看我上一篇博文,skywalking分布式服务调用链路追踪APM应用监控 其使用javaAgent技术,使得应用接入监

随机推荐