Eureka源碼解析服務(wù)離線(xiàn)狀態(tài)變更
環(huán)境
- eureka版本:1.10.11
- Spring Cloud : 2020.0.2
- Spring Boot :2.4.4
測(cè)試代碼:github.com/hsfxuebao/s…
1. 服務(wù)離線(xiàn)的方式
服務(wù)離線(xiàn),即某服務(wù)不能對(duì)外提供服務(wù)了。服務(wù)離線(xiàn)的原因有兩種:服務(wù)下架與服務(wù)下線(xiàn)。
- 服務(wù)下架:表示這個(gè)已經(jīng)被kill掉了,不能對(duì)外提供服務(wù),自己也不能訪(fǎng)問(wèn)
- 服務(wù)下線(xiàn):只是該服務(wù)不能被 eureka server端發(fā)現(xiàn)(不能注冊(cè)),不能被遠(yuǎn)程訪(fǎng)問(wèn),但是可以自己訪(fǎng)問(wèn)自己的服務(wù)
1.1 基于A(yíng)ctuator監(jiān)控器實(shí)現(xiàn)
提交如下POST請(qǐng)求
,可實(shí)現(xiàn)相應(yīng)的服務(wù)離線(xiàn)操作:
- 服務(wù)下架:http://localhost:端口號(hào)/actuator/shutdown 無(wú)需請(qǐng)求體
- 服務(wù)下線(xiàn):http://localhost:端口號(hào)/actuator/serviceregistry 請(qǐng)求體為(該方法稱(chēng)為服務(wù)平滑上下 線(xiàn))
{ "status":"OUT_OF_SERVICE" 或 "UP" }
注意,從Spring Cloud 2020.0.0版本開(kāi)始,服務(wù)平滑上下線(xiàn)的監(jiān)控終端由service-registry變更為 了serviceregistry
1.2 直接向Eureka Server提交請(qǐng)求
可以通過(guò)直接向Eureka Server
提交不同的請(qǐng)求的方式來(lái)實(shí)現(xiàn)指定服務(wù)離線(xiàn)操作:
服務(wù)下架:通過(guò)向eureka server
發(fā)送DELETE請(qǐng)求
來(lái)刪除指定client
的服務(wù)
http://${server}:${port}/eureka/apps/${serviceName}/${instanceId}
服務(wù)下線(xiàn):通過(guò)向eureka server
發(fā)送PUT請(qǐng)求
來(lái)修改指定client的status
,其中${value}
的取值 為:OUT_OF_SERVICE或UP
http://${server}:${port}/eureka/apps/${serviceName}/${instanceId}/stat us?value=${value}
1.3 特殊狀態(tài)CANCEL_OVERRIDE
用戶(hù)提交的狀態(tài)修改請(qǐng)求中指定的狀態(tài),除了InstanceInfo
的內(nèi)置枚舉類(lèi)InstanceStatus
中定義的狀態(tài) 外,還可以是CANCEL_OVERRIDE狀態(tài)
。
若用戶(hù)提交的狀態(tài)為CANCEL_OVERRIDE
,則Client會(huì)通過(guò)Jersey
向Server提交一個(gè)DELETE請(qǐng)求
,用于 在Server端將對(duì)應(yīng)InstanceInfo
的overridenStatus
修改為UNKNWON
,即刪除了原來(lái)的overridenStatus
的狀態(tài)值。此時(shí),該Client發(fā)送的心跳Server是不接收的。Server會(huì)向該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) { } } }
當(dāng)Actuator
監(jiān)聽(tīng)到服務(wù)下架時(shí),會(huì)調(diào)用DiscoveryClient.shutdown()
方法:
// 服務(wù)下架 @PreDestroy @Override public synchronized void shutdown() { if (isShutdown.compareAndSet(false, true)) { logger.info("Shutting down DiscoveryClient ..."); // 注銷(xiāo)狀態(tài)改變監(jiān)聽(tīng)器 if (statusChangeListener != null && applicationInfoManager != null) { applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId()); } // todo 取消定時(shí)任務(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"); } }
有兩個(gè)核心方法,我們分別看一下。
2.1 cancelScheduledTasks()
取消定時(shí)任務(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ù)下架請(qǐng)求。
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 請(qǐng)求 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ù)下架請(qǐng)求:DELETE請(qǐng)求,path:"apps/" + appName + '/' + id;
3. 服務(wù)下線(xiàn)源碼分析(狀態(tài)變更)
Eureka 整合了 Actuator
,可以通過(guò) Actuator
變更實(shí)例在服務(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
是一個(gè)配置類(lèi),往容器中注入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) { // 獲取實(shí)例信息 InstanceInfo info = registration.getApplicationInfoManager().getInfo(); // TODO: howto deal with delete properly? if ("CANCEL_OVERRIDE".equalsIgnoreCase(status)) { // 如果變更狀態(tài)請(qǐng)求傳過(guò)來(lái) status = "CANCEL_OVERRIDE",向服務(wù)端發(fā)起 Jersey 刪除狀態(tài)請(qǐng)求 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)請(qǐng)求 registration.getEurekaClient().setStatus(newStatus, info); }
核心流程有2個(gè),分別為
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 請(qǐng)求 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請(qǐng)求: DELETE請(qǐng)求 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 請(qǐng)求 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)請(qǐng)求:PUT請(qǐng)求,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(); // 獲取本地實(shí)例信息 InstanceInfo info = registration.getEurekaClient().getInstanceInfo(appname, instanceId); HashMap<String, Object> status = new HashMap<>(); if (info != null) { // 從實(shí)例信息取出相應(yīng)狀態(tài)返回 status.put("status", info.getStatus().toString()); status.put("overriddenStatus", info.getOverriddenStatus().toString()); } else { // 如果實(shí)例信息不存在,則返回 UNKNOWN 狀態(tài) status.put("status", UNKNOWN.toString()); } return status; }
參考文章
eureka-0.10.11源碼(注釋?zhuān)?/a>
springcloud-source-study學(xué)習(xí)github地址
以上就是Eureka源碼解析服務(wù)離線(xiàn)狀態(tài)變更的詳細(xì)內(nèi)容,更多關(guān)于Eureka 服務(wù)離線(xiàn)狀態(tài)變更的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Eureka源碼閱讀Client啟動(dòng)入口注冊(cè)續(xù)約及定時(shí)任務(wù)
- Eureka源碼核心類(lèi)預(yù)備知識(shí)
- Eureka源碼閱讀之環(huán)境搭建及工程結(jié)構(gòu)
- SpringCloud?eureka(server)微服務(wù)集群搭建過(guò)程
- spring cloud-給Eureka Server加上安全的用戶(hù)認(rèn)證詳解
- spring-cloud入門(mén)之eureka-server(服務(wù)發(fā)現(xiàn))
- spring cloud將spring boot服務(wù)注冊(cè)到Eureka Server上的方法
- Eureka源碼閱讀解析Server服務(wù)端啟動(dòng)流程實(shí)例
相關(guān)文章
freemarker?jsp?java內(nèi)存方式實(shí)現(xiàn)分頁(yè)示例
這篇文章主要介紹了freemarker?jsp?java內(nèi)存方式實(shí)現(xiàn)分頁(yè)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06Spring?Cloud?Gateway動(dòng)態(tài)路由Apollo實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Spring?Cloud?Gateway動(dòng)態(tài)路由通過(guò)Apollo實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10Java調(diào)用HTTPS接口實(shí)現(xiàn)繞過(guò)SSL認(rèn)證
SSL認(rèn)證是確保通信安全的重要手段,有的時(shí)候?yàn)榱朔奖阏{(diào)用,我們會(huì)繞過(guò)SSL認(rèn)證,這篇文章主要介紹了Java如何調(diào)用HTTPS接口實(shí)現(xiàn)繞過(guò)SSL認(rèn)證,需要的可以參考下2023-11-11springboot普通類(lèi)中如何獲取session問(wèn)題
這篇文章主要介紹了springboot普通類(lèi)中如何獲取session問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01Java Stream map, Collectors(toMap, toLis
這篇文章主要介紹了Java Stream map, Collectors(toMap, toList, toSet, groupingBy, collectingAndThen)使用案例,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09Java Web使用Html5 FormData實(shí)現(xiàn)多文件上傳功能
這篇文章主要介紹了Java Web使用Html5 FormData實(shí)現(xiàn)多文件上傳功能,需要的朋友可以參考下2017-07-07SpringBoot使用thymeleaf實(shí)現(xiàn)一個(gè)前端表格方法詳解
Thymeleaf是一個(gè)現(xiàn)代的服務(wù)器端 Java 模板引擎,適用于 Web 和獨(dú)立環(huán)境。Thymeleaf 的主要目標(biāo)是為您的開(kāi)發(fā)工作流程帶來(lái)優(yōu)雅的自然模板,本文就來(lái)用它實(shí)現(xiàn)一個(gè)前端表格,感興趣的可以了解一下2022-10-10使用MyBatis進(jìn)行數(shù)據(jù)庫(kù)映射的方式
這篇文章主要介紹了使用MyBatis進(jìn)行數(shù)據(jù)庫(kù)映射的方式,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-10-10springmvc+kindeditor文件上傳實(shí)例詳解
這篇文章主要為大家詳細(xì)介紹了springmvc+kindeditor文件上傳實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08