├── .gitignore ├── src └── main │ ├── java │ └── cn │ │ └── aoho │ │ └── generator │ │ ├── service │ │ ├── SnowflakeId.java │ │ ├── IdConverter.java │ │ ├── IdService.java │ │ └── impl │ │ │ ├── IdConverterImpl.java │ │ │ ├── SnowflakeIdWorker.java │ │ │ └── IdServiceImpl.java │ │ ├── entity │ │ ├── IdEntity.java │ │ ├── AssembleID.java │ │ └── IdMeta.java │ │ ├── config │ │ ├── JerseyConfig.java │ │ └── ServiceConfig.java │ │ ├── GeneratorApplication.java │ │ └── rest │ │ └── IdResource.java │ └── resources │ ├── application.yml │ └── bootstrap.yml ├── Dockerfile ├── pom.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /*.iml 3 | /target 4 | /logs 5 | .DS_Store 6 | /yunpian_error.* 7 | /yunpian_send.* 8 | -------------------------------------------------------------------------------- /src/main/java/cn/aoho/generator/service/SnowflakeId.java: -------------------------------------------------------------------------------- 1 | package cn.aoho.generator.service; 2 | 3 | /** 4 | * Created by keets on 2017/9/9. 5 | */ 6 | public interface SnowflakeId { 7 | long nextId(); 8 | } 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM 192.168.1.202/library/basejava 2 | VOLUME /tmp 3 | ADD ./target/snowflake-id-generate-1.0-SNAPSHOT.jar app.jar 4 | RUN bash -c 'touch /app.jar' 5 | ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] -------------------------------------------------------------------------------- /src/main/java/cn/aoho/generator/service/IdConverter.java: -------------------------------------------------------------------------------- 1 | package cn.aoho.generator.service; 2 | 3 | 4 | import cn.aoho.generator.entity.IdEntity; 5 | 6 | /** 7 | * Created by keets on 2017/9/9. 8 | */ 9 | public interface IdConverter { 10 | long convert(IdEntity id); 11 | 12 | IdEntity convert(long id); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cn/aoho/generator/service/IdService.java: -------------------------------------------------------------------------------- 1 | package cn.aoho.generator.service; 2 | 3 | 4 | import java.util.Date; 5 | 6 | /** 7 | * Created by keets on 2017/9/9. 8 | */ 9 | public interface IdService { 10 | long genId(); 11 | 12 | Date transTime(long time); 13 | 14 | long makeId(long time, long seq); 15 | 16 | long makeId(long time, long seq, long machine); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/cn/aoho/generator/entity/IdEntity.java: -------------------------------------------------------------------------------- 1 | package cn.aoho.generator.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * Created by keets on 2017/9/9. 11 | */ 12 | 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class IdEntity implements Serializable { 17 | private long timeStamp; 18 | private long worker; 19 | private long sequence; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | ####################### swagger starter info ########################### 2 | swagger: 3 | enabled: true 4 | title: spring-boot-id-generate 5 | config-id: id-generate 6 | version: v2 7 | license: Apache License, Version 2.0 8 | licenseUrl: https://www.apache.org/licenses/LICENSE-2.0.html 9 | termsOfServiceUrl: https://git.oschina.net/keets/snowflake-id-generator 10 | contact: aoho002@gmail.com 11 | base-path: /** 12 | resource-package: cn.aoho.generator.rest 13 | 14 | 15 | ####################### generate info ########################### 16 | generate: 17 | origin: false #是否启用原生的snowflake 18 | worker: 1001 -------------------------------------------------------------------------------- /src/main/java/cn/aoho/generator/config/JerseyConfig.java: -------------------------------------------------------------------------------- 1 | package cn.aoho.generator.config; 2 | 3 | import cn.aoho.generator.rest.IdResource; 4 | import io.swagger.jaxrs.listing.ApiListingResource; 5 | import io.swagger.jaxrs.listing.SwaggerSerializers; 6 | import org.glassfish.jersey.server.ResourceConfig; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.annotation.PostConstruct; 10 | 11 | /** 12 | * Created by keets on 2017/9/9. 13 | */ 14 | @Component 15 | public class JerseyConfig extends ResourceConfig { 16 | 17 | public JerseyConfig() { 18 | register(IdResource.class); 19 | // External 20 | } 21 | 22 | @PostConstruct 23 | public void init() { 24 | this.register(ApiListingResource.class, SwaggerSerializers.class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/cn/aoho/generator/GeneratorApplication.java: -------------------------------------------------------------------------------- 1 | package cn.aoho.generator; 2 | 3 | import cn.keets.swagger.EnableSwagger2Doc; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 8 | import org.springframework.cloud.netflix.feign.EnableFeignClients; 9 | 10 | @SpringBootApplication 11 | @Slf4j 12 | @EnableFeignClients 13 | @EnableDiscoveryClient 14 | @EnableSwagger2Doc 15 | public class GeneratorApplication { 16 | 17 | public static void main(String[] args) { 18 | log.info("start execute GeneratorApplication....\n"); 19 | SpringApplication.run(GeneratorApplication.class, args); 20 | log.info("end execute GeneratorApplication....\n"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/cn/aoho/generator/entity/AssembleID.java: -------------------------------------------------------------------------------- 1 | package cn.aoho.generator.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | 8 | import javax.validation.constraints.Max; 9 | import javax.validation.constraints.Min; 10 | 11 | /** 12 | * Created by keets on 2017/8/28. 13 | */ 14 | @ApiModel(value = "生成ID所需的参数") 15 | @Data 16 | public class AssembleID { 17 | @Max(1023) 18 | @Min(0) 19 | @ApiModelProperty(value = "机器ID") 20 | @JsonProperty("worker") 21 | private long machine = -1; 22 | 23 | @ApiModelProperty(value = "时间戳", required = true) 24 | @JsonProperty("timeStamp") 25 | private long time = -1; 26 | 27 | @Max(4095) 28 | @Min(0) 29 | @ApiModelProperty(value = "序列号", required = true) 30 | @JsonProperty("sequence") 31 | private long seq = -1; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/aoho/generator/service/impl/IdConverterImpl.java: -------------------------------------------------------------------------------- 1 | package cn.aoho.generator.service.impl; 2 | 3 | import cn.aoho.generator.entity.IdEntity; 4 | import cn.aoho.generator.entity.IdMeta; 5 | import cn.aoho.generator.service.IdConverter; 6 | 7 | /** 8 | * Created by keets on 2017/9/9. 9 | */ 10 | public class IdConverterImpl implements IdConverter { 11 | public long convert(IdEntity id) { 12 | long ret = 0; 13 | 14 | ret |= id.getSequence(); 15 | 16 | ret |= id.getWorker() << IdMeta.SEQUENCE_BITS; 17 | 18 | ret |= id.getTimeStamp() << IdMeta.TIMESTAMP_LEFT_SHIFT_BITS; 19 | 20 | return ret; 21 | } 22 | 23 | public IdEntity convert(long id) { 24 | IdEntity ret = new IdEntity(); 25 | 26 | ret.setSequence(id & IdMeta.SEQUENCE_MASK); 27 | 28 | ret.setWorker((id >>> IdMeta.SEQUENCE_BITS) & IdMeta.ID_MASK); 29 | 30 | ret.setTimeStamp((id >>> IdMeta.TIMESTAMP_LEFT_SHIFT_BITS) & IdMeta.TIMESTAMP_MASK); 31 | 32 | return ret; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/cn/aoho/generator/entity/IdMeta.java: -------------------------------------------------------------------------------- 1 | package cn.aoho.generator.entity; 2 | 3 | /** 4 | * Created by keets on 2017/8/28. 5 | */ 6 | public class IdMeta { 7 | //开始时间截 (从2015-01-01起) 8 | public static final long START_TIME = 1420041600000L; 9 | // 机器ID所占位数 10 | public static final long ID_BITS = 10L; 11 | // 机器ID最大值1023 (此移位算法可很快计算出n位二进制数所能表示的最大十进制数) 12 | public static final long MAX_ID = ~(-1L << ID_BITS); 13 | //Sequence所占位数 14 | public static final long SEQUENCE_BITS = 12L; 15 | //机器ID偏移量12 16 | public static final long ID_SHIFT_BITS = SEQUENCE_BITS; 17 | //时间戳的偏移量12+10=22 18 | public static final long TIMESTAMP_LEFT_SHIFT_BITS = SEQUENCE_BITS + ID_BITS; 19 | // Sequence掩码4095 20 | public static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS); 21 | // 机器ID掩码1023 22 | public static final long ID_MASK = ~(-1L << ID_BITS); 23 | // 时间戳掩码2的41次方减1 24 | public static final long TIMESTAMP_MASK = ~(-1L << 41L); 25 | 26 | /** 27 | * 构造方法 28 | */ 29 | private IdMeta() { 30 | //构造方法 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: ${HOST_ADDRESS:aoho}-generator-${spring.profiles.active:default} 4 | 5 | --- 6 | server: 7 | port: 9090 8 | 9 | spring: 10 | profiles: default 11 | cloud: 12 | config: 13 | discovery: 14 | service-id: config-service 15 | enabled: true 16 | consul: 17 | discovery: 18 | preferIpAddress: true 19 | enabled: true 20 | register: true 21 | service-name: generator 22 | health-check-interval: 10s 23 | health-check-timeout: 20s 24 | heartbeat: 25 | enabled: true 26 | ip-address: ${HOST_ADDRESS:localhost} 27 | port: ${SERVER_PORT:${server.port}} 28 | lifecycle: 29 | enabled: true 30 | scheme: http 31 | prefer-agent-address: false 32 | register-health-check: true 33 | tags: master 34 | retry: 35 | initial-interval: 1000 36 | max-attempts: 4 37 | max-interval: 1000 38 | host: ${CONSUL_ADDRESS:192.168.1.200} 39 | port: ${CONSUL_PORT:8500} -------------------------------------------------------------------------------- /src/main/java/cn/aoho/generator/config/ServiceConfig.java: -------------------------------------------------------------------------------- 1 | package cn.aoho.generator.config; 2 | 3 | import cn.aoho.generator.service.IdConverter; 4 | import cn.aoho.generator.service.IdService; 5 | import cn.aoho.generator.service.SnowflakeId; 6 | import cn.aoho.generator.service.impl.IdConverterImpl; 7 | import cn.aoho.generator.service.impl.IdServiceImpl; 8 | import cn.aoho.generator.service.impl.SnowflakeIdWorker; 9 | import com.ecwid.consul.v1.ConsulClient; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.core.annotation.Order; 15 | 16 | /** 17 | * Created by keets on 2017/9/9. 18 | */ 19 | @Configuration 20 | public class ServiceConfig { 21 | 22 | @Value("${generate.worker:11}") 23 | private long workId; 24 | 25 | @Value("${generate.dateCenterId:10}") 26 | private long dateCenterId; 27 | 28 | @Value("${spring.cloud.consul.host:localhost}") 29 | private String host; 30 | 31 | @Value("${spring.cloud.consul.port:8500}") 32 | private int port; 33 | 34 | @Bean 35 | @Order(1) 36 | public ConsulClient consulClient() { 37 | return new ConsulClient(host, port); 38 | } 39 | 40 | @Bean 41 | public IdService idService() { 42 | return new IdServiceImpl(workId, consulClient()); 43 | } 44 | 45 | @Bean 46 | public IdConverter idConverter() { 47 | return new IdConverterImpl(); 48 | } 49 | 50 | @Bean 51 | @ConditionalOnProperty(value = "generate.origin") 52 | public SnowflakeId snowflakeId() { 53 | return new SnowflakeIdWorker(workId, dateCenterId); 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/cn/aoho/generator/rest/IdResource.java: -------------------------------------------------------------------------------- 1 | package cn.aoho.generator.rest; 2 | 3 | import cn.aoho.generator.entity.AssembleID; 4 | import cn.aoho.generator.entity.IdEntity; 5 | import cn.aoho.generator.service.IdConverter; 6 | import cn.aoho.generator.service.IdService; 7 | import io.swagger.annotations.Api; 8 | import io.swagger.annotations.ApiOperation; 9 | import io.swagger.annotations.ApiParam; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.apache.commons.lang3.time.DateFormatUtils; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | 14 | import javax.ws.rs.Consumes; 15 | import javax.ws.rs.GET; 16 | import javax.ws.rs.POST; 17 | import javax.ws.rs.Path; 18 | import javax.ws.rs.PathParam; 19 | import javax.ws.rs.Produces; 20 | import javax.ws.rs.core.MediaType; 21 | import javax.ws.rs.core.Response; 22 | 23 | /** 24 | * Created by keets on 2017/9/9. 25 | */ 26 | 27 | @Api(value = "/") 28 | @Slf4j 29 | @Path(value = "/api") 30 | public class IdResource { 31 | 32 | @Autowired 33 | private IdService idService; 34 | 35 | @Autowired 36 | private IdConverter idConverter; 37 | 38 | 39 | @Path(value = "/id") 40 | @GET 41 | @ApiOperation(value = "生成ID", httpMethod = "GET", 42 | notes = "成功返回ID", 43 | response = long.class 44 | ) 45 | public Response genId() { 46 | return Response.ok().entity(idService.genId()).build(); 47 | } 48 | 49 | @Path("/id/{id:[0-9]*}") 50 | @GET 51 | @Produces(MediaType.APPLICATION_JSON) 52 | @ApiOperation(value = "对ID进行解析", httpMethod = "GET", 53 | notes = "成功返回解析后的ID(json格式)", 54 | response = IdEntity.class 55 | ) 56 | public Response explainId(@ApiParam(value = "要解析的ID", required = true) @PathParam("id") long id) { 57 | log.info("id is {}", id); 58 | return Response.ok().entity(idConverter.convert(id)).build(); 59 | } 60 | 61 | @Path("/time/{time:[0-9]*}") 62 | @GET 63 | @ApiOperation(value = "对时间戳进行解析", httpMethod = "GET", 64 | notes = "成功返回yyyy-MM-dd HH:mm:ss格式的日期时间", 65 | response = String.class 66 | ) 67 | public Response transTime(@ApiParam(value = "要解析的时间戳", required = true) @PathParam("time") long time) { 68 | log.info("time is {}", time); 69 | return Response.ok().entity(DateFormatUtils.format(idService.transTime(time), "yyyy-MM-dd HH:mm:ss")).build(); 70 | } 71 | 72 | @Path("/id") 73 | @POST 74 | @Consumes(MediaType.APPLICATION_JSON) 75 | @ApiOperation(value = "传入相应参数生成ID", httpMethod = "POST", 76 | notes = "成功返回ID", 77 | response = long.class 78 | ) 79 | public Response makeId(@ApiParam(value = "传入的相应参数(json格式)", required = true) AssembleID makeID) { 80 | long worker = makeID.getMachine(); 81 | long time = makeID.getTime(); 82 | long sequence = makeID.getSeq(); 83 | log.info("worker is {}", worker); 84 | log.info("time is {}", time); 85 | log.info("sequence is {}", sequence); 86 | 87 | if (time == -1 || sequence == -1) { 88 | throw new IllegalArgumentException("Both time and sequence are required."); 89 | } 90 | 91 | long ret = worker == -1 ? idService.makeId(time, sequence) : idService.makeId(time, worker, sequence); 92 | return Response.ok().entity(ret).build(); 93 | } 94 | } -------------------------------------------------------------------------------- /src/main/java/cn/aoho/generator/service/impl/SnowflakeIdWorker.java: -------------------------------------------------------------------------------- 1 | package cn.aoho.generator.service.impl; 2 | 3 | import cn.aoho.generator.service.SnowflakeId; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | /** 7 | *
8 | * Snowflake算法是带有时间戳的全局唯一ID生成算法。它有一套固定的ID格式,如下: 9 | *
10 | * 41位的时间序列(精确到毫秒,41位的长度可以使用69年) 11 | * 10位的机器标识(10位的长度最多支持部署1024个节点) 12 | * 12位的Sequence序列号(12位的Sequence序列号支持每个节点每毫秒产生4096个ID序号) 13 | *
14 | * 结构如下(每部分用-分开):
15 | * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
16 | * 优点是:整体上按照时间自增排序,且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分)
17 | */
18 | @Slf4j
19 | public class SnowflakeIdWorker implements SnowflakeId {
20 | //开始时间截 (从2015-01-01起)
21 | private static final long START_TIME = 1420041600000L;
22 | // 机器ID所占位数
23 | private static final long ID_BITS = 5L;
24 | //数据中心ID所占位数
25 | private static final long DATA_CENTER_ID_BITS = 5L;
26 | // 机器ID最大值31 (此移位算法可很快计算出n位二进制数所能表示的最大十进制数)
27 | private static final long MAX_ID = ~(-1L << ID_BITS);
28 | // 数据中心ID最大值31
29 | private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);
30 | //Sequence所占位数
31 | private static final long SEQUENCE_BITS = 12L;
32 | //机器ID偏移量12
33 | private static final long ID_SHIFT_BITS = SEQUENCE_BITS;
34 | //数据中心ID偏移量12+5=17
35 | private static final long DATA_CENTER_ID_SHIFT_BITS = SEQUENCE_BITS + ID_BITS;
36 | //时间戳的偏移量12+5+5=22
37 | private static final long TIMESTAMP_LEFT_SHIFT_BITS = SEQUENCE_BITS + ID_BITS + DATA_CENTER_ID_BITS;
38 | // Sequence掩码4095
39 | private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
40 | // 上一毫秒数
41 | private static long lastTimestamp = -1L;
42 | //毫秒内Sequence(0~4095)
43 | private static long sequence = 0L;
44 | //机器ID(0-31)
45 | private final long workerId;
46 | //数据中心ID(0-31)
47 | private final long dataCenterId;
48 |
49 | /**
50 | * 构造
51 | *
52 | * @param workerId 机器ID(0-31)
53 | * @param dataCenterId 数据中心ID(0-31)
54 | */
55 | public SnowflakeIdWorker(long workerId, long dataCenterId) {
56 | if (workerId > MAX_ID || workerId < 0) {
57 | throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_ID));
58 | }
59 | if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
60 | throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", MAX_DATA_CENTER_ID));
61 | }
62 | this.workerId = workerId;
63 | this.dataCenterId = dataCenterId;
64 | log.info(String.format("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d", TIMESTAMP_LEFT_SHIFT_BITS, DATA_CENTER_ID_BITS, ID_BITS, SEQUENCE_BITS, workerId));
65 | }
66 |
67 | /**
68 | * 生成ID(线程安全)
69 | *
70 | * @return id
71 | */
72 | @Override
73 | public synchronized long nextId() {
74 | long timestamp = timeGen();
75 |
76 | //如果当前时间小于上一次ID生成的时间戳,说明系统时钟被修改过,回退在上一次ID生成时间之前应当抛出异常!!!
77 | if (timestamp < lastTimestamp) {
78 | log.error(String.format("clock is moving backwards. Rejecting requests until %d.", lastTimestamp));
79 | throw new IllegalStateException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
80 | }
81 |
82 | //如果是同一时间生成的,则进行毫秒内sequence生成
83 | if (lastTimestamp == timestamp) {
84 | sequence = (sequence + 1) & SEQUENCE_MASK;
85 | //溢出处理
86 | if (sequence == 0) {//阻塞到下一毫秒,获得新时间戳
87 | timestamp = tilNextMillis(lastTimestamp);
88 | }
89 | } else {//时间戳改变,毫秒内sequence重置
90 | sequence = 0L;
91 | }
92 | //上次生成ID时间截
93 | lastTimestamp = timestamp;
94 |
95 | //移位并通过或运算组成64位ID
96 | return ((timestamp - START_TIME) << TIMESTAMP_LEFT_SHIFT_BITS) | (dataCenterId << DATA_CENTER_ID_SHIFT_BITS) | (workerId << ID_SHIFT_BITS) | sequence;
97 | }
98 |
99 | /**
100 | * 阻塞到下一毫秒,获得新时间戳
101 | *
102 | * @param lastTimestamp 上次生成ID时间截
103 | * @return 当前时间戳
104 | */
105 | private long tilNextMillis(long lastTimestamp) {
106 | long timestamp = timeGen();
107 | while (timestamp <= lastTimestamp) {
108 | timestamp = timeGen();
109 | }
110 | return timestamp;
111 | }
112 |
113 | /**
114 | * 获取以毫秒为单位的当前时间
115 | *
116 | * @return 当前时间(毫秒)
117 | */
118 | private long timeGen() {
119 | return System.currentTimeMillis();
120 | }
121 | }
--------------------------------------------------------------------------------
/src/main/java/cn/aoho/generator/service/impl/IdServiceImpl.java:
--------------------------------------------------------------------------------
1 | package cn.aoho.generator.service.impl;
2 |
3 | import cn.aoho.generator.entity.IdEntity;
4 | import cn.aoho.generator.entity.IdMeta;
5 | import cn.aoho.generator.service.IdConverter;
6 | import cn.aoho.generator.service.IdService;
7 | import com.ecwid.consul.v1.ConsulClient;
8 | import com.ecwid.consul.v1.Response;
9 | import com.ecwid.consul.v1.kv.model.GetValue;
10 | import lombok.extern.slf4j.Slf4j;
11 |
12 | import java.util.Date;
13 |
14 | /**
15 | * Created by keets on 2017/9/9.
16 | */
17 |
18 | @Slf4j
19 | public class IdServiceImpl implements IdService {
20 | // 上一毫秒数
21 | private static long lastTimestamp = -1L;
22 | //毫秒内Sequence(0~4095)
23 | private static long sequence = 0L;
24 | //机器ID(0-1023)
25 | private final long workerId;
26 | //各种元数据
27 | protected IdMeta idMeta;
28 |
29 | private ConsulClient consulClient;
30 |
31 | private enum Periods {
32 | START, RUNNING
33 | }
34 |
35 | /**
36 | * 构造
37 | *
38 | * @param workerId 机器ID((0-1023)
39 | */
40 | public IdServiceImpl(long workerId, ConsulClient consulClient) {
41 | if (workerId > idMeta.MAX_ID || workerId < 0) {
42 | throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", idMeta.MAX_ID));
43 | }
44 | this.workerId = workerId;
45 | this.consulClient = consulClient;
46 | validateStoredTimestamp();
47 | log.info("worker starting. timestamp left shift {}, worker id bits {}, sequence bits {}, workerid {}", idMeta.TIMESTAMP_LEFT_SHIFT_BITS, idMeta.ID_BITS, idMeta.SEQUENCE_BITS, workerId);
48 | }
49 |
50 | /**
51 | * 生成ID(线程安全)
52 | *
53 | * @return id
54 | */
55 | public synchronized long genId() {
56 | long timestamp = timeGen();
57 |
58 | //如果当前时间小于上一次ID生成的时间戳,说明系统时钟被修改过,回退在上一次ID生成时间之前应当抛出异常!!!
59 | validateTimestamp(timestamp, lastTimestamp, Periods.RUNNING);
60 |
61 | //如果是同一时间生成的,则进行毫秒内sequence生成
62 | if (lastTimestamp == timestamp) {
63 | sequence = (sequence + 1) & IdMeta.SEQUENCE_MASK;
64 | //溢出处理
65 | if (sequence == 0) {//阻塞到下一毫秒,获得新时间戳
66 | timestamp = tilNextMillis(lastTimestamp);
67 | }
68 | } else {//时间戳改变,毫秒内sequence重置
69 | sequence = 0L;
70 | }
71 | //上次生成ID时间截
72 | lastTimestamp = timestamp;
73 | consulClient.setKVValue(String.valueOf(workerId), String.valueOf(lastTimestamp));
74 | //移位并通过或运算组成64位ID
75 | return ((timestamp - idMeta.START_TIME) << idMeta.TIMESTAMP_LEFT_SHIFT_BITS) | (workerId << idMeta.ID_SHIFT_BITS) | sequence;
76 | }
77 |
78 | /**
79 | * 如果当前时间戳小于上一次ID生成的时间戳,说明系统时钟被修改过,回退在上一次ID生成时间之前应当抛出异常!!!
80 | *
81 | * @param lastTimestamp 上一次ID生成的时间戳
82 | * @param timestamp 当前时间戳
83 | */
84 | private void validateTimestamp(long timestamp, long lastTimestamp, Periods period) {
85 | if (timestamp < lastTimestamp) {
86 | log.error(String.format("clock is moving backwards. Rejecting requests until %d.", lastTimestamp));
87 | throw new IllegalStateException(String.format("Clock moved backwards in %s. Refusing to generate id for %d milliseconds", period, lastTimestamp - timestamp));
88 | }
89 | }
90 |
91 | /**
92 | * checks for timestamp by workerId when server starts.
93 | * if server starts for the first time, just let it go and log warns.
94 | * if current timestamp is smaller than the value stored in consul server, throw exception.
95 | */
96 | private void validateStoredTimestamp() {
97 | long current = timeGen();
98 | Response