版本:
SpringBoot 1.5.4.RELEASE
SpringCloud Dalston.RELEASE
本文主要讨论的是微服务注册到Eureka注册中心,并使用Zuul网关负载访问的情况,如何停机可以使用户无感知。
方式一:kill -9 java进程id【不建议】
kill -9
属于强杀进程,首先微服务正在执行的任务被强制中断了;其次,没有通过Eureka注册中心服务下线,Zuul网关作为Eureka Client仍保存这个服务的路由信息,会继续调用服务,Http请求返回500,后台异常是Connection refuse连接拒绝。
这种情况默认最长需要等待:
90s(微服务在Eureka Server上租约到期)
+
30s(Eureka Server服务列表刷新到只读缓存ReadOnlyMap的时间,Eureka Client默认读此缓存)
+
30s(Zuul作为Eureka Client默认每30秒拉取一次服务列表)
+
30s(Ribbon默认动态刷新其ServerList的时间间隔)
= 180s,即 3分钟
总结:
此种方式既会导致正在执行中的任务无法执行完,又会导致服务没有从Eureka Server摘除,并给Eureka Client时间刷新到服务列表,导致了通过Zuul仍然调用已停掉服务报500错误的情况,不推荐。
方式二:kill -15 java进程id 或 直接使用/shutdown 端点【不建议】
kill 与/shutdown 的含义
首先,kill
等于kill -15
,根据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没有执行信号等同于TERM(终止,termination)
而kill -l
查看信号编号与信号之间的关系,kill -15
就是 SIGTERM,TERM信号
给JVM进程发送TERM终止信号时,会调用其注册的 Shutdown Hook,当SpringBoot微服务启动时也注册了 Shutdown Hook
而直接调用/shutdown
端点本质和使用 Shutdown Hook是一样的,所以无论是使用kill
或 kill -15
,还是直接使用/shutdown
端点,都会调用到JVM注册的Shutdown Hook
注意:
启用 /shutdown端点,需要如下配置
endpoints.shutdown.enabled = true
endpoints.shutdown.sensitive = false
所有问题都导向了 Shutdown Hook会执行什么??
Spring注册的Shutdown Hook
通过查询项目组使用Runtime.getRuntime().addShutdownHook(Thread shutdownHook)
的地方,发现ribbon注册了一些Shutdown Hook,但这不是我们这次关注的,我们关注的是Spring的应用上下文抽象类AbstractApplicationContext
注册了针对整个Spring容器的Shutdown Hook,在执行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,线程真正调用的是 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.
// 发送ContextClosedEvent事件,会有对应此事件的Listener处理相应的逻辑
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.
// 调用所有 Lifecycle bean 的 stop() 方法
try {
getLifecycleProcessor().onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on