这篇文章主要通过下面的程序来实现优雅地关闭spring boot应用程序。
这个文章的原始实现是由Andy Wilkinson实现的,我将他改造成了spring boot 2.0版本。这个最源始代码可以在GitHub comment找到。
引言
许多架构师及开发人员讨论最多的就是应用程序的设计、流量负载、框架、设计模式的运用,但是他们很少讨论应用程序的关闭阶段。
让我们考虑下面的场景,假如你的应用程序有一个须要执行很久的阻塞操作而这时你的程序须要关闭来更换为新的版本。如果我们直接杀死所有的连接这样很不友好。我们可以选择让他们执行完成再关闭该应用程序可能会更好。
这就是本文中要讲的东西。
基本要求:
-
jdk1.8
-
你自己喜欢的IDE工具
-
Maven3.0版本
Spring Boot, Tomcat
要完成这项工作,第一步是须要去实现TomcatConnectorCustomizer``接口。
import org.apache.catalina.connector.Connector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
private static final int TIMEOUT = 30;
private volatile Connector connector;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(TIMEOUT, TimeUnit.SECONDS)) {
log.warn("Tomcat thread pool did not shut down gracefully within "
+ TIMEOUT + " seconds. Proceeding with forceful shutdown");
threadPoolExecutor.shutdownNow();
if (!threadPoolExecutor.awaitTermination(TIMEOUT, TimeUnit.SECONDS)) {
log.error("Tomcat thread pool did not terminate");
}
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
我们上面的实现类里的ThreadPoolExecutor
将在关闭之前等待30s,非常简单对吧?现在我们需要将这个bean注册到应用程序上下文中并添加到Tomcat容器中。
现在我们只需要创建Spring所需要的@Bean。
@Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
@Bean
public ConfigurableServletWebServerFactory webServerFactory(final GracefulShutdown gracefulShutdown) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(gracefulShutdown);
return factory;
}
怎么测试?
要测试这个结果,我们只用创建一个LongProcessController``,然后在里的方法执行Thread.sleep(10000)操作。
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LongProcessController {
@RequestMapping("/long-process")
public String pause() throws InterruptedException {
Thread.sleep(10000);
return "Process finished";
}
}
现在我们开始运行我们的spring boot应用,构建一个请求到/long-process
端点上面,这时我们执行一个kill操作。
找到进程ID
当我们启动这个应用,你能从日志中找到应用程序的进程iD,在我机器上是6578。
2018-06-28 20:37:28.292 INFO 6578 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2018-06-28 20:37:28.296 INFO 6578 --- [ main] c.m.wd.gracefulshutdown.Application : Started Application in 2.158 seconds (JVM running for 2.591)
请求和关闭
我们向/long-process
端点发送一个请求,我用的culr来实现:`
$ curl -i localhost:8080/long-process
对该进程执行kill操作:
$ kill 6578
这个curl请求最后还是收到了响应:
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 14
Date: Thu, 28 Jun 2018 18:39:56 GMT
Process finished
总结:
恭喜!你已经学会了如何优雅的关闭spring boot应用程序。
Github地址:https://github.com/weekly-drafts/graceful-shutdown-spring-boot
最源始的code comment:https://github.com/spring-projects/spring-boot/issues/4657#issuecomment-161354811
网友评论