Любите жизнь, любите кодирование, эта статья включенаКолонка архитектурных технологийПодпишитесь на лайки, чтобы поделиться.
Проект с открытым исходным кодом:
-
Распределенный мониторинг (самый ценный проект Gitee GVP с открытым исходным кодом):git ee.com/Примадонна Трех мушкетеров…
-
Сбор видеопотока камеры:git ee.com/Примадонна Трех мушкетеров…
**Весенняя коллекция ботинок**
Spring Boot Series (1): Начало работы с SpringApplication
Серия Spring Boot (2) Анализ особенностей конфигурации
Серия Spring Boot (3): подробное объяснение последней версии элегантного завершения работы.
Spring Boot Series (4): подробная динамическая конфигурация журнала
изящное завершение работы
В настоящее время Spring Boot разработан до 2.3.4.RELEASE, С появлением версии 2.3 механизм корректного завершения работы также стал более совершенным.
Текущая версия корректного завершения работы Spring Boot поддерживает Jetty, Reactor Netty, Tomcat и Undertow, а также реактивные веб-приложения и веб-приложения на основе сервлетов.
Цель изящного выключения:
Если корректного завершения работы нет, а сервер в это время выключается напрямую (kill -9), то бизнес, работающий в данный момент в контейнере, потерпит неудачу напрямую, и в некоторых особых сценариях будут сгенерированы грязные данные.
После добавления конфигурации корректного завершения работы:
При выключении сервера (kill -2) будет зарезервировано немного времени для выполнения внутренних бизнес-потоков контейнера, и контейнер не будет пропускать в это время новые запросы. Метод обработки нового запроса связан с веб-сервером. Reactor Netty и Tomcat прекратят доступ к запросу, а метод обработки Undertow должен вернуть 503.
новая конфигурация
YAML-конфигурация
Новая конфигурация версии очень проста, Server.shutdown = изящная доставка (обратите внимание, что элегантное завершение работы необходимо сотрудничать с Tomcat 9.0.33 (включительно) или выше)
server:
port: 6080
shutdown: graceful #开启优雅停机
spring:
lifecycle:
timeout-per-shutdown-phase: 20s #设置缓冲时间 默认30s
После установки параметра буфера timeout-per-shutdown-phase, если поток не может быть выполнен в течение указанного времени, он будет принудительно остановлен.
Давайте посмотрим на разницу между добавлением изящных журналов остановки и их недобавлением при выключении:
//未加优雅停机配置
Disconnected from the target VM, address: '127.0.0.1:49754', transport: 'socket'
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
После добавления конфигурации изящного выключения журналы, которые можно найти, четкоWaiting for active requests to cpmplete, контейнер остановится после выполнения ShutdownHook.
Закрыть метод
1. Не используйте kill -9, используйте kill -2, чтобы закрыть контейнер. Это вызовет внутреннюю операцию java ShutdownHook, kill -9 не вызовет ShutdownHook.
2. Вы можете использовать конечные точки для мониторинга запросов POST./actuator/shutdownвыполнить изящное завершение работы.
Добавить ShutdownHook
Через приведенный выше лог мы обнаружили, что Druid выполнил свой собственный ShutdownHook, поэтому давайте также добавим ShutdownHook, есть несколько простых способов:
1. Реализуйте интерфейс DisposableBean и реализуйте метод уничтожения.
@Slf4j
@Service
public class DefaultDataStore implements DisposableBean {
private final ExecutorService executorService = new ThreadPoolExecutor(OSUtil.getAvailableProcessors(), OSUtil.getAvailableProcessors() + 1, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(200), new DefaultThreadFactory("UploadVideo"));
@Override
public void destroy() throws Exception {
log.info("准备优雅停止应用使用 DisposableBean");
executorService.shutdown();
}
}
2. Используйте аннотацию @PreDestroy
@Slf4j
@Service
public class DefaultDataStore {
private final ExecutorService executorService = new ThreadPoolExecutor(OSUtil.getAvailableProcessors(), OSUtil.getAvailableProcessors() + 1, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(200), new DefaultThreadFactory("UploadVideo"));
@PreDestroy
public void shutdown() {
log.info("准备优雅停止应用 @PreDestroy");
executorService.shutdown();
}
}
Обратите внимание, что @PreDestroy выполняется до DisposableBean.
принцип закрытия
1. Для закрытия используйте kill pid, исходный код очень простой, можете посмотреть на GracefulShutdown
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);
}
2. Используйте конечные точки для мониторинга запросов POST/actuator/shutdownзакрытие
Поскольку все приводы используют расширение SPI, мы смотрим на AutoConfiguration и видим, что ключевым моментом является ShutdownEndpoint.
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnAvailableEndpoint(
endpoint = ShutdownEndpoint.class
)
public class ShutdownEndpointAutoConfiguration {
public ShutdownEndpointAutoConfiguration() {
}
@Bean(
destroyMethod = ""
)
@ConditionalOnMissingBean
public ShutdownEndpoint shutdownEndpoint() {
return new ShutdownEndpoint();
}
}
ShutdownEndpoint, в целях экономии места, только немного важно
@Endpoint(
id = "shutdown",
enableByDefault = false
)
public class ShutdownEndpoint implements ApplicationContextAware {
@WriteOperation
public Map<String, String> shutdown() {
if (this.context == null) {
return NO_CONTEXT_MESSAGE;
} else {
boolean var6 = false;
Map var1;
try {
var6 = true;
var1 = SHUTDOWN_MESSAGE;
var6 = false;
} finally {
if (var6) {
Thread thread = new Thread(this::performShutdown);
thread.setContextClassLoader(this.getClass().getClassLoader());
thread.start();
}
}
Thread thread = new Thread(this::performShutdown);
thread.setContextClassLoader(this.getClass().getClassLoader());
thread.start();
return var1;
}
}
private void performShutdown() {
try {
Thread.sleep(500L);
} catch (InterruptedException var2) {
Thread.currentThread().interrupt();
}
this.context.close(); //这里才是核心
}
}
Когда вызывается this.context.close(), это на самом деле метод close() класса AbstractApplicationContext (фокус находится на doClose())
/**
* Close this application context, destroying all beans in its bean factory.
* <p>Delegates to {@code doClose()} for the actual closing procedure.
* Also removes a JVM shutdown hook, if registered, as it's not needed anymore.
* @see #doClose()
* @see #registerShutdownHook()
*/
@Override
public void close() {
synchronized (this.startupShutdownMonitor) {
doClose(); //重点:销毁bean 并执行jvm shutdown hook
// If we registered a JVM shutdown hook, we don't need it anymore now:
// We've already explicitly closed the context.
if (this.shutdownHook != null) {
try {
Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
}
catch (IllegalStateException ex) {
// ignore - VM is already shutting down
}
}
}
}
постскриптум
здесь, о单机
Изящное завершение работы версии Spring Boot выполнено. зачем говорить单机
? Потому что каждый также может обнаружить, что при закрытии это только для того, чтобы обеспечить выполнение внутреннего потока сервера, а состояние вызывающего объекта не касается.
Будь то Dubbo или облачная структура распределенных услуг, необходимо обратить внимание на то, как отменить регистрацию поставщика в центре регистрации до того, как служба будет остановлена, а затем остановить поставщика услуг, чтобы гарантировать, что бизнес-система не будет генерировать различные 503, тайм-аут и другие явления.
К счастью, в этом нам помог текущий Spring Boot в сочетании с Kubernetes, то есть новые функции версии Spring Boot 2.3 Liveness (состояние выживания) и Readiness (состояние готовности)
Просто упомяните эти два состояния:
-
Liveness: Статус Liveness для просмотра внутренней ситуации можно понимать как Проверка работоспособности. Если Liveness дает сбой, это означает, что приложение находится в состоянии сбоя и в настоящее время не может восстановиться, это перезапускается. В настоящее время Kubernetes убьет Container, если обнаружение выживания не удастся.
-
Готовность (readiness): Используется, чтобы сообщить, готово ли приложение принимать запросы клиентов.Если Готовность не готова, k8s не может маршрутизировать трафик.