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