Eureka源碼解析服務(wù)離線狀態(tài)變更
環(huán)境
- eureka版本:1.10.11
- Spring Cloud : 2020.0.2
- Spring Boot :2.4.4
測試代碼:github.com/hsfxuebao/s…
1. 服務(wù)離線的方式
服務(wù)離線,即某服務(wù)不能對外提供服務(wù)了。服務(wù)離線的原因有兩種:服務(wù)下架與服務(wù)下線。
- 服務(wù)下架:表示這個已經(jīng)被kill掉了,不能對外提供服務(wù),自己也不能訪問
- 服務(wù)下線:只是該服務(wù)不能被 eureka server端發(fā)現(xiàn)(不能注冊),不能被遠程訪問,但是可以自己訪問自己的服務(wù)
1.1 基于Actuator監(jiān)控器實現(xiàn)
提交如下POST請求,可實現(xiàn)相應的服務(wù)離線操作:
- 服務(wù)下架:http://localhost:端口號/actuator/shutdown 無需請求體
- 服務(wù)下線:http://localhost:端口號/actuator/serviceregistry 請求體為(該方法稱為服務(wù)平滑上下 線)
{
"status":"OUT_OF_SERVICE" 或 "UP"
}
注意,從Spring Cloud 2020.0.0版本開始,服務(wù)平滑上下線的監(jiān)控終端由service-registry變更為 了serviceregistry
1.2 直接向Eureka Server提交請求
可以通過直接向Eureka Server提交不同的請求的方式來實現(xiàn)指定服務(wù)離線操作:
服務(wù)下架:通過向eureka server發(fā)送DELETE請求來刪除指定client的服務(wù)
http://${server}:${port}/eureka/apps/${serviceName}/${instanceId}
服務(wù)下線:通過向eureka server發(fā)送PUT請求來修改指定client的status,其中${value}的取值 為:OUT_OF_SERVICE或UP
http://${server}:${port}/eureka/apps/${serviceName}/${instanceId}/stat us?value=${value}
1.3 特殊狀態(tài)CANCEL_OVERRIDE
用戶提交的狀態(tài)修改請求中指定的狀態(tài),除了InstanceInfo的內(nèi)置枚舉類InstanceStatus中定義的狀態(tài) 外,還可以是CANCEL_OVERRIDE狀態(tài)。
若用戶提交的狀態(tài)為CANCEL_OVERRIDE,則Client會通過Jersey向Server提交一個DELETE請求,用于 在Server端將對應InstanceInfo的overridenStatus修改為UNKNWON,即刪除了原來的overridenStatus 的狀態(tài)值。此時,該Client發(fā)送的心跳Server是不接收的。Server會向該Client返回404。
2. 服務(wù)下架源碼
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監(jiān)聽到服務(wù)下架時,會調(diào)用DiscoveryClient.shutdown()方法:
// 服務(wù)下架
@PreDestroy
@Override
public synchronized void shutdown() {
if (isShutdown.compareAndSet(false, true)) {
logger.info("Shutting down DiscoveryClient ...");
// 注銷狀態(tài)改變監(jiān)聽器
if (statusChangeListener != null && applicationInfoManager != null) {
applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
}
// todo 取消定時任務(wù)
cancelScheduledTasks();
// If APPINFO was registered
if (applicationInfoManager != null
&& clientConfig.shouldRegisterWithEureka()
&& clientConfig.shouldUnregisterOnShutdown()) {
applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
// todo 服務(wù)下架
unregister();
}
if (eurekaTransport != null) {
eurekaTransport.shutdown();
}
heartbeatStalenessMonitor.shutdown();
registryStalenessMonitor.shutdown();
Monitors.unregisterObject(this);
logger.info("Completed shut down of DiscoveryClient");
}
}
有兩個核心方法,我們分別看一下。
2.1 cancelScheduledTasks()
取消定時任務(wù)。
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()
發(fā)送服務(wù)下架請求。
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();
}
}
}
服務(wù)下架請求:DELETE請求,path:"apps/" + appName + '/' + id;
3. 服務(wù)下線源碼分析(狀態(tài)變更)
Eureka 整合了 Actuator ,可以通過 Actuator 變更實例在服務(wù)端的狀態(tài)。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");
}
// 變更狀態(tài)
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");
}
// 獲取狀態(tài)
return ResponseEntity.ok().body(this.serviceRegistry.getStatus(this.registration));
}
}
3.1 變更狀態(tài)
核心方法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)) {
// 如果變更狀態(tài)請求傳過來 status = "CANCEL_OVERRIDE",向服務(wù)端發(fā)起 Jersey 刪除狀態(tài)請求
registration.getEurekaClient().cancelOverrideStatus(info);
return;
}
// TODO: howto deal with status types across discovery systems?
InstanceInfo.InstanceStatus newStatus = InstanceInfo.InstanceStatus.toEnum(status);
// 如果不是刪除狀態(tài),則向服務(wù)端發(fā)起 Jersey 變更狀態(tài)請求
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"
直接調(diào)用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();
}
}
}
變更狀態(tài)請求:PUT請求,path為 :"apps/" + appName + '/' + id + "/status"
3.2 獲取狀態(tài)
// 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) {
// 從實例信息取出相應狀態(tài)返回
status.put("status", info.getStatus().toString());
status.put("overriddenStatus", info.getOverriddenStatus().toString());
}
else {
// 如果實例信息不存在,則返回 UNKNOWN 狀態(tài)
status.put("status", UNKNOWN.toString());
}
return status;
}
參考文章
springcloud-source-study學習github地址
以上就是Eureka源碼解析服務(wù)離線狀態(tài)變更的詳細內(nèi)容,更多關(guān)于Eureka 服務(wù)離線狀態(tài)變更的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
freemarker?jsp?java內(nèi)存方式實現(xiàn)分頁示例
這篇文章主要介紹了freemarker?jsp?java內(nèi)存方式實現(xiàn)分頁示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06
Spring?Cloud?Gateway動態(tài)路由Apollo實現(xiàn)詳解
這篇文章主要為大家介紹了Spring?Cloud?Gateway動態(tài)路由通過Apollo實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10
Java調(diào)用HTTPS接口實現(xiàn)繞過SSL認證
SSL認證是確保通信安全的重要手段,有的時候為了方便調(diào)用,我們會繞過SSL認證,這篇文章主要介紹了Java如何調(diào)用HTTPS接口實現(xiàn)繞過SSL認證,需要的可以參考下2023-11-11
Java Stream map, Collectors(toMap, toLis
這篇文章主要介紹了Java Stream map, Collectors(toMap, toList, toSet, groupingBy, collectingAndThen)使用案例,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09
Java Web使用Html5 FormData實現(xiàn)多文件上傳功能
這篇文章主要介紹了Java Web使用Html5 FormData實現(xiàn)多文件上傳功能,需要的朋友可以參考下2017-07-07
SpringBoot使用thymeleaf實現(xiàn)一個前端表格方法詳解
Thymeleaf是一個現(xiàn)代的服務(wù)器端 Java 模板引擎,適用于 Web 和獨立環(huán)境。Thymeleaf 的主要目標是為您的開發(fā)工作流程帶來優(yōu)雅的自然模板,本文就來用它實現(xiàn)一個前端表格,感興趣的可以了解一下2022-10-10

