Springboot web应用优雅关闭浅谈

背景

当Springboot Web(2.3.0前) 应用通过nginx负载均衡, 应用节点进行滚动重启时, 发现nginx会将请求转发到处于shutdown阶段的节点. 此时由于应用内资源已释放(e.g singletion destruction), http请求将无法得到适当的处理, 从而影响系统运行/用户体验.

本文将会分析springboot web 2.3.0 前后的迭代, 比较gracefully shutdown 引入前后springboot web 应用对SIGTERM kill信号量处理区别.

web应用的优雅关闭

简单来讲, 当一个应用接收到SIGTERM 信号时(kill/kill -15)时, 会拒绝后续来自客户端的请求(此时, 客户端指一个负载组件 : nginx/网关/SLB). 随后主动等待一个grace period, 等待在途请求和线程池内任务的处理完毕, 随后正式开始释放.

需要指出的是, 如果要做到用户层面无感知, 不但web应用需要拥有优雅关闭能力, web应用前面的负载组件也要能够平滑将请求转发到未关闭的web节点上 —
e.g nginx proxy_next_upstream能够将请求重路由到未处于connection refused的server.

Springboot web 2.3.0前的关闭

整合spring-boot-starter-web的springboot应用在关闭时会经历:

publish关闭事件
关闭全部lifecycle bean
释放全部singleton对象
关闭beanFactory
关闭embedded web server 拒绝http请求

核心函数:
org.springframework.context.support.AbstractApplicationContext#doClose

 // 只保留核心
  LiveBeansView.unregisterApplicationContext(this);
  publishEvent(new ContextClosedEvent(this));
  this.lifecycleProcessor.onClose();
  destroyBeans();
  closeBeanFactory();
  onClose();

需要注意的是, 整合整合spring-boot-starter-web 模块的springboot应用, ServletWebServerApplicationContext 重写了 onClose() 函数, 并在函数中增加了关闭web server能力(模版方法设计模式中的勾子函数).

protected void onClose() {
		super.onClose();
		stopAndReleaseWebServer();
	}

问题

上述资源释放流程中, web容器的关闭在最后一步, 既会存在短暂的spring bean & bean factory 释放但web容器依旧能拦截http请求的阶段, 此时http请求的处理需要已释放的bean资源, 容易引发 BeanCreationNotAllowedException

Springboot web 优雅关闭方案

优雅关闭在springboot web 2.3.0引入, 通过将embeded web容器适配优雅关闭函数, 在springboot 容器关闭早期(destroy bean前) 将web容器设置为拒绝连接状态. 并且在收到SIGTERM信号量时应用会暂停一段时间用于处理在途请求 & 线程池任务

2.3.0后springboot 关闭顺序

publish关闭事件
关闭全部lifecycle bean
webserver作为lifecycle bean被关闭
释放全部singleton对象
关闭beanFactory

需要注意的是, 嵌入式web容器接口org.springframework.boot.web.server.WebServer新增优雅关闭相关函数定义

default void shutDownGracefully(GracefulShutdownCallback callback) {
		callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
	}

Springboot web模块四大嵌入式web容器(tomcat undertow jetty reactor-netty)都定义了各自的优雅关闭实现, 需要指出的是除了undertow在优雅关闭阶段立即返回http_code 503(service unavailable)外, 其他web容器优雅关闭阶段都是通过拒绝请求(connection refuse)

Tomcat为例

Tomcat对应抽象接口org.springframework.boot.web.server.WebServer的shutDownGracefully函数由 org.springframework.boot.web.servlet.context.WebServerGracefulShutdownLifecycle调用, 该类实现了SmartLifecycle接口因此能在application context关闭的早期被调用.

Tomcat优雅关闭实现

private void doShutdown(GracefulShutdownCallback callback) {
		List<Connector> connectors = getConnectors();
		connectors.forEach(this::close);
		try {
			for (Container host : this.tomcat.getEngine().findChildren()) {
				for (Container context : host.findChildren()) {
					while (isActive(context)) {
						if (this.aborted) {
							logger.info("Graceful shutdown aborted with one or more requests still active");
							callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
							return;
						}
						Thread.sleep(50);
					}
				}
			}

		}
		catch (InterruptedException ex) {
			Thread.currentThread().interrupt();
		}
		logger.info("Graceful shutdown complete");
		callback.shutdownComplete(GracefulShutdownResult.IDLE);
	}

PS:写了一份简单的spring-boot-starter 适用于springboot 2.3.0前优雅关闭web应用(收到SIGTERM后立刻返回503状态码) 项目地址

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值