Eureka源码中如何实现离线状态变更检测?

2026-05-25 22:591阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1918个文字,预计阅读时间需要8分钟。

Eureka源码中如何实现离线状态变更检测?

目录 + 环境

1.服务离线的方式

Eureka源码中如何实现离线状态变更检测?

1.1. 基于Actuator监控器实现 1.2. 直接向Eureka Server提交请求 1.3. 特殊状态:CANCEL_OVERRIDE

2.服务下架源码

2.1. cancelScheduledTasks() 2.2. unregister()

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

目录
  • 环境
  • 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.com/hsfxuebao/s…

1. 服务离线的方式

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

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

1.1 基于Actuator监控器实现

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

  • 服务下架:localhost:端口号/actuator/shutdown 无需请求体
  • 服务下线: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的服务

${server}:${port}/eureka/apps/${serviceName}/${instanceId}

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

${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 服务离线状态变更的资料请关注自由互联其它相关文章!

本文共计1918个文字,预计阅读时间需要8分钟。

Eureka源码中如何实现离线状态变更检测?

目录 + 环境

1.服务离线的方式

Eureka源码中如何实现离线状态变更检测?

1.1. 基于Actuator监控器实现 1.2. 直接向Eureka Server提交请求 1.3. 特殊状态:CANCEL_OVERRIDE

2.服务下架源码

2.1. cancelScheduledTasks() 2.2. unregister()

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

目录
  • 环境
  • 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.com/hsfxuebao/s…

1. 服务离线的方式

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

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

1.1 基于Actuator监控器实现

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

  • 服务下架:localhost:端口号/actuator/shutdown 无需请求体
  • 服务下线: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的服务

${server}:${port}/eureka/apps/${serviceName}/${instanceId}

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

${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 服务离线状态变更的资料请关注自由互联其它相关文章!