Commit e652aa0b authored by ag's avatar ag

feat : graceful shutdown & base destroy

parent 622760e3
......@@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>cn.quantgroup</groupId>
<artifactId>commons</artifactId>
<version>0.0.2</version>
<version>0.0.3</version>
<packaging>jar</packaging>
......@@ -29,6 +29,7 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
......@@ -45,6 +46,21 @@
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<!-- 部署时往公司私服上发包, 可以在网址上找到 其他项目引入该包的需要的 XML -->
......
package cn.quantgroup.tech.shutdown;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.config.RabbitListenerConfigUtils;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.Lifecycle;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
@Slf4j
@Component
public class BaseDestroyHandler {
@Autowired
private ApplicationContext applicationContext;
@PreDestroy
private void stopRedisSub() {
// context.getBean(RedisMessageListenerContainer.)
log.info("redis stopped");
}
@PreDestroy
private void stopRabbitMQ() {
RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry = applicationContext.getBean(
RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME,
RabbitListenerEndpointRegistry.class);
rabbitListenerEndpointRegistry.getListenerContainers().forEach(Lifecycle::stop);
log.info("MQ listener stopped");
}
}
package cn.quantgroup.tech.shutdown;
import cn.quantgroup.tech.shutdown.properties.GracefulShutdownProperties;
import cn.quantgroup.tech.shutdown.service.Shutdown;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import sun.misc.Signal;
import sun.misc.SignalHandler;
/**
* 默认的停止信号接收处理.
*
* @author ag
*/
@Slf4j
public class DefaultSignalHandler implements SignalHandler {
private ApplicationContext context;
public DefaultSignalHandler(ApplicationContext context) {
this.context = context;
}
@Override
public void handle(Signal signal) {
String applicationName = context.getApplicationName();
log.info("开始执行停止{}服务", applicationName);
GracefulShutdownProperties bean = context.getBean(GracefulShutdownProperties.class);
try {
context.getBean(Shutdown.class).shutdown(bean.getTimeout());
log.info("servlet container 停止接收请求");
} catch (InterruptedException e) {
}
Thread.getAllStackTraces().forEach((thread, stackTraceElements) -> {
if (!thread.isDaemon()) {
//如果中断 daemon 线程, 会导致 PreDestroy 方法不执行,
//如果不中断 non-daemon 线程, 会导致无法 exit(0).
log.debug("中断正在进行的 non-daemon 线程: {} ; {}", thread.getId(), thread.getName());
thread.interrupt();
}
});
log.info("{} 即将执行 @PreDestroy 方法", applicationName);
System.exit(0);
}
}
package cn.quantgroup.tech.shutdown.configuration;
import cn.quantgroup.tech.shutdown.properties.GracefulShutdownProperties;
import cn.quantgroup.tech.shutdown.service.TomcatShutdown;
import cn.quantgroup.tech.shutdown.service.UndertowShutdown;
import cn.quantgroup.tech.shutdown.wrapper.UndertowShutdownHandlerWrapper;
import io.undertow.Undertow;
import org.apache.catalina.startup.Tomcat;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.undertow.UndertowDeploymentInfoCustomizer;
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import javax.servlet.Servlet;
/**
* This configuration class will be picked up by Spring Boot's auto configuration capabilities as soon as it's
* on the classpath.
*/
@Configuration
@ConditionalOnProperty(prefix = "shutdown.graceful", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(GracefulShutdownProperties.class)
@Import(EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar.class)
public class GracefulShutdownAutoConfiguration {
/**
* Configuration for Tomcat.
*/
@Configuration
@ConditionalOnClass({Servlet.class, Tomcat.class})
public static class EmbeddedTomcat {
@Bean
public TomcatShutdown tomcatShutdown() {
return new TomcatShutdown();
}
/**
* Customise the tomcat factory.
*
* @return an EmbeddedServletContainerCustomizer
*/
@Bean
public EmbeddedServletContainerCustomizer tomcatCustomizer() {
return container -> {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
((TomcatEmbeddedServletContainerFactory) container).addConnectorCustomizers(tomcatShutdown());
}
};
}
}
/**
* Configuration for Undertow.
*/
@Configuration
@ConditionalOnClass({Servlet.class, Undertow.class})
public static class EmbeddedUndertow {
@Bean
public UndertowShutdown undertowShutdown() {
return new UndertowShutdown();
}
/**
* Customise the undertow factory.
*
* @return an EmbeddedServletContainerCustomizer
*/
@Bean
public EmbeddedServletContainerCustomizer undertowCustomizer() {
return container -> {
if (container instanceof UndertowEmbeddedServletContainerFactory) {
((UndertowEmbeddedServletContainerFactory) container).addDeploymentInfoCustomizers(undertowDeploymentInfoCustomizer());
}
};
}
@Bean
public UndertowDeploymentInfoCustomizer undertowDeploymentInfoCustomizer() {
return deploymentInfo -> deploymentInfo.addOuterHandlerChainWrapper(undertowShutdownHandlerWrapper());
}
@Bean
public UndertowShutdownHandlerWrapper undertowShutdownHandlerWrapper() {
return new UndertowShutdownHandlerWrapper();
}
}
}
package cn.quantgroup.tech.shutdown.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Global graceful shutdown properties.
*
* @author Corentin Azelart
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "shutdown.graceful")
public class GracefulShutdownProperties {
/**
* The timeout before force shutdown. TimeUnit.Second
*/
private Integer timeout = 10;
}
package cn.quantgroup.tech.shutdown.service;
/**
* Shutdown service.
*/
public interface Shutdown {
/**
* Perform shutdown.
* @param delay is delay to force
* @throw InterruptedException if we have an interruption
*/
void shutdown(Integer delay) throws InterruptedException;
}
package cn.quantgroup.tech.shutdown.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
* Perform a tomcat shutdown.
*/
@Slf4j
public class TomcatShutdown implements Shutdown, TomcatConnectorCustomizer {
/**
* Implementation of a Coyote connector.
*/
private volatile Connector connector;
/**
* Perform a shutdown
*
* @param delay is delay to force is the delay before perform a force shutdown
* @throws InterruptedException if we have an exception
*/
@Override
public void shutdown(Integer delay) throws InterruptedException {
// Used to properly handle the work queue.
final Executor executor = connector.getProtocolHandler().getExecutor();
final ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
/*
* Initiates an orderly shutdown in which previously submitted
* tasks are executed, but no new tasks will be accepted.
* Invocation has no additional effect if already shut down.
*/
threadPoolExecutor.shutdown();
// We wait after the end of the current requests
if (!threadPoolExecutor.awaitTermination(delay, TimeUnit.SECONDS)) {
log.warn("Tomcat thread pool did not shut down gracefully within " + delay + " second(s). Proceeding with force shutdown");
} else {
log.debug("Tomcat thread pool is empty, we stop now");
}
}
/**
* Set connector.
*
* @param connector is the catalina connector.
*/
@Override
public void customize(final Connector connector) {
this.connector = connector;
}
}
package cn.quantgroup.tech.shutdown.service;
import cn.quantgroup.tech.shutdown.wrapper.UndertowShutdownHandlerWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Undertow shutdown.
*/
@Slf4j
public class UndertowShutdown implements Shutdown {
/**
* The wrapper for manual commands.
*/
@Autowired
private UndertowShutdownHandlerWrapper undertowShutdownHandlerWrapper;
/**
* Perform shutdown.
*
* @param delay is delay to force
* @throw InterruptedException if we have an interruption
*/
@Override
public void shutdown(Integer delay) throws InterruptedException {
undertowShutdownHandlerWrapper.getGracefulShutdownHandler().shutdown();
undertowShutdownHandlerWrapper.getGracefulShutdownHandler().awaitShutdown(delay * 1000);
}
}
package cn.quantgroup.tech.shutdown.wrapper;
import io.undertow.server.HandlerWrapper;
import io.undertow.server.HttpHandler;
import io.undertow.server.handlers.GracefulShutdownHandler;
/**
* Undertow handler wrapper.
*/
public class UndertowShutdownHandlerWrapper implements HandlerWrapper {
/**
* graceful shutdown handler.
*/
private GracefulShutdownHandler gracefulShutdownHandler;
/**
* Wrapper.
* @param handler is the http handler from chain.
* @return the Undertown shutdown handler.
*/
@Override
public HttpHandler wrap(final HttpHandler handler) {
if(gracefulShutdownHandler == null) {
this.gracefulShutdownHandler = new GracefulShutdownHandler(handler);
}
return gracefulShutdownHandler;
}
/**
* Return the graceful shutdown handler to perform manual command : pause/shutdown.
* @return the shutdown handler.
*/
public GracefulShutdownHandler getGracefulShutdownHandler() {
return gracefulShutdownHandler;
}
}
......@@ -4,10 +4,10 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
......@@ -30,11 +30,11 @@ import java.util.concurrent.locks.ReentrantLock;
* @author zhiguo.liu
* @date 2017/5/18
*/
@Component
@Slf4j
@Component
@ConditionalOnBean(RedisTemplate.class)
public class IDGenerator {
private static final String REDIS_WORK_ID_KEY = "GLOBAL:WORK:ID:";
private static final String ID_FORMAT = "yyyyMMddHHmmss";
......@@ -72,7 +72,6 @@ public class IDGenerator {
static {
Date now = new Date();
MAX_TIME_SECOND = now.getTime() / 1000;
}
private static LoadingCache cache = CacheBuilder.newBuilder()
......@@ -95,6 +94,7 @@ public class IDGenerator {
@PostConstruct
public void init() {
log.info("test Init id generator");
int workerId = (int) (redis.opsForValue().increment(REDIS_WORK_ID_KEY + DATA_CENTER_ID, 1) % MAX_WORK_ID);
WORKER_ID_STR = String.format("%04d", workerId);
DATA_CENTER_STR = String.format("%03d", DATA_CENTER_ID);
......
......@@ -6,6 +6,8 @@ import org.springframework.data.redis.core.RedisTemplate;
import redis.clients.jedis.Jedis;
/**
* 目前只写了一个锁, 后面可能还有其他的 Redis 工具类组合
*
* @author zhiguo.liu
* @date 2017/7/28
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment