Commit 344cb26a authored by xiaoguang.xu's avatar xiaoguang.xu

merge

parents 547ff863 3801dc72
commons 简介
=====================
该项目主要利用 Spring Boot 的自动化配置特性.
集成了.
平滑退出/调用链追踪/唯一ID生成器/读写分离/健康检查/
Apollo 配置中心等功能
#基础依赖介绍
spring-boot-starter-parent 1.5.8
lombok 1.6.20
guava 23.0
#如何使用
[使用说明见Confluence](http://confluence.quantgroup.cn/pages/viewpage.action?pageId=5458608)
#贡献者
[@张恒](heng.zhang@quantgroup.cn)
[@刘志国](zhiguo.liu@quantgroup.cn)
[@朱劲松](jinsong.liu@quantgroup.cn)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>commons-parent</artifactId>
<groupId>cn.quantgroup</groupId>
<version>0.2.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>brave-spring-boot-starter</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<brave.version>4.19.1</brave.version>
<zipkin-reporter2.version>2.5.0</zipkin-reporter2.version>
<zipkin.version>2.6.1</zipkin.version>
<okhttp.version>3.10.0</okhttp.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter</artifactId>
<version>${zipkin-reporter2.version}</version>
</dependency>
<dependency>
<groupId>io.zipkin.zipkin2</groupId>
<artifactId>zipkin</artifactId>
<version>${zipkin.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave</artifactId>
<version>${brave.version}</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-spring-beans</artifactId>
<version>${brave.version}</version>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-sender-kafka11</artifactId>
<version>${zipkin-reporter2.version}</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-spring-web</artifactId>
<version>${brave.version}</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-spring-webmvc</artifactId>
<version>${brave.version}</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-httpclient</artifactId>
<version>${brave.version}</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-mysql</artifactId>
<version>${brave.version}</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-okhttp3</artifactId>
<version>${brave.version}</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-spring-rabbit</artifactId>
<version>${brave.version}</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package cn.quantgroup.tech.brave.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* brave配置信息
*
* @author zhangheng
* create on 2018.04.25
*/
@Data
@ConfigurationProperties( prefix = "tech.brave" )
public class BraveProperties {
/**
* kafka地址
*/
private String kafkaBootstrapServers;
/**
* kafka的topic
*/
private String kafkaTopic;
/**
* 采集率
*/
private Float sample = 1.0f;
}
package cn.quantgroup.tech.brave.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* brave配置信息
*
* @author zhangheng
* create on 2018.04.25
*/
@Data
@ConfigurationProperties("spring.application")
public class ServiceProperties {
/**
*
*/
String name;
}
package cn.quantgroup.tech.brave.service;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
/**
* @author zhangheng
* create on 2018.04.27
*/
public interface ITechRabbitBuilder {
/**
* 构建RabbitTemplate
* @param connectionFactory
* @returne
*/
RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory);
/**
* 构建SimpleRabbitListenerContainerFactory
* @param connectionFactory
* @return
*/
SimpleRabbitListenerContainerFactory createSimpleRabbitListenerContainerFactory(ConnectionFactory connectionFactory);
}
package cn.quantgroup.tech.brave.service;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
/**
* @author zhangheng
* create on 2018.04.27
*/
public class TechRabbitBuilderNoTrace implements ITechRabbitBuilder {
public TechRabbitBuilderNoTrace() {
}
@Override
public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory) {
return new RabbitTemplate(connectionFactory);
}
@Override
public SimpleRabbitListenerContainerFactory createSimpleRabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
return factory;
}
}
package cn.quantgroup.tech.brave.service;
import brave.Tracing;
import brave.spring.rabbit.SpringRabbitTracing;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
/**
* @author zhangheng
* create on 2018.04.27
*/
public class TechRabbitBuilderTrace implements ITechRabbitBuilder {
private SpringRabbitTracing.Builder springRabbitTracingBuilder;
public TechRabbitBuilderTrace(Tracing tracing) {
springRabbitTracingBuilder = SpringRabbitTracing.newBuilder(tracing);
}
@Override
public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory) {
String virtualHost = connectionFactory.getVirtualHost();
SpringRabbitTracing.Builder builder = springRabbitTracingBuilder.remoteServiceName("MQ-" + virtualHost);
return builder.build().newRabbitTemplate(connectionFactory);
}
@Override
public SimpleRabbitListenerContainerFactory createSimpleRabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
String virtualHost = connectionFactory.getVirtualHost();
SpringRabbitTracing.Builder builder = springRabbitTracingBuilder.remoteServiceName("MQ-" + virtualHost);
return builder.build().newSimpleRabbitListenerContainerFactory(connectionFactory);
}
}
package cn.quantgroup.tech.brave.slf4j;
import brave.internal.HexCodec;
import brave.internal.Nullable;
import brave.propagation.CurrentTraceContext;
import brave.propagation.TraceContext;
import org.slf4j.MDC;
import static brave.internal.HexCodec.lowerHexEqualsTraceId;
import static brave.internal.HexCodec.lowerHexEqualsUnsignedLong;
/**
* Adds {@linkplain MDC} properties TRACE_ID, PARENT_ID and SPAN_ID when a {@link
* brave.Tracer#currentSpan() span is current}. These can be used in log correlation.
*/
public final class MDCCurrentTraceContext extends CurrentTraceContext {
private static final String TRACE_ID = "X-B3-TraceId";
private static final String SPAN_ID = "X-B3-SpanId";
private static final String PARENT_ID = "X-B3-ParentId";
private static final String EXPORTABLE = "X-Span-Export";
public static MDCCurrentTraceContext create() {
return create(CurrentTraceContext.Default.inheritable());
}
public static MDCCurrentTraceContext create(CurrentTraceContext delegate) {
return new MDCCurrentTraceContext(delegate);
}
final CurrentTraceContext delegate;
MDCCurrentTraceContext(CurrentTraceContext delegate) {
if (delegate == null) throw new NullPointerException("delegate == null");
this.delegate = delegate;
}
@Override
public TraceContext get() {
return delegate.get();
}
@Override
public Scope newScope(@Nullable TraceContext currentSpan) {
return newScope(currentSpan, MDC.get(TRACE_ID), MDC.get(SPAN_ID), MDC.get(EXPORTABLE));
}
@Override
public Scope maybeScope(@Nullable TraceContext currentSpan) {
String previousTraceId = MDC.get(TRACE_ID);
String previousSpanId = MDC.get(SPAN_ID);
String sampled = MDC.get(EXPORTABLE);
if (currentSpan == null) {
if (previousTraceId == null) {
return Scope.NOOP;
}
return newScope(null, previousTraceId, previousSpanId, sampled);
}
if (lowerHexEqualsTraceId(previousTraceId, currentSpan)
&& lowerHexEqualsUnsignedLong(previousSpanId, currentSpan.spanId())) {
return Scope.NOOP;
}
return newScope(currentSpan, previousTraceId, previousSpanId, sampled);
}
// all input parameters are nullable
Scope newScope(TraceContext currentSpan, String previousTraceId, String previousSpanId, String sampled) {
String previousParentId = MDC.get(PARENT_ID);
if (currentSpan != null) {
maybeReplaceTraceContext(currentSpan, previousTraceId, previousParentId, previousSpanId, sampled);
} else {
MDC.remove(TRACE_ID);
MDC.remove(PARENT_ID);
MDC.remove(SPAN_ID);
MDC.remove(EXPORTABLE);
}
Scope scope = delegate.newScope(currentSpan);
class MDCCurrentTraceContextScope implements Scope {
@Override
public void close() {
scope.close();
replace(TRACE_ID, previousTraceId);
replace(PARENT_ID, previousParentId);
replace(SPAN_ID, previousSpanId);
//true = 采样了. false = 未采样. null = 决定不了...等会再说
replace(EXPORTABLE, sampled);
}
}
return new MDCCurrentTraceContextScope();
}
void maybeReplaceTraceContext(
TraceContext currentSpan,
String previousTraceId,
@Nullable String previousParentId,
String previousSpanId,
@Nullable String sampled
) {
MDC.put(EXPORTABLE, String.valueOf(currentSpan.sampled()));
boolean sameTraceId = lowerHexEqualsTraceId(previousTraceId, currentSpan);
if (!sameTraceId) {
MDC.put(TRACE_ID, currentSpan.traceIdString());
}
long parentId = currentSpan.parentIdAsLong();
if (parentId == 0L) {
MDC.remove(PARENT_ID);
} else {
boolean sameParentId = lowerHexEqualsUnsignedLong(previousParentId, parentId);
if (!sameParentId) MDC.put(PARENT_ID, HexCodec.toLowerHex(parentId));
}
boolean sameSpanId = lowerHexEqualsUnsignedLong(previousSpanId, currentSpan.spanId());
if (!sameSpanId) MDC.put(SPAN_ID, HexCodec.toLowerHex(currentSpan.spanId()));
}
static void replace(String key, @Nullable String value) {
if (value != null) {
MDC.put(key, value);
} else {
MDC.remove(key);
}
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.quantgroup.tech.brave.configuration.BraveAutoConfiguration
\ No newline at end of file
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>commons-parent</artifactId> <artifactId>commons-parent</artifactId>
<groupId>cn.quantgroup</groupId> <groupId>cn.quantgroup</groupId>
<version>0.1.4</version> <version>0.2.3</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>commons-parent</artifactId> <artifactId>commons-parent</artifactId>
<groupId>cn.quantgroup</groupId> <groupId>cn.quantgroup</groupId>
<version>0.1.4</version> <version>0.2.3</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
...@@ -31,45 +31,17 @@ ...@@ -31,45 +31,17 @@
</dependencyManagement> </dependencyManagement>
<dependencies> <dependencies>
<!--健康检查需要-->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-actuator</artifactId>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>*</groupId>
</exclusion>
<exclusion>
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
<groupId>org.jboss.spec.javax.servlet</groupId>
</exclusion>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
<exclusion>
<artifactId>annotations</artifactId>
<groupId>com.google.code.findbugs</groupId>
</exclusion>
</exclusions>
</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>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId> <artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-boot-starter-security</artifactId>
...@@ -80,11 +52,6 @@ ...@@ -80,11 +52,6 @@
<artifactId>spring-boot-starter-data-redis</artifactId> <artifactId>spring-boot-starter-data-redis</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId> <artifactId>fastjson</artifactId>
...@@ -111,23 +78,6 @@ ...@@ -111,23 +78,6 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
...@@ -135,18 +85,11 @@ ...@@ -135,18 +85,11 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-stream</artifactId> <artifactId>spring-cloud-starter</artifactId>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId> <artifactId>spring-boot-starter-aop</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
......
package cn.quantgroup.tech.autoconfigure; package cn.quantgroup.tech.autoconfigure;
import cn.quantgroup.tech.endpoint.HealthCheckMvcEndpoint; import cn.quantgroup.tech.endpoint.HealthCheckMvcEndpoint;
import cn.quantgroup.tech.shutdown.DefaultSignalHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.EndpointProperties; import org.springframework.boot.actuate.endpoint.EndpointProperties;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import sun.misc.Signal;
/** /**
* @author jinsong.zhu * @author jinsong.zhu
* @date 2017/12/18 * @date 2017/12/18
*/ */
@Slf4j
@Configuration @Configuration
@AutoConfigureAfter({FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class}) @AutoConfigureAfter({FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class})
@EnableConfigurationProperties(EndpointProperties.class) @EnableConfigurationProperties(EndpointProperties.class)
public class TechCommonAutoConfiguration { public class TechCommonAutoConfiguration {
ConfigurableApplicationContext applicationContext;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
initSignalHandler(this.applicationContext);
}
private void initSignalHandler(ConfigurableApplicationContext applicationContext) {
log.info("starting init graceful shutdown by listening on int signal .");
Signal.handle(new Signal("INT"), new DefaultSignalHandler(applicationContext));
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public HealthCheckMvcEndpoint healthCheckMvcEndpoint() { public HealthCheckMvcEndpoint healthCheckMvcEndpoint() {
......
...@@ -21,6 +21,7 @@ public class HealthCheckMvcEndpoint implements MvcEndpoint { ...@@ -21,6 +21,7 @@ public class HealthCheckMvcEndpoint implements MvcEndpoint {
@RequestMapping(value = "check", method = {RequestMethod.GET, RequestMethod.POST}) @RequestMapping(value = "check", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody @ResponseBody
public Result check() { public Result check() {
return Result.ok(); return Result.ok();
} }
......
package cn.quantgroup.tech.util;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 注意事项:
* 1. 使用前在配置文件内配置 DATA_CENTER_ID
* <p>
* <p>
*
* @author zhiguo.liu
* @date 2017/5/18
*/
@Slf4j
@Component
@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnProperty(name = "data.center.id")
public class IDGenerator {
private static final String REDIS_WORK_ID_KEY = "GLOBAL:WORK:ID:";
private static final String ID_FORMAT = "yyyyMMddHHmmss";
private static Lock lock = new ReentrantLock();
/**
* data center,默认为 1
*/
private static int DATA_CENTER_ID = 1;
/**
* 最高支持同时1W台机器
*/
private static final int MAX_WORK_ID = 10000;
/**
* 最高每秒发号 100w
*/
private static final int MAX_COUNT = 999999;
private static AtomicInteger COUNTER = new AtomicInteger(0);
private static long MAX_TIME_SECOND;
/**
* Worker ID 字符串
*/
private static String WORKER_ID_STR;
/**
* data center 字符串
*/
private static String DATA_CENTER_STR;
/**
* 最长回退时间,120 秒
*/
private static int MAX_BACK_SECOND = 120;
static {
Date now = new Date();
MAX_TIME_SECOND = now.getTime() / 1000;
}
private static LoadingCache cache = CacheBuilder.newBuilder()
.expireAfterWrite(MAX_BACK_SECOND, TimeUnit.SECONDS)
.build(new CacheLoader<Long, AtomicInteger>() {
@Override
public AtomicInteger load(Long key) throws Exception {
return new AtomicInteger(0);
}
});
@Autowired
private StringRedisTemplate redis;
@Value("${data.center.id}")
public void setDataCenterId(Integer dataCenterId) {
DATA_CENTER_ID = dataCenterId;
}
@PostConstruct
public void init() {
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);
}
/**
* 1. 需要获取 dataCenterId 和 workeId
*/
public static String getId(String prefix) {
Date now = new Date();
Long timeSecond = now.getTime() / 1000;
Integer counter = 0;
if (timeSecond > MAX_TIME_SECOND) {
lock.lock();
if (timeSecond > MAX_TIME_SECOND) {
cache.put(MAX_TIME_SECOND, COUNTER);
COUNTER = new AtomicInteger(0);
MAX_TIME_SECOND = timeSecond;
}
lock.unlock();
}
if (timeSecond == MAX_TIME_SECOND) {
counter = COUNTER.incrementAndGet();
}
// 时间回退时到 cache 里拿,或者直接抛出错误
if (timeSecond < MAX_TIME_SECOND) {
if (timeSecond + MAX_BACK_SECOND < MAX_TIME_SECOND) {
throw new RuntimeException("时间回撤, 请稍后再试");
}
try {
AtomicInteger historyCounter = (AtomicInteger) cache.get(timeSecond);
counter = historyCounter.incrementAndGet();
} catch (ExecutionException e) {
log.error("取出缓存时出错");
}
}
// 达到计数器上上限, 休眠半秒并重试
if (counter >= MAX_COUNT) {
try {
Thread.sleep(500);
return getId(prefix);
} catch (InterruptedException e) {
log.error("发号器休眠时发生错误:{}", e);
}
}
String currentTimeStr = new SimpleDateFormat(ID_FORMAT, Locale.SIMPLIFIED_CHINESE).format(now);
return prefix + currentTimeStr + DATA_CENTER_STR + WORKER_ID_STR + String.format("%06d", counter);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>commons-parent</artifactId>
<groupId>cn.quantgroup</groupId>
<version>0.2.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>idgenerator-spring-boot-starter</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package cn.quantgroup.tech.generator;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.springframework.util.Assert;
import java.math.BigInteger;
/**
* Allocate 64 bits for the UID(long)<br>
* sign (fixed 1bit) -> deltaSecond -> workerId -> sequence(within the same second)
*/
public class BitsAllocator {
/**
* Total 64 bits
* dataCenterIdBits + workerIdBits + sequenceBits
*/
public static final int TOTAL_BITS = 1 << 6;
private final int timestampBits;
private final int dataCenterIdBits;
private final int workerIdBits;
private final int sequenceBits;
/**
* Max value for dataCenterId & workerId & sequence
*/
private final long maxDeltaSeconds;
private final long maxDataCenterId;
private final long maxWorkerId;
private final long maxSequence;
/**
* Shift for dataCenterId & workerId & sequence
*/
private final int timestampShift;
private final int dataCenterIdShift;
private final int workerIdShift;
/**
* Constructor with timestampBits, workerIdBits, sequenceBits<br>
* The highest bit used for sign, so <code>63</code> bits for timestampBits, workerIdBits, sequenceBits
*/
public BitsAllocator(int timestampBits, int dataCenterIdBits, int workerIdBits, int sequenceBits) {
// make sure allocated 64 bits
int allocateTotalBits = dataCenterIdBits + workerIdBits + sequenceBits;
Assert.isTrue(allocateTotalBits + 1 < TOTAL_BITS, "allocate greater than 64 bits");
// initialize bits
this.timestampBits = timestampBits;
this.dataCenterIdBits = dataCenterIdBits;
this.workerIdBits = workerIdBits;
this.sequenceBits = sequenceBits;
// initialize max value
this.maxDeltaSeconds = ~(-1L << timestampBits);
this.maxDataCenterId = ~(-1L << dataCenterIdBits);
this.maxWorkerId = ~(-1L << workerIdBits);
this.maxSequence = ~(-1L << sequenceBits);
// initialize shift
this.timestampShift = dataCenterIdBits + workerIdBits + sequenceBits;
this.dataCenterIdShift = workerIdBits + sequenceBits;
this.workerIdShift = sequenceBits;
}
/**
* Allocate bits for UID according to delta seconds & workerId & sequence<br>
*
* @param deltaSeconds
* @param workerId
* @param sequence
* @return
*/
public long allocate(long deltaSeconds, long dataCenterId, long workerId, long sequence) {
return (deltaSeconds << timestampShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence;
}
public BigInteger allocateBigInteger(long deltaSeconds, long dataCenterId, long workerId, long sequence) {
return BigInteger.ZERO.or(BigInteger.valueOf(deltaSeconds).shiftLeft(timestampShift))
.or(BigInteger.valueOf(dataCenterId).shiftLeft(dataCenterIdShift))
.or(BigInteger.valueOf(workerId).shiftLeft(workerIdShift))
.or(BigInteger.valueOf(sequence));
}
public int getTimestampBits() {
return timestampBits;
}
public int getDataCenterIdBits() {
return dataCenterIdBits;
}
public int getWorkerIdBits() {
return workerIdBits;
}
public int getSequenceBits() {
return sequenceBits;
}
public long getMaxDeltaSeconds() {
return maxDeltaSeconds;
}
public long getMaxDataCenterId() {
return maxDataCenterId;
}
public long getMaxWorkerId() {
return maxWorkerId;
}
public long getMaxSequence() {
return maxSequence;
}
public int getTimestampShift() {
return timestampShift;
}
public int getWorkerIdShift() {
return workerIdShift;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
\ No newline at end of file
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.quantgroup.tech.generator;
/**
* IDGenerateException
*/
public class IDGenerateException extends RuntimeException {
/**
* Serial Version UID
*/
private static final long serialVersionUID = -27048199131316992L;
/**
* Default constructor
*/
public IDGenerateException() {
super();
}
/**
* Constructor with cause
*
* @param cause
*/
public IDGenerateException(Throwable cause) {
super(cause);
}
}
package cn.quantgroup.tech.generator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.math.BigInteger;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* Represents an implementation of {@link IDGenerator}
* 基于百度开源项目 uid-generator 的增强版,Snowflake Java 实现版本。项目 Github:https://github.com/baidu/uid-generator
* <p>
* uid-generator 通过对 64 位数字分区来生成唯一 ID,由以下组成:
* <p>
* -----------------------------------------------------------------------------------
* | sign | delta seconds | worker id | SEQUENCE
* -----------------------------------------------------------------------------------
* 1bits 28bits 22bits 13bits
* -----------------------------------------------------------------------------------
* 其中 delta seconds 为 当前时间 - 指定起始时间。
* 该版本有三个问题
* 1. delta seconds 位数有限,28bits 也只能允许运行 8.7 年左右。
* 2. worker id 生成号码为用后即弃,可容纳重启次数有限。
* 3. 微服务分布式的情况下,无法使用统一数据源,则不同服务生成 worker id 时会重复
* <p>
* 于是做出以下改进
* 1. worker id 拆分成 data center id,每个服务通过约定指定自己的 data center id 。
* 2. worker id 通过 redis 自增指定,设计为首尾相连的环形,自增数字达到设定的最大值时,会从0开始。
* 2. 不限制使用 delta seconds 的位数,则实现了无限时间的使用。当位数增长到 64 为后,改用 BigInteger 的位运算实现。
* <p>
* 经测试,BigInteger 实现时,性能降低 60% 左右,每秒发号约为 100w~150w。
* 现在 uid 由以下组成
* ---------------------------------------------------------------------------------------------------------
* | sign(length < 64) | delta seconds (unlimited) | data center id | worker id | SEQUENCE
* ---------------------------------------------------------------------------------------------------------
* 1bits 28bits 22bits 22bits 13bits
* ---------------------------------------------------------------------------------------------------------
* 其中 data center id + worker id + SEQUENCE 设定的位数不大于 63。
* <p>
* 使用注意:
* 1. 号码的位数不固定,会随着时间增长。data center id + worker id + SEQUENCE 总数设定越大,号码位数越长
* 2. 各个分区的位数、起始时间一旦设定完成投入使用,则后续不能更改。否则会导致发号重复。
*
* @author zhiguo.liu
*/
@Slf4j
public class IDGenerator {
private static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
private long dataCenterId;
/**
* Customer epoch, unit as second. For example 2018-03-01 (ms: 1463673600000)
*/
private long epochSeconds;
/**
* Stable fields after spring bean initializing
*/
private BitsAllocator allocator;
private long workerId;
/**
* Volatile fields caused by nextId()
*/
private long sequence = 0L;
private long lastSecond = -1L;
public IDGenerator(Long dataCenterId, Long epochSeconds, Long workerId, BitsAllocator allocator) {
this.dataCenterId = dataCenterId;
this.epochSeconds = epochSeconds;
this.workerId = workerId;
this.allocator = allocator;
}
public String getID(String prefix) throws IDGenerateException {
try {
return nextId(prefix);
} catch (Exception e) {
log.error("Generate unique id exception. ", e);
throw new IDGenerateException(e);
}
}
public String parseID(String idStr) {
BigInteger bigInteger = new BigInteger(idStr);
int totalBits = bigInteger.bitLength();
long dataCenterIdBits = allocator.getDataCenterIdBits();
long workerIdBits = allocator.getWorkerIdBits();
long sequenceBits = allocator.getSequenceBits();
if (totalBits < 64) {
totalBits = 64;
long id = bigInteger.longValue();
long sequence = (id << (totalBits - sequenceBits)) >>> (totalBits - sequenceBits);
long workerId = (id << (totalBits - workerIdBits - sequenceBits)) >>> (totalBits - workerIdBits);
long dataCenterId = (id << (totalBits - dataCenterIdBits - workerIdBits - sequenceBits)) >>> (totalBits - dataCenterIdBits);
if (dataCenterIdBits == 0) {
dataCenterId = 0;
}
long deltaSeconds = id >>> (dataCenterIdBits + workerIdBits + sequenceBits);
Date thatTime = new Date(TimeUnit.SECONDS.toMillis(epochSeconds + deltaSeconds));
String thatTimeStr = DateFormatUtils.format(thatTime, DATETIME_PATTERN);
return String.format("{\"ID\":\"%d\",\"timestamp\":\"%s\",\"DATA_CENTER_ID\":\"%d\",\"WORKER_ID\":\"%d\",\"SEQUENCE\":\"%d\"}",
id, thatTimeStr, dataCenterId, workerId, sequence);
} else {
long sequence = getBigIntegerFromLength(sequenceBits).and(bigInteger).longValue();
long workerId = getBigIntegerFromLength(workerIdBits).and(bigInteger.shiftRight((int) sequenceBits)).longValue();
long dataCenterId = getBigIntegerFromLength(dataCenterIdBits).and(bigInteger.shiftRight((int) sequenceBits + (int) workerIdBits)).longValue();
if (dataCenterIdBits == 0) {
dataCenterId = 0;
}
long deltaSeconds = bigInteger.shiftRight((int) dataCenterIdBits + (int) workerIdBits + (int) sequenceBits).longValue();
Date thatTime = new Date(TimeUnit.SECONDS.toMillis(epochSeconds + deltaSeconds));
String thatTimeStr = DateFormatUtils.format(thatTime, DATETIME_PATTERN);
return String.format("{\"ID\":\"%d\",\"timestamp\":\"%s\",\"DATA_CENTER_ID\":\"%d\",\"WORKER_ID\":\"%d\",\"SEQUENCE\":\"%d\"}",
bigInteger, thatTimeStr, dataCenterId, workerId, sequence);
}
}
private BigInteger getBigIntegerFromLength(long n) {
return BigInteger.valueOf(-1).shiftLeft((int) n).not();
}
/**
* Get ID
*
* @return ID
* @throws IDGenerateException in the case: Clock moved backwards; Exceeds the max timestamp
*/
protected synchronized String nextId(String preFix) {
long currentSecond = getCurrentSecond();
// Clock moved backwards, wait for newest time
if (currentSecond < lastSecond) {
getNextSecond(lastSecond);
}
// At the same second, increase SEQUENCE
if (currentSecond == lastSecond) {
sequence = (sequence + 1) & allocator.getMaxSequence();
// Exceed the max SEQUENCE, we wait the next second to generate ID
if (sequence == 0) {
currentSecond = getNextSecond(lastSecond);
}
// At the different second, SEQUENCE restart from zero
} else {
sequence = 0L;
}
lastSecond = currentSecond;
// 当前时间小于设定的最大时间,即总位数在 64 位以下,用 long 生成数字
if (currentSecond - epochSeconds <= allocator.getMaxDeltaSeconds()) {
return preFix + allocator.allocate(currentSecond - epochSeconds, dataCenterId, workerId, sequence);
}
return preFix + allocator.allocateBigInteger(currentSecond - epochSeconds, dataCenterId, workerId, sequence);
}
/**
* Get next millisecond
*/
private long getNextSecond(long lastTimestamp) {
long timestamp = getCurrentSecond();
while (timestamp <= lastTimestamp) {
timestamp = getCurrentSecond();
}
return timestamp;
}
/**
* Get current second
*/
private long getCurrentSecond() {
return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
}
}
package cn.quantgroup.tech.generator.configuration;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
* 只要有 redis 就可以使用id generator
*
* @author zhiguo.liu
*/
@ConditionalOnClass(RedisTemplate.class)
@EnableConfigurationProperties(GeneratorAutoConfiguration.IdGeneratorProperties.class)
public class GeneratorAutoConfiguration {
private StringRedisTemplate redisTemplate;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
}
@Bean
public GeneratorFactoryBean generatorFactoryBean(IdGeneratorProperties properties) {
return GeneratorFactoryBean.builder()
.dataCenterId(properties.getDataCenter())
.dataCenterIdBits(properties.getDataCenterIdBits())
.epochStr(properties.getEpochStr())
.seqBits(properties.getSeqBits())
.workerBits(properties.getWorkerBits())
.stringRedisTemplate(redisTemplate)
.build();
}
@Data
@ConfigurationProperties(prefix = "tech.id")
protected static class IdGeneratorProperties {
/**
* 这里每个服务都需要单独配置一个
*/
private int dataCenter = 1;
/**
* 下面每一个配置,如果不懂就不建议修改.
* dataCenterIdBits 1024个服务
*/
private int dataCenterIdBits = 10;
private int seqBits = 13;
private int workerBits = 8;
private String epochStr = "2018-04-01";
}
}
package cn.quantgroup.tech.generator.configuration;
import cn.quantgroup.tech.generator.BitsAllocator;
import cn.quantgroup.tech.generator.IDGenerator;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.Assert;
import java.util.concurrent.TimeUnit;
@Slf4j
@Builder
public class GeneratorFactoryBean implements FactoryBean<IDGenerator>, InitializingBean {
private static final String REDIS_WORK_ID_KEY = "GLOBAL:WORK:ID:";
private static final String DAY_PATTERN = "yyyy-MM-dd";
/**
* from config
*/
private int dataCenterIdBits;
private int seqBits;
private int workerBits;
private long dataCenterId;
private String epochStr;
private long workerId;
private StringRedisTemplate stringRedisTemplate;
/**
* local construct
*/
private long epochSeconds;
private BitsAllocator allocator;
private IDGenerator idGenerator;
@Override
public IDGenerator getObject() throws Exception {
return idGenerator;
}
@Override
public Class<?> getObjectType() {
return IDGenerator.class;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
if (stringRedisTemplate == null) {
log.error("redis template is null ");
return;
}
// initialize bits allocator
int timeBits = 64 - 1 - dataCenterIdBits - workerBits - seqBits;
allocator = new BitsAllocator(timeBits, dataCenterIdBits, workerBits, seqBits);
// initialize worker id
workerId = stringRedisTemplate.opsForValue().increment(REDIS_WORK_ID_KEY + dataCenterId, 1) % allocator.getMaxWorkerId();
Assert.isTrue(workerId <= allocator.getMaxWorkerId(), "WORKER_ID is too big");
Assert.isTrue(dataCenterId <= allocator.getMaxDataCenterId(), "DATA_CENTER_ID is too big");
epochSeconds = TimeUnit.MILLISECONDS.toSeconds(DateUtils.parseDate(epochStr, new String[]{DAY_PATTERN}).getTime());
log.info("Initialized bits dataCenterBits:{}, workerBits:{}, seqBits:{}", dataCenterIdBits, workerBits, seqBits);
log.info("Initialized nodes, WORKER_ID:{}, DATA_CENTER_ID:{}", workerId, dataCenterId);
idGenerator = new IDGenerator(dataCenterId, epochSeconds, workerId, allocator);
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.quantgroup.tech.generator.configuration.GeneratorAutoConfiguration
\ No newline at end of file
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>cn.quantgroup</groupId> <groupId>cn.quantgroup</groupId>
<artifactId>commons-parent</artifactId> <artifactId>commons-parent</artifactId>
<version>0.1.4</version> <version>0.2.3</version>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
...@@ -17,25 +17,49 @@ ...@@ -17,25 +17,49 @@
<modules> <modules>
<module>commons-core</module> <module>commons-core</module>
<module>commons-spring</module> <module>commons-spring</module>
<module>shutdown-spring-boot-starter</module>
<module>brave-spring-boot-starter</module>
<module>idgenerator-spring-boot-starter</module>
</modules> </modules>
<packaging>pom</packaging> <packaging>pom</packaging>
<properties> <properties>
<java.version>1.8</java.version>
<guava.version>23.0</guava.version>
<apollo.client.version>0.10.2</apollo.client.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.test.skip>true</maven.test.skip> <maven.test.skip>true</maven.test.skip>
<java.version>1.8</java.version>
<lombok.version>1.16.20</lombok.version>
<guava.version>23.0</guava.version>
<apollo.client.version>0.10.2</apollo.client.version>
<common.parent.version>0.2.3</common.parent.version>
</properties> </properties>
<dependencies> <dependencies>
<!--所有工程基础依赖. apollo 配置中心-->
<dependency> <dependency>
<groupId>com.ctrip.framework.apollo</groupId> <groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId> <artifactId>apollo-client</artifactId>
<version>${apollo.client.version}</version> <version>${apollo.client.version}</version>
</dependency> </dependency>
<!--所有工程基础依赖. lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--commons 项目需要自动配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--所有项目都有guava-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
</dependencies> </dependencies>
<dependencyManagement> <dependencyManagement>
...@@ -43,12 +67,27 @@ ...@@ -43,12 +67,27 @@
<dependency> <dependency>
<groupId>cn.quantgroup</groupId> <groupId>cn.quantgroup</groupId>
<artifactId>commons-spring</artifactId> <artifactId>commons-spring</artifactId>
<version>0.1.4</version> <version>${common.parent.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>cn.quantgroup</groupId> <groupId>cn.quantgroup</groupId>
<artifactId>commons-core</artifactId> <artifactId>commons-core</artifactId>
<version>0.1.4</version> <version>${common.parent.version}</version>
</dependency>
<dependency>
<groupId>cn.quantgroup</groupId>
<artifactId>shutdown-spring-boot-starter</artifactId>
<version>${common.parent.version}</version>
</dependency>
<dependency>
<groupId>cn.quantgroup</groupId>
<artifactId>brave-spring-boot-starter</artifactId>
<version>${common.parent.version}</version>
</dependency>
<dependency>
<groupId>cn.quantgroup</groupId>
<artifactId>idgenerator-spring-boot-starter</artifactId>
<version>${common.parent.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>commons-parent</artifactId>
<groupId>cn.quantgroup</groupId>
<version>0.2.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shutdown-spring-boot-starter</artifactId>
<dependencies>
<!--以下为可选的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</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-jetty</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package cn.quantgroup.tech.shutdown; package cn.quantgroup.tech.shutdown;
import lombok.extern.slf4j.Slf4j; 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.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.Lifecycle;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
/** /**
...@@ -19,6 +23,19 @@ public class BaseDestroyHandler { ...@@ -19,6 +23,19 @@ public class BaseDestroyHandler {
@Autowired @Autowired
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
private RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry;
@PostConstruct
public void initMQConf() {
try {
rabbitListenerEndpointRegistry = applicationContext.getBean(
RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME,
RabbitListenerEndpointRegistry.class);
} catch (Exception e) {
log.warn("注入MQ endpoint registry 失败, 如果你有MQ队列接收, 可能你要关注一下了");
}
}
@PreDestroy @PreDestroy
private void stopRedisSub() { private void stopRedisSub() {
log.info("我什么都没做, redis stopped"); log.info("我什么都没做, redis stopped");
...@@ -26,6 +43,16 @@ public class BaseDestroyHandler { ...@@ -26,6 +43,16 @@ public class BaseDestroyHandler {
@PreDestroy @PreDestroy
private void stopRabbitMQ() { private void stopRabbitMQ() {
try {
if (rabbitListenerEndpointRegistry == null) {
log.warn("我什么都没做, MQ listener stopped");
return;
}
rabbitListenerEndpointRegistry.getListenerContainers().forEach(Lifecycle::stop);
} catch (Exception e) {
//貌似这段日志不会打印...
log.error("貌似停止 MQ 遇到了问题... 你有MQ么?", e);
}
log.info("MQ listener stopped"); log.info("MQ listener stopped");
} }
} }
...@@ -27,12 +27,19 @@ public class DefaultSignalHandler implements SignalHandler { ...@@ -27,12 +27,19 @@ public class DefaultSignalHandler implements SignalHandler {
String applicationName = context.getApplicationName(); String applicationName = context.getApplicationName();
log.info("开始执行停止{}服务", applicationName); log.info("开始执行停止{}服务", applicationName);
GracefulShutdownProperties bean = context.getBean(GracefulShutdownProperties.class); GracefulShutdownProperties bean = context.getBean(GracefulShutdownProperties.class);
String shutdownBeanName = null;
try { try {
context.getBean(Shutdown.class).shutdown(bean.getTimeout()); String[] shutdownBeanNames = context.getBeanNamesForType(Shutdown.class);
log.info("servlet container 停止接收请求"); shutdownBeanName = shutdownBeanNames[0];
} catch (InterruptedException e) { //如果不是web应用. 这里会找不到shutdown bean
context.getBean(shutdownBeanName, Shutdown.class).shutdown(bean.getTimeout());
log.info("{} 停止接收请求", shutdownBeanName);
} catch (Exception e) {
log.info("{} shutdown 失败", shutdownBeanName);
} }
log.info("{} 即将执行 @PreDestroy 方法", applicationName);
System.exit(0);
//处理无法exit(0)的动作
Thread.getAllStackTraces().forEach((thread, stackTraceElements) -> { Thread.getAllStackTraces().forEach((thread, stackTraceElements) -> {
if (!thread.isDaemon()) { if (!thread.isDaemon()) {
//如果中断 daemon 线程, 会导致 PreDestroy 方法不执行, //如果中断 daemon 线程, 会导致 PreDestroy 方法不执行,
...@@ -41,7 +48,6 @@ public class DefaultSignalHandler implements SignalHandler { ...@@ -41,7 +48,6 @@ public class DefaultSignalHandler implements SignalHandler {
thread.interrupt(); thread.interrupt();
} }
}); });
log.info("{} 即将执行 @PreDestroy 方法", applicationName);
System.exit(0); System.exit(0);
} catch (Exception e) { } catch (Exception e) {
// 此处可能导致异常的里面包含了logback中类未能找到, 所以增加输出到控制台. // 此处可能导致异常的里面包含了logback中类未能找到, 所以增加输出到控制台.
......
package cn.quantgroup.tech.shutdown.configuration; package cn.quantgroup.tech.shutdown.configuration;
import cn.quantgroup.tech.shutdown.DefaultSignalHandler;
import cn.quantgroup.tech.shutdown.properties.GracefulShutdownProperties; import cn.quantgroup.tech.shutdown.properties.GracefulShutdownProperties;
import cn.quantgroup.tech.shutdown.service.JettyShutdown; import cn.quantgroup.tech.shutdown.service.JettyShutdown;
import cn.quantgroup.tech.shutdown.service.TomcatShutdown; import cn.quantgroup.tech.shutdown.service.TomcatShutdown;
import cn.quantgroup.tech.shutdown.service.UndertowShutdown; import cn.quantgroup.tech.shutdown.service.UndertowShutdown;
import cn.quantgroup.tech.shutdown.wrapper.UndertowShutdownHandlerWrapper; import cn.quantgroup.tech.shutdown.wrapper.UndertowShutdownHandlerWrapper;
import io.undertow.Undertow; import io.undertow.Undertow;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.Tomcat;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
...@@ -22,9 +23,14 @@ import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletCon ...@@ -22,9 +23,14 @@ import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletCon
import org.springframework.boot.context.embedded.undertow.UndertowDeploymentInfoCustomizer; import org.springframework.boot.context.embedded.undertow.UndertowDeploymentInfoCustomizer;
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.xnio.SslClientAuthMode;
import sun.misc.Signal;
import javax.servlet.Servlet; import javax.servlet.Servlet;
...@@ -33,12 +39,26 @@ import javax.servlet.Servlet; ...@@ -33,12 +39,26 @@ import javax.servlet.Servlet;
* This configuration class will be picked up by Spring Boot's auto configuration capabilities as soon as it's * This configuration class will be picked up by Spring Boot's auto configuration capabilities as soon as it's
* on the classpath. * on the classpath.
*/ */
@Slf4j
@Configuration @Configuration
@ConditionalOnWebApplication
@ConditionalOnProperty(prefix = "shutdown.graceful", name = "enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnProperty(prefix = "shutdown.graceful", name = "enabled", havingValue = "true", matchIfMissing = true)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@EnableConfigurationProperties(GracefulShutdownProperties.class) @EnableConfigurationProperties(GracefulShutdownProperties.class)
@Import(EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar.class) @Import(EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar.class)
public class GracefulShutdownAutoConfiguration { public class GracefulShutdownAutoConfiguration {
ConfigurableApplicationContext applicationContext;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
log.info("注册对 Signal INT 信号的接收.");
Signal.handle(new Signal("INT"), new DefaultSignalHandler(this.applicationContext));
}
/** /**
* Configuration for Tomcat. * Configuration for Tomcat.
*/ */
...@@ -49,6 +69,7 @@ public class GracefulShutdownAutoConfiguration { ...@@ -49,6 +69,7 @@ public class GracefulShutdownAutoConfiguration {
@Bean @Bean
public TomcatShutdown tomcatShutdown() { public TomcatShutdown tomcatShutdown() {
log.info("容器是 tomcat,成功构造 shutdown hook.");
return new TomcatShutdown(); return new TomcatShutdown();
} }
...@@ -76,6 +97,7 @@ public class GracefulShutdownAutoConfiguration { ...@@ -76,6 +97,7 @@ public class GracefulShutdownAutoConfiguration {
@Bean @Bean
public JettyShutdown jettyShutdown() { public JettyShutdown jettyShutdown() {
log.info("容器是 jetty,成功构造 shutdown hook.");
return new JettyShutdown(); return new JettyShutdown();
} }
...@@ -100,12 +122,13 @@ public class GracefulShutdownAutoConfiguration { ...@@ -100,12 +122,13 @@ public class GracefulShutdownAutoConfiguration {
* Configuration for Undertow. * Configuration for Undertow.
*/ */
@Configuration @Configuration
@ConditionalOnClass({Servlet.class, Undertow.class}) @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow { public static class EmbeddedUndertow {
@Bean @Bean
public UndertowShutdown undertowShutdown() { public UndertowShutdown undertowShutdown() {
log.info("容器是 undertow,成功构造 shutdown hook.");
return new UndertowShutdown(); return new UndertowShutdown();
} }
......
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.quantgroup.tech.shutdown.configuration.GracefulShutdownAutoConfiguration
\ No newline at end of file
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