Eureka源码解析服务离线状态变更
环境
- 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端将对应InstanceInfo
的overridenStatus
修改为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个,分别为
status
为CANCEL_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 服务离线状态变更的资料请关注其它相关文章!
相关文章