SpringCloud-优雅停机方案
文章内索引
[显示]
SpringCloud-优雅停机方案
当系统中服务需要下线时,需要等待未处理完成的请求处理完。可以依赖actuator,接收shutdown请求,之后根据不同容器处理优雅停机方案。
监听shutdown
首先引入Actuator
1 2 3 4 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> |
增加配置
1 2 3 4 |
#优雅停机 endpoints.shutdown.enabled=true #关闭验证,已经有了本地token验证 management.security.enabled=false |
不同容器优雅停机的处理
tomcat
只处理的tomcat容器的线程池,当请求到shutdown时,关闭容器的线城池,也就是说只有controller内的线程会执行完毕,如果controller内的线程又另启动了线程或者线程池无法控制, 会报出停止异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
package com.hzcf.gently.tomcat; 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.beans.factory.annotation.Configurable; 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.event.ContextClosedEvent; /** * Create by hanlin on 2018年6月19日 * 只处理的tomcat容器的线程池,当请求到shutdown时,关闭容器的线城池,也就是说只有controller内的线程会执行完毕,如果controller内的线程又另启动了线程或者线程池无法控制。 * 会报出停止异常。 **/ @Configurable public class TomcatGentlyShutdownConfiguration { /** * 用于接受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 = 100; @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(waitTime, TimeUnit.SECONDS)) { log.warn("Tomcat thread pool did not shut down gracefully within " + waitTime + " seconds. Proceeding with forceful shutdown"); } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } } } |
Undertow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.hzcf.gently.undertow; import org.springframework.stereotype.Component; import com.sun.net.httpserver.HttpHandler; /** * Create by hanlin on 2018年6月19日 **/ @Component public class GracefulShutdownWrapper implements HandlerWrapper{ private GracefulShutdownHandler gracefulShutdownHandler; @Override public HttpHandler wrap(HttpHandler handler) { if(gracefulShutdownHandler == null) { this.gracefulShutdownHandler = new GracefulShutdownHandler(handler); } return gracefulShutdownHandler; } public GracefulShutdownHandler getGracefulShutdownHandler() { return gracefulShutdownHandler; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.hzcf.gently.undertow; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; /** * Create by hanlin on 2018年6月19日 **/ @Component @AllArgsConstructor public class UndertowExtraConfiguration { private final GracefulShutdownWrapper gracefulShutdownWrapper; @Bean public UndertowServletWebServerFactory servletWebServerFactory() { UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory(); factory.addDeploymentInfoCustomizers(deploymentInfo -> deploymentInfo.addOuterHandlerChainWrapper(gracefulShutdownWrapper)); factory.addBuilderCustomizers(builder -> builder.setServerOption(UndertowOptions.ENABLE_STATISTICS, true)); return factory; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.hzcf.gently.undertow; import org.springframework.stereotype.Component; import com.sun.net.httpserver.HttpHandler; /** * Create by hanlin on 2018年6月19日 **/ @Component public class GracefulShutdownWrapper implements HandlerWrapper{ private GracefulShutdownHandler gracefulShutdownHandler; @Override public HttpHandler wrap(HttpHandler handler) { if(gracefulShutdownHandler == null) { this.gracefulShutdownHandler = new GracefulShutdownHandler(handler); } return gracefulShutdownHandler; } public GracefulShutdownHandler getGracefulShutdownHandler() { return gracefulShutdownHandler; } } |
Jetty
默认处理完所有请求之后关闭,无需特殊处理。
请求shutdown接口
http://localhost:8002/shutdown 请求接口,触发优雅停机,会先通知注册中心服务下线,之后等待程序运行完毕再停止应用。
Shutdown时 EurekaAutoServiceRegistration异常
EurekaAutoServiceRegistration bean已经被销毁,然后它收到了与feignclient关联的应用程序上下文的contextcloseent。BeanFactory尝试再次创建该bean并触发该异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package com.hzcf.gently; import java.util.Arrays; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.stereotype.Component; /** * Create by hanlin on 2018年6月19日 **/ /** * #1952 * https://github.com/spring-cloud/spring-cloud-netflix/issues/1952 * 解决shutdown时报 Error creating bean with name 'eurekaAutoServiceRegistration' 异常。 * 问题是EurekaAutoServiceRegistration bean已经被销毁,然后它收到了与feignclient关联的应用程序上下文的contextcloseent。BeanFactory尝试再次创建该bean并触发该异常。 */ @Component public class FeignBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { if (containsBeanDefinition(beanFactory, "feignContext", "eurekaAutoServiceRegistration")) { BeanDefinition bd = beanFactory.getBeanDefinition("feignContext"); bd.setDependsOn("eurekaAutoServiceRegistration"); } } private boolean containsBeanDefinition(ConfigurableListableBeanFactory beanFactory, String... beans) { return Arrays.stream(beans).allMatch(b -> beanFactory.containsBeanDefinition(b)); } } |
©版权声明:本文为【翰林小院】(huhanlin.com)原创文章,转载时请注明出处!
发表评论