В процессе повторной публикации проекта, если некоторые запросы занимают много времени и еще не были выполнены, перезапуск в это время приведет к прерыванию запроса и повлияет на бизнес-функции.Мягкий перезапуск может гарантировать, что никакие внешние новые запросы не будут полученные, когда он остановлен.Запросы, ожидающие завершения невыполненных запросов, что обеспечивает целостность данных.
Spring Boot 1.X
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.catalina.connector.Connector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;
/**
* Spring Boot1.X Tomcat容器优雅停机
* @author yinjihuan
*
*/
@Configuration
public class ShutdownConfig {
/**
* 用于接受shutdown事件
* @return
*/
@Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
/**
* 用于注入 connector
* @return
*/
@Bean
public EmbeddedServletContainerCustomizer tomcatCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
((TomcatEmbeddedServletContainerFactory) container).addConnectorCustomizers(gracefulShutdown());
}
}
};
}
private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
private volatile Connector connector;
private final int waitTime = 120;
@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;
log.info("shutdown start");
threadPoolExecutor.shutdown();
log.info("shutdown end");
if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
log.info("Tomcat 进程在" + waitTime + "秒内无法结束,尝试强制结束");
}
log.info("shutdown success");
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
}
Spring Boot 2.X
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
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.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;
/**
* Spring Boot2.X Tomcat容器优雅停机
* @author yinjihuan
*
*/
@Configuration
public class ShutdownConfig {
/**
* 用于接受shutdown事件
* @return
*/
@Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addConnectorCustomizers(gracefulShutdown());
return tomcat;
}
private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
private volatile Connector connector;
private final int waitTime = 120;
@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;
log.info("shutdown start");
threadPoolExecutor.shutdown();
log.info("shutdown end");
if (!threadPoolExecutor.awaitTermination(waitTime, TimeUnit.SECONDS)) {
log.info("Tomcat 进程在" + waitTime + "秒内无法结束,尝试强制结束");
}
log.info("shutdown success");
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
}
Сценарий перезапуска службы:
LANG="zh_CN.UTF-8"
pid=`ps ax | grep fangjia-youfang-web | grep java | head -1 | awk '{print $1}'`
echo $pid
#kill $pid
curl -X POST http://127.0.0.1:8086/shutdown?token=认证信息
while [[ $pid != "" ]]; do
echo '服务停止中...'
sleep 1
pid=`ps ax | grep fangjia-youfang-web | grep java | head -1 | awk '{print $1}'`
done
echo '服务停止成功,开始重启服务...'
java -jar xxx.jar
Сначала отправьте команду перезапуска на конечную точку перед перезапуском или используйте метод уничтожения идентификатора процесса, никогда не используйте команду kill -9.
Затем выполните цикл, чтобы проверить, существует ли процесс. Если служба останавливается нормально, процесс не существует. Если процесс все еще существует, он доказывает, что есть незавершенные запросы, останавливается на 1 секунду и продолжает обнаружение.
Что касается перезапуска службы, рекомендуется использовать метод kill, чтобы вам не приходилось полагаться на spring-boot-starter-actuator.Если вы используете метод конечной точки, вам необходимо контролировать разрешения, иначе это может быть перезапущен в любое время, вы можете использовать безопасность для управления разрешениями, I Это контролируется самим фильтром.
Если вы используете привод для перезапуска, вам необходимо настроить функцию включения перезапуска: 1.x конфигурация следующая:
endpoints.shutdown.enabled=true
В версии 2.x больше конфигураций, по умолчанию выставлено только несколько часто используемых, да и адреса доступа тоже изменились, например, health, который раньше напрямую обращался к /health, теперь требует для доступа /actuator/health. Мы можем настроить его для совместимости с предыдущим адресом доступа.
Завершение работы не отображается по умолчанию. Его можно открыть и запустить через конфигурацию. Конфигурация выглядит следующим образом:
#访问路径,配置后就和1.x版本路径一样
management.endpoints.web.base-path=/
# 暴露所有,也可以暴露单个或多个
management.endpoints.web.exposure.include=*
# 开启shutdown
management.endpoint.shutdown.enabled=true
Документацию см. по адресу: https://docs.spring.io/spring-boot/docs/2.0.2.RELEASE/reference/htmlsingle/#production-ready.
как проверить
Для тестирования мы можем написать простой интерфейс, подождать в интерфейсе, а затем выполнить скрипт для остановки проекта.Если он нормальный, он выдаст, что служба остановлена.После завершения выполнения вашего интерфейса процесс исчезнет, но если оно превысит настроенное вами значение, время ожидания принудительно завершит работу.
@GetMapping("/hello")
public String hello() {
System.out.println("req.........");
try {
Thread.sleep(1000 * 60 * 3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello";
}
Проблемы, о которых следует знать
Если вы используете в своем проекте другие пулы потоков, такие как Spring ThreadPoolTaskExecutor, незнакомые студенты могут обратиться к этой моей статье.«Асинхронное выполнение Spring Boot Async»
После отправки команды остановки, если у ThreadPoolTaskExecutor есть необработанные потоки, процесс не будет автоматически закрыт в это время. В это время нам нужно закрыть пул потоков, и добавленный код выглядит следующим образом:
AsyncTaskExecutePool asyncTaskExecutePool = event.getApplicationContext().getBean(AsyncTaskExecutePool.class);
Executor executors = asyncTaskExecutePool.getAsyncExecutor();
try {
if (executors instanceof ThreadPoolTaskExecutor) {
ThreadPoolTaskExecutor threadPoolExecutor = (ThreadPoolTaskExecutor) executors;
log.info("Async shutdown start");
threadPoolExecutor.setWaitForTasksToCompleteOnShutdown(true);
threadPoolExecutor.setAwaitTerminationSeconds(waitTime);
threadPoolExecutor.shutdown();
}
} catch (Exception ex) {
Thread.currentThread().interrupt();
}
ThreadPoolTaskExecutor имеет только метод завершения работы и не имеет метода awaitTermination.Просмотрев исходный код, установив setWaitForTasksToCompleteOnShutdown и setAwaitTerminationSeconds перед завершением работы, вы также можете достичь awaitTermination.
Исходный код выглядит следующим образом:
public void shutdown() {
if (logger.isInfoEnabled()) {
logger.info("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (this.executor != null) {
if (this.waitForTasksToCompleteOnShutdown) {
this.executor.shutdown();
}
else {
for (Runnable remainingTask : this.executor.shutdownNow()) {
cancelRemainingTask(remainingTask);
}
}
awaitTerminationIfNecessary(this.executor);
}
}
Когда значение waitForTasksToCompleteOnShutdown равно true, непосредственно вызывается executor.shutdown(); и, наконец, выполняется метод awaitTerminationIfNecessary.
private void awaitTerminationIfNecessary(ExecutorService executor) {
if (this.awaitTerminationSeconds > 0) {
try {
if (!executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)) {
if (logger.isWarnEnabled()) {
logger.warn("Timed out while waiting for executor" +
(this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
}
}
}
catch (InterruptedException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Interrupted while waiting for executor" +
(this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
}
Thread.currentThread().interrupt();
}
}
}
В awaitTerminationIfNecessary будет оценено, что атрибут awaitTerminationSeconds равен значению, и будет выполнена логика обнаружения ожидания выключения, которая аналогична коду, который мы имеем дело с выключением tomcat.
Я обнаружил, что после этого это не дало никакого эффекта, поэтому я изменил способ написания и выполнил логику выключения напрямую, получив ThreadPoolExecutor в ThreadPoolTaskExecutor:
AsyncTaskExecutePool asyncTaskExecutePool = event.getApplicationContext().getBean(AsyncTaskExecutePool.class);
Executor executors = asyncTaskExecutePool.getAsyncExecutor();
try {
if (executors instanceof ThreadPoolTaskExecutor) {
ThreadPoolTaskExecutor threadPoolExecutor = (ThreadPoolTaskExecutor) executors;
log.info("Async shutdown start");
threadPoolExecutor.getThreadPoolExecutor().shutdown();
log.info("Async shutdown end"+threadPoolExecutor.getThreadPoolExecutor().isTerminated());
if (!threadPoolExecutor.getThreadPoolExecutor().awaitTermination(waitTime, TimeUnit.SECONDS)) {
log.info("Tomcat 进程在" + waitTime + "秒内无法结束,尝试强制结束");
}
log.info("Async shutdown success");
}
} catch (Exception ex) {
Thread.currentThread().interrupt();
}
Этот метод бесполезен для достижения желаемого эффекта. Когда я запускаю команду kill, я выхожу напрямую. На самом деле у меня есть фоновый поток, работающий в ThreadPoolTaskExecutor. Из журнала вывода я вижу, что до тех пор, пока выключение и isTerminated методы называются The return is true, а это значит, что он был закрыт.Причина этого не найдена.Если вы исследовали его, пожалуйста, поделитесь им.
Для большего обмена технологиями, пожалуйста, обратите внимание на общедоступную учетную запись WeChat: Yuantiandi