目录
前言
最近项目当中在搞各种测试,有一项就是节点服务停机或者各种kill或者各种不可用,然后看看应用能扛住多少场景。
优雅停机定义
我想给出一个定义,但是没有发现能有一个准确的定义,不妨我们给他下一个定义,在停止服务的时候不是什么都不做直接退出进程,而是将手里的工作做一些善后处理,便于后续能够继续处理。
为什么要优雅停机
- 从用户体验方面或者从服务提供方的角度看,应用在停止服务的时候,此时正好接收到了一个新的请求该怎么处理或者说停的时候正在处理的请求该怎么办。
- 从数据完整性来看,如果在停服务的时候不做任何的动作,可能会使数据出现预想不到的结果比如一些数据状态的更新以及一个连续性很强的业务被中断,导致连续性得不到保证。
- 从系统资来看,应用系统应该本着谁申请谁治理的原则对于应用程序中申请的内存,线程,连接等资源进行管理,而不是直接停服务把这些工作甩给系统来完成也是不可取的,万一系统资源比较紧张,系统还没来得及去检查和回收已经出问题了呢,本来是完全可以避免的一件事。
如何实现优雅停机
基本原理
在spring的项目当中,优雅的停止一个容器需要考虑到Bean生命周期以及事件如下:
启动阶段: @PostConstruct -> InitializingBean -> init-method -> refreshContext -> ContextStartedEvent
停止阶段:ContextClosedEvent -> @PreDestroy -> DisposableBean -> destroy method
其中在refreshContext的时候会向JVM注册shutdownHook的钩子,让jvm在退出时能够通知应用的一个线程使得其有机会做一些事情
@Override
public void registerShutdownHook() {
if (this.shutdownHook == null) {
// No shutdown hook registered yet.
this.shutdownHook = new Thread() {
@Override
public void run() {
synchronized (startupShutdownMonitor) {
doClose();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
spring正是利用这个点,在接收到消息后发布context close事件,让上层应用去处理
protected void doClose() {
if (this.active.get() && this.closed.compareAndSet(false, true)) {
if (logger.isDebugEnabled()) {
logger.debug("Closing " + this);
}
LiveBeansView.unregisterApplicationContext(this);
try {
// Publish shutdown event.
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.
if (this.lifecycleProcessor != null) {
try {
this.lifecycleProcessor.onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
}
// Destroy all cached singletons in the context's BeanFactory.
destroyBeans();
// Close the state of this context itself.
closeBeanFactory();
// Let subclasses do some final clean-up if they wish...
onClose();
this.active.set(false);
}
}
关闭的顺序
应用从业务和数据流向来分析先停什么后停什么以免由于顺序而带来新的问题。
原则就是先从数据流量来源的地方开始切断,然后再考虑放进来的这些流量的处理的先后顺序,如web请求, rpc provider,以及mq 的consumer等等。
篇幅有点长,后面的我分两三篇来写吧。