版本:
SpringBoot 1.5.4.RELEASE
SpringCloud Dalston.RELEASE
本文主要討論的是微服務(wù)注冊到Eureka注冊中心呛凶,并使用Zuul網(wǎng)關(guān)負載訪問的情況尼荆,如何停機可以使用戶無感知藻烤。
方式一:kill -9 java進程id【不建議】
kill -9
屬于強殺進程哨颂,首先微服務(wù)正在執(zhí)行的任務(wù)被強制中斷了祭钉;其次霸饲,沒有通過Eureka注冊中心服務(wù)下線铃肯,Zuul網(wǎng)關(guān)作為Eureka Client仍保存這個服務(wù)的路由信息患亿,會繼續(xù)調(diào)用服務(wù),Http請求返回500押逼,后臺異常是Connection refuse連接拒絕
這種情況默認最長需要等待:
90s(微服務(wù)在Eureka Server上租約到期)
+
30s(Eureka Server服務(wù)列表刷新到只讀緩存ReadOnlyMap的時間步藕,Eureka Client默認讀此緩存)
+
30s(Zuul作為Eureka Client默認每30秒拉取一次服務(wù)列表)
+
30s(Ribbon默認動態(tài)刷新其ServerList的時間間隔)
= 180s充包,即 3分鐘
總結(jié):
此種方式既會導(dǎo)致正在執(zhí)行中的任務(wù)無法執(zhí)行完谭贪,又會導(dǎo)致服務(wù)沒有從Eureka Server摘除,并給Eureka Client時間刷新到服務(wù)列表中捆,導(dǎo)致了通過Zuul仍然調(diào)用已停掉服務(wù)報500錯誤的情況漂彤,不推薦雾消。
方式二:kill -15 java進程id 或 直接使用/shutdown 端點【不建議】
kill 與/shutdown 的含義
首先灾搏,kill
等于kill -15
,根據(jù)man kill
的描述信息
The command kill sends the specified signal to the specified process or process group. If no signal is specified, the TERM signal is sent.
即kill沒有執(zhí)行信號等同于TERM(終止立润,termination)
而kill -l
查看信號編號與信號之間的關(guān)系狂窑,kill -15
就是 SIGTERM,TERM信號
給JVM進程發(fā)送TERM終止信號時桑腮,會調(diào)用其注冊的 Shutdown Hook泉哈,當SpringBoot微服務(wù)啟動時也注冊了 Shutdown Hook
而直接調(diào)用/shutdown
端點本質(zhì)和使用 Shutdown Hook是一樣的,所以無論是使用kill
或 kill -15
破讨,還是直接使用/shutdown
端點丛晦,都會調(diào)用到JVM注冊的Shutdown Hook
注意:
啟用 /shutdown端點,需要如下配置
endpoints.shutdown.enabled = true
endpoints.shutdown.sensitive = false
所有問題都導(dǎo)向了 Shutdown Hook會執(zhí)行什么添忘?采呐?
Spring注冊的Shutdown Hook
通過查詢項目組使用Runtime.getRuntime().addShutdownHook(Thread shutdownHook)
的地方,發(fā)現(xiàn)ribbon注冊了一些Shutdown Hook搁骑,但這不是我們這次關(guān)注的斧吐,我們關(guān)注的是Spring的應(yīng)用上下文抽象類AbstractApplicationContext
注冊了針對整個Spring容器的Shutdown Hook,在執(zhí)行Shutdown Hook時的邏輯在 AbstractApplicationContext#doClose()
//## org.springframework.context.support.AbstractApplicationContext#registerShutdownHook
/**
* Register a shutdown hook with the JVM runtime, closing this context
* on JVM shutdown unless it has already been closed at that time.
* <p>Delegates to {@code doClose()} for the actual closing procedure.
* @see Runtime#addShutdownHook
* @see #close()
* @see #doClose()
*/
@Override
public void registerShutdownHook() {
if (this.shutdownHook == null) {
// No shutdown hook registered yet.
// 注冊shutdownHook仲器,線程真正調(diào)用的是 doClose()
this.shutdownHook = new Thread() {
@Override
public void run() {
synchronized (startupShutdownMonitor) {
doClose();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
//## org.springframework.context.support.AbstractApplicationContext#doClose
/**
* Actually performs context closing: publishes a ContextClosedEvent and
* destroys the singletons in the bean factory of this application context.
* <p>Called by both {@code close()} and a JVM shutdown hook, if any.
* @see org.springframework.context.event.ContextClosedEvent
* @see #destroyBeans()
* @see #close()
* @see #registerShutdownHook()
*/
protected void doClose() {
if (this.active.get() && this.closed.compareAndSet(false, true)) {
if (logger.isInfoEnabled()) {
logger.info("Closing " + this);
}
// 注銷注冊的MBean
LiveBeansView.unregisterApplicationContext(this);
try {
// Publish shutdown event.
// 發(fā)送ContextClosedEvent事件煤率,會有對應(yīng)此事件的Listener處理相應(yīng)的邏輯
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// Stop all Lifecycle beans, to avoid delays during individual destruction.
// 調(diào)用所有 Lifecycle bean 的 stop() 方法
try {
getLifecycleProcessor().onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
// Destroy all cached singletons in the context's BeanFactory.
// 銷毀所有單實例bean
destroyBeans();
// Close the state of this context itself.
closeBeanFactory();
// Let subclasses do some final clean-up if they wish...
// 調(diào)用子類的 onClose() 方法,比如 EmbeddedWebApplicationContext#onClose()
onClose();
this.active.set(false);
}
}
AbstractApplicationContext#doClose()
的關(guān)鍵點在于
- publishEvent(new ContextClosedEvent(this)): 發(fā)送ContextClosedEvent事件乏冀,會有對應(yīng)此事件的Listener處理相應(yīng)的邏輯
- getLifecycleProcessor().onClose(): 調(diào)用所有 Lifecycle bean 的 stop() 方法
而ContextClosedEvent事件的Listener有很多蝶糯,實現(xiàn)了Lifecycle生命周期接口的bean也很多,但其中我們只關(guān)心一個辆沦,即 EurekaAutoServiceRegistration
昼捍,它即監(jiān)聽了ContextClosedEvent事件,也實現(xiàn)了Lifecycle接口
EurekaAutoServiceRegistration的stop()事件
//## org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration
public class EurekaAutoServiceRegistration implements AutoServiceRegistration, SmartLifecycle, Ordered {
// lifecycle接口的 stop()
@Override
public void stop() {
this.serviceRegistry.deregister(this.registration);
this.running.set(false); // 設(shè)置liffecycle的running標示為false
}
// ContextClosedEvent事件監(jiān)聽器
@EventListener(ContextClosedEvent.class)
public void onApplicationEvent(ContextClosedEvent event) {
// register in case meta data changed
stop();
}
}
如上可以看到肢扯,EurekaAutoServiceRegistration
中對 ContextClosedEvent事件 和 Lifecycle接口 的實現(xiàn)都調(diào)用了stop()
方法妒茬,雖然都調(diào)用了stop()
方法,但由于各種對于狀態(tài)的判斷導(dǎo)致不會重復(fù)執(zhí)行蔚晨,如
- Lifecycle的running標示置為false乍钻,就不會調(diào)用到此Lifecycle#stop()
-
EurekaServiceRegistry#deregister()
方法包含將實例狀態(tài)置為DOWN 和 EurekaClient#shutdown() 兩個操作,其中狀態(tài)置為DOWN一次后铭腕,下一次只要狀態(tài)不變就不會觸發(fā)狀態(tài)復(fù)制請求银择;EurekaClient#shutdown() 之前也會判斷AtomicBoolean isShutdown
標志位
下面具體看看EurekaServiceRegistry#deregister()
方法
EurekaServiceRegistry#deregister() 注銷
//## org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry#deregister
@Override
public void deregister(EurekaRegistration reg) {
if (reg.getApplicationInfoManager().getInfo() != null) {
if (log.isInfoEnabled()) {
log.info("Unregistering application " + reg.getInstanceConfig().getAppname()
+ " with eureka with status DOWN");
}
// 更改實例狀態(tài),會立即觸發(fā)狀態(tài)復(fù)制請求
reg.getApplicationInfoManager().setInstanceStatus(InstanceInfo.InstanceStatus.DOWN);
//TODO: on deregister or on context shutdown
// 關(guān)閉EurekaClient
reg.getEurekaClient().shutdown();
}
}
主要涉及兩步:
-
更新Instance狀態(tài)為 DOWN: 更新狀態(tài)會觸發(fā)
StatusChangeListener
監(jiān)聽器累舷,狀態(tài)復(fù)制器InstanceInfoReplicator
會向Eureka Server發(fā)送狀態(tài)更新請求浩考。實際上狀態(tài)更新和Eureka Client第一次注冊時都是調(diào)用的DiscoveryClient.register()
,都是發(fā)送POST /eureka/apps/appID
請求到Eureka Server被盈,只不過請求Body中的Instance實例狀態(tài)不同怀挠。執(zhí)行完此步驟后析蝴,Eureka Server頁面上變成
-
EurekaClient.shutdown(): 整個Eureka Client的關(guān)閉操作包含以下幾步
@PreDestroy @Override public synchronized void shutdown() { if (isShutdown.compareAndSet(false, true)) { logger.info("Shutting down DiscoveryClient ..."); // 1、注銷所有 StatusChangeListener if ( statusChangeListener != null && applicationInfoManager != null) { applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId()); } // 2绿淋、停掉所有定時線程(實例狀態(tài)復(fù)制、心跳尝盼、client緩存刷新吞滞、監(jiān)督線程) cancelScheduledTasks(); // If APPINFO was registered // 3、向Eureka Server注銷實例 if (applicationInfoManager != null && clientConfig.shouldRegisterWithEureka()) { applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN); unregister(); } // 4盾沫、各種shutdown關(guān)閉 if (eurekaTransport != null) { eurekaTransport.shutdown(); } heartbeatStalenessMonitor.shutdown(); registryStalenessMonitor.shutdown(); logger.info("Completed shut down of DiscoveryClient"); } }
其中應(yīng)關(guān)注
unregister()
注銷裁赠,其調(diào)用AbstractJerseyEurekaHttpClient#cancel()
方法,向Eureka Server發(fā)送DELETE /eureka/v2/apps/appID/instanceID
請求赴精,DELETE請求成功后佩捞,Eureka Server頁面上服務(wù)列表就沒有當前實例信息了。注意: 由于在注銷上一步已經(jīng)停掉了定時心跳線程蕾哟,否則注銷后的下次心跳又會導(dǎo)致服務(wù)上線
總結(jié)
使用kill
一忱、kill -15
或 /shutdown
端點都會調(diào)用Shutdown Hook,觸發(fā)Eureka Instance實例的注銷操作谭确,這一步是沒有問題的帘营,優(yōu)雅下線的第一步就是從Eureka注冊中心注銷實例,但關(guān)鍵問題是shutdown操作除了注銷Eureka實例逐哈,還會馬上停止服務(wù)芬迄,而此時無論Eureka Server端,Zuul作為Eureka Client端都存在陳舊的緩存還未刷新昂秃,服務(wù)列表中仍然有注銷下線的服務(wù)禀梳,通過zuul再次調(diào)用報500錯誤,后臺是connection refuse連接拒絕異常肠骆,故不建議使用
另外算途,由于unregister
注銷操作涉及狀態(tài)更新DOWN 和 注銷下線 兩步操作,且是分兩個線程執(zhí)行的哗戈,實際注銷時郊艘,根據(jù)兩個線程執(zhí)行完成的先后順序,最終在Eureka Server上體現(xiàn)的結(jié)果不同唯咬,但最終效果是相同的纱注,經(jīng)過一段時間的緩存刷新后,此服務(wù)實例不會再被調(diào)用
- 狀態(tài)更新DOWN先結(jié)束胆胰,注銷實例后結(jié)束: Eureka Server頁面清除此服務(wù)實例信息
- 注銷實例先結(jié)束狞贱,狀態(tài)更新DOWN后結(jié)束: Eureka Server頁面顯示此服務(wù)實例狀態(tài)為DOWN
方式三:/pause 端點【可用,但有缺陷】
/pause 端點
首先蜀涨,啟用/pause
端點需要如下配置
endpoints.pause.enabled = true
endpoints.pause.sensitive = false
PauseEndpoint
是RestartEndPoint
的內(nèi)部類
//## Restart端點
@ConfigurationProperties("endpoints.restart")
@ManagedResource
public class RestartEndpoint extends AbstractEndpoint<Boolean>
implements ApplicationListener<ApplicationPreparedEvent> {
// Pause端點
@ConfigurationProperties("endpoints")
public class PauseEndpoint extends AbstractEndpoint<Boolean> {
public PauseEndpoint() {
super("pause", true, true);
}
@Override
public Boolean invoke() {
if (isRunning()) {
pause();
return true;
}
return false;
}
}
// 暫停操作
@ManagedOperation
public synchronized void pause() {
if (this.context != null) {
this.context.stop();
}
}
}
如上可見瞎嬉,/pause
端點最終會調(diào)用Spring應(yīng)用上下文的stop()
方法
AbstractApplicationContext#stop()
//## org.springframework.context.support.AbstractApplicationContext#stop
@Override
public void stop() {
// 1蝎毡、所有實現(xiàn)Lifecycle生命周期接口 stop()
getLifecycleProcessor().stop();
// 2、觸發(fā)ContextStoppedEvent事件
publishEvent(new ContextStoppedEvent(this));
}
查看源碼氧枣,并沒有發(fā)現(xiàn)有用的ContextStoppedEvent事件監(jiān)聽器沐兵,故stop的邏輯都在Lifecycle生命周期接口實現(xiàn)類的stop()
而getLifecycleProcessor().stop()
與 方式二中shutdown調(diào)用的 getLifecycleProcessor().doClose()
內(nèi)部邏輯都是一樣的,都是調(diào)用了DefaultLifecycleProcessor#stopBeans()
便监,進而調(diào)用Lifecycle接口實現(xiàn)類的stop()扎谎,如下
//## DefaultLifecycleProcessor
@Override
public void stop() {
stopBeans();
this.running = false;
}
@Override
public void onClose() {
stopBeans();
this.running = false;
}
所以,執(zhí)行/pause
端點 和 shutdown時的其中一部分邏輯是一樣的烧董,依賴于EurekaServiceRegistry#deregister() 注銷
毁靶,會依次執(zhí)行:
- 觸發(fā)狀態(tài)復(fù)制為DOWN,和Eureka Client注冊上線register調(diào)用方法一樣
DiscoveryClient#register()
逊移,發(fā)送POST /eureka/apps/appID
請求到Eureka Server预吆,只不過請求Body中的Instance實例狀態(tài)不同。執(zhí)行完此步驟后胳泉,Eureka Server頁面上實例狀態(tài)變成DOWN - 觸發(fā)
EurekaClient.shutdown
- 1拐叉、注銷所有 StatusChangeListener
- 2、停掉所有定時線程(實例狀態(tài)復(fù)制胶背、心跳巷嚣、client緩存刷新、監(jiān)督線程)
- 3钳吟、向Eureka Server注銷實例
- 調(diào)用
AbstractJerseyEurekaHttpClient#cancel()
方法廷粒,向Eureka Server發(fā)送DELETE /eureka/v2/apps/appID/instanceID
請求,DELETE請求成功后红且,Eureka Server頁面上服務(wù)列表就沒有當前實例信息了坝茎。注意: 由于在注銷上一步已經(jīng)停掉了定時心跳線程,否則注銷后的下次心跳又會導(dǎo)致服務(wù)上線
- 調(diào)用
- 4暇番、各種shutdown關(guān)閉
- stop()執(zhí)行完畢后嗤放,Eureka Server端當前實例狀態(tài)是DOWN,還是下線壁酬,取決于 狀態(tài)DOWN的復(fù)制線程 和 注銷請求 哪個執(zhí)行快
總結(jié)
/pause
端點可以用于讓服務(wù)從Eureka Server下線次酌,且與shutdown不一樣的是,其不會停止整個服務(wù)舆乔,導(dǎo)致整個服務(wù)不可用岳服,只會做從Eureka Server注銷的操作,最終在Eureka Server上體現(xiàn)的是 服務(wù)下線 或 服務(wù)狀態(tài)為DOWN希俩,且eureka client相關(guān)的定時線程也都停止了吊宋,不會再被定時線程注冊上線,所以可以在sleep一段時間颜武,待服務(wù)實例下線被像Zuul這種Eureka Client刷新到璃搜,再停止微服務(wù)拖吼,就可以做到優(yōu)雅下線(停止微服務(wù)的時候可以使用/shutdown端點
或 直接暴利kill -9
)
注意:
我實驗的當前版本下,使用/pause
端點下線服務(wù)后这吻,無法使用/resume
端點再次上線吊档,即如果發(fā)版過程中想重新注冊服務(wù),只有重啟微服務(wù)橘原。且為了從Eureka Server下線服務(wù)籍铁,將整個Spring容器stop(),也有點“興師動眾”
/resume
端點無法讓服務(wù)再次上線的原因是趾断,雖然此端點會調(diào)用AbstractApplicationContext#start()
--> EurekaAutoServiceRegistration#start()
--> EurekaServiceRegistry#register()
,但由于之前已經(jīng)停止了Eureka Client的所有定時任務(wù)線程吩愧,比如狀態(tài)復(fù)制 和 心跳線程芋酌,重新注冊時雖然有maybeInitializeClient(eurekaRegistration)
嘗試重新啟動EurekaClient,但并沒有成功(估計是此版本的Bug)雁佳,導(dǎo)致UP狀態(tài)并沒有發(fā)送給Eureka Server
可下線脐帝,無法重新上線
方式四:/service-registry 端點【可用,但有坑】
/service-registry 端點
首先糖权,在我使用的版本 /service-registry
端點默認是啟用的堵腹,但是是sensitive
的,也就是需要認證才能訪問
我試圖找一個可以單獨將/service-registry
的sensitive
置為false的方式星澳,但在當前我用的版本沒有找到疚顷,/service-registry
端點是通過 ServiceRegistryAutoConfiguration
自動配置的 ServiceRegistryEndpoint
,而 ServiceRegistryEndpoint
這個MvcEndpoint的isSensitive()
方法寫死了返回true禁偎,并沒有給可配置的地方或者自定義什么實現(xiàn)腿堤,然后在ManagementWebSecurityAutoConfiguration
這個安全管理自動配置類中,將所有這些sensitive==true
的通過Spring Security的 httpSecurity.authorizeRequests().xxx.authenticated()
設(shè)置為必須認證后才能訪問如暖,目前我找到只能通過 management.security.enabled=false
這種將所有端點都關(guān)閉認證的方式才可以無認證訪問
# 無認證訪問 /service-registry 端點
management.security.enabled=false
更新遠端實例狀態(tài)
/service-registry端點的實現(xiàn)類是ServiceRegistryEndpoint
笆檀,其暴露了兩個RequestMapping,分別是GET 和 POST請求的/service-registry盒至,GET請求的用于獲取實例本地的status酗洒、overriddenStatus,POST請求的用于調(diào)用Eureka Server修改當前實例狀態(tài)
//## org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
@ManagedResource(description = "Can be used to display and set the service instance status using the service registry")
@SuppressWarnings("unchecked")
public class ServiceRegistryEndpoint implements MvcEndpoint {
private final ServiceRegistry serviceRegistry;
private Registration registration;
public ServiceRegistryEndpoint(ServiceRegistry<?> serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
public void setRegistration(Registration registration) {
this.registration = registration;
}
@RequestMapping(path = "instance-status", method = RequestMethod.POST)
@ResponseBody
@ManagedOperation
public ResponseEntity<?> setStatus(@RequestBody 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();
}
@RequestMapping(path = "instance-status", method = RequestMethod.GET)
@ResponseBody
@ManagedAttribute
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));
}
@Override
public String getPath() {
return "/service-registry";
}
@Override
public boolean isSensitive() {
return true;
}
@Override
public Class<? extends Endpoint<?>> getEndpointType() {
return null;
}
}
我們關(guān)注的肯定是POST請求的/service-registry枷遂,如上可以看到樱衷,其調(diào)用了 EurekaServiceRegistry.setStatus()
方法更新實例狀態(tài)
//## org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry
public class EurekaServiceRegistry implements ServiceRegistry<EurekaRegistration> {
// 更新狀態(tài)
@Override
public void setStatus(EurekaRegistration registration, String status) {
InstanceInfo info = registration.getApplicationInfoManager().getInfo();
// 如果更新的status狀態(tài)為CANCEL_OVERRIDE,調(diào)用EurekaClient.cancelOverrideStatus()
//TODO: howto deal with delete properly?
if ("CANCEL_OVERRIDE".equalsIgnoreCase(status)) {
registration.getEurekaClient().cancelOverrideStatus(info);
return;
}
// 調(diào)用EurekaClient.setStatus()
//TODO: howto deal with status types across discovery systems?
InstanceInfo.InstanceStatus newStatus = InstanceInfo.InstanceStatus.toEnum(status);
registration.getEurekaClient().setStatus(newStatus, info);
}
}
EurekaServiceRegistry.setStatus()
方法支持像Eureka Server發(fā)送兩種請求登淘,分別是通過 EurekaClient.setStatus()
和 EurekaClient.cancelOverrideStatus()
來支持的箫老,下面分別分析:
-
EurekaClient.setStatus()
: - 實際是發(fā)送
PUT /eureka/apps/appID/instanceID/status?value=xxx
到Eureka Server,這是注冊中心對于Take instance out of service 實例下線
而開放的Rest API黔州,可以做到更新Eureka Server端的實例狀態(tài)(status 和 overriddenstatus)耍鬓,一般會在發(fā)版部署時使用阔籽,讓服務(wù)下線,更新為 OUT_OF_SERVICE - 由于overriddenstatus更新為了OUT_OF_SERVICE牲蜀,故即使有 心跳 或 UP狀態(tài)復(fù)制笆制,也不會改變其OUT_OF_SERVICE的狀態(tài),overriddenstatus覆蓋狀態(tài)就是為了避免服務(wù)下線后又被定時線程上線或更新狀態(tài)而設(shè)計的涣达,有很多所謂的 “覆蓋策略”
- 也正是由于overriddenstatus覆蓋狀態(tài)無法被 心跳 和 UP狀態(tài)復(fù)制(其實就是EurekaClient.register())而影響在辆,故在發(fā)版部署完新版本后,最好先調(diào)用Rest API清除overriddenstatus度苔,再啟動服務(wù)匆篓,如果直接啟動服務(wù),可能導(dǎo)致Server端仍是OUT_OF_SERVICE狀態(tài)的問題
- 實驗: 更新狀態(tài)為OUT_OF_SERVICE后寇窑,直接停服務(wù)鸦概,只有等到Server端服務(wù)租約到期下線后,再啟動客戶端上線才能成功注冊并狀態(tài)為UP甩骏;如果沒等Server端下線服務(wù)不存在后就啟動服務(wù)窗市,注冊上線后無法改變overriddenstatus==OUT_OF_SERVICE
-
EurekaClient.cancelOverrideStatus()
:- 實際是發(fā)送
DELETE /eureka/v2/apps/appID/instanceID/status
到Eureka Server,用于清除覆蓋狀態(tài)饮笛,其實官方給出的是DELETE /eureka/v2/apps/appID/instanceID/status?value=UP
咨察,其中value=UP
可選,是刪除overriddenstatus為UNKNOWN之后福青,建議status回滾為什么狀態(tài)摄狱,但我當前使用版本里沒有這個value=UP
可選參數(shù),就導(dǎo)致發(fā)送后素跺,Eureka Server端 status=UNKNOWN 且 overriddenstatus=UNKNOWN二蓝,但UNKNOWN覆蓋狀態(tài)不同的事,雖然心跳線程仍對其無作用指厌,但注冊(等同于UP狀態(tài)更新)是可以讓服務(wù)上線的
- 實際是發(fā)送
總結(jié)
/service-registry
端點可以更新服務(wù)實例狀態(tài)為 OUT_OF_SERVICE刊愚,再經(jīng)過一段Server端、Client端緩存的刷新踩验,使得服務(wù)不會再被調(diào)用鸥诽,此時再通過/shutdown
端點 或 暴利的kill -9
停止服務(wù)進程,可以達到優(yōu)雅下線的效果-
如希望回滾箕憾,可以通過幾種方式
- 還是
/service-registry
端點牡借,只不過狀態(tài)為 CANCEL_OVERRIDE,具體邏輯在EurekaServiceRegistry.setStatus()
中袭异,其等同于直接調(diào)用Eureka Server API :DELETE /eureka/v2/apps/appID/instanceID/status
钠龙,可以讓Server端 status=UNKNOWN 且 overriddenstatus=UNKNOWN - 也可以用
/service-registry
端點,狀態(tài)為UP,可使得Server端 status=UP且 overriddenstatus=UP碴里,雖然可以臨時起到上線目的沈矿,但 overriddenstatus=UP 仍需要上一步的DELETE請求才能清楚,很麻煩咬腋,不建議使用 - 不通過Eureka Client的端點羹膳,直接調(diào)用Eureka Server端點:
DELETE /eureka/apps/appID/instanceID/status?value=UP
- 還是
-
實際使用過程中建議如下順序
1、調(diào)用
/service-registry
端點將狀態(tài)置為 OUT_OF_SERVICE-
2根竿、sleep 緩存刷新時間 + 單個請求處理時間
-
緩存刷新時間 指的是Eureka Server刷新只讀緩存陵像、Eureka Client刷新本地服務(wù)列表、Ribbon刷新ServerList的時間寇壳,默認都是30s醒颖,可以適當縮短緩存刷新時間
# Eureka Server端配置 eureka.server.responseCacheUpdateIntervalMs=5000 eureka.server.eviction-interval-timer-in-ms=5000 # Eureka Client端配置 eureka.client.registryFetchIntervalSeconds=5 ribbon.ServerListRefreshInterval=5000
-
- **單個請求處理時間** 是為了怕服務(wù)還有請求沒處理完
3、調(diào)用
/service-registry
端點將狀態(tài)置為 CANCEL_OVERRIDE壳炎,其實就是向Server端發(fā)送DELETE overriddenstatus的請求图贸,這會讓Server端 status=UNKNOWN 且 overriddenstatus=UNKNOWN4、使用
/shutdown
端點 或 暴利kill -9
終止服務(wù)5冕广、發(fā)版部署后,啟動服務(wù)注冊到Eureka Server偿洁,服務(wù)狀態(tài)變?yōu)閁P
方式五: 直接調(diào)用Eureka Server Rest API【可用撒汉,但URL比較復(fù)雜】
上面說了這么多,其實這些都是針對Eureka Server Rest API在Eureka客戶端上的封裝涕滋,即通過Eureka Client服務(wù)由于引入了actuator睬辐,增加了一系列端點,其實一些端點通過調(diào)用Eureka Server暴露的Rest API的方式實現(xiàn)Eureka實例服務(wù)下線功能
Eureka Rest API包括:
Operation | HTTP action | Description |
---|---|---|
Register new application instance | POST /eureka/apps/appID | Input: JSON/XMLpayload HTTPCode: 204 on success |
De-register application instance | DELETE /eureka/apps/appID/instanceID | HTTP Code: 200 on success |
Send application instance heartbeat | PUT /eureka/apps/appID/instanceID | HTTP Code: * 200 on success * 404 if instanceID doesn’t exist |
Query for all instances | GET /eureka/apps | HTTP Code: 200 on success Output: JSON/XML |
Query for all appID instances | GET /eureka/apps/appID | HTTP Code: 200 on success Output: JSON/XML |
Query for a specific appID/instanceID | GET /eureka/apps/appID/instanceID | HTTP Code: 200 on success Output: JSON/XML |
Query for a specific instanceID | GET /eureka/instances/instanceID | HTTP Code: 200 on success Output: JSON/XML |
Take instance out of service | PUT /eureka/apps/appID/instanceID/status?value=OUT_OF_SERVICE | HTTP Code: * 200 on success * 500 on failure |
Move instance back into service (remove override) | DELETE /eureka/apps/appID/instanceID/status?value=UP (The value=UP is optional, it is used as a suggestion for the fallback status due to removal of the override) | HTTP Code: * 200 on success * 500 on failure |
Update metadata | PUT /eureka/apps/appID/instanceID/metadata?key=value | HTTP Code: * 200 on success * 500 on failure |
Query for all instances under a particular vip address | GET /eureka/vips/vipAddress | * HTTP Code: 200 on success Output: JSON/XML * 404 if the vipAddressdoes not exist. |
Query for all instances under a particular secure vip address | GET /eureka/svips/svipAddress | * HTTP Code: 200 on success Output: JSON/XML * 404 if the svipAddressdoes not exist. |
其中大多數(shù)非查詢類的操作在之前分析Eureka Client的端點時都分析過了宾肺,其實調(diào)用Eureka Server的Rest API是最直接的溯饵,但由于目前多采用一些類似Jenkins的發(fā)版部署工具,其中操作均在腳本中執(zhí)行锨用,Eureka Server API雖好丰刊,但URL中都涉及appID 、instanceID增拥,對于制作通用的腳本來說拼接出調(diào)用端點的URL有一定難度啄巧,且不像調(diào)用本地服務(wù)端點IP使用localhost 或 127.0.0.1即可,需要指定Eureka Server地址掌栅,所以整理略顯復(fù)雜秩仆。不過在比較規(guī)范化的公司中,也是不錯的選擇
參考: