编写一个springboot start 类型的分布式全局id生成器
2017-11-21 16:27
537 查看
因为公司最近在做分布式项目,需要用到全局的id生成器,所以在网上找了个Twitter 的Snowflake id生成器。因为项目是用springboot做的微服务,用到该类的地方比较多,于是我就把这个工具类改造成了一个springbootstart类型的工具类。
下面说下详细改造过程:
首先是SnowflakeIdWorker类,用于生成全局id
没改造前直接读取配置文件后使用:
application.yml 配置
下面开始改造成spring boot start:
首先新建立一个maven model,pom.xml主要配置如下:
建立一个SnowflakeIdWorkerProperties类,用于读取需要的属性:
下面是重点:建立SnowflakeIdWorkerAutoConfigure类用于springboot自动配置
然后在resources/META-INF 下建立一个additional-spring-configuration-metadata.json用于描述需要的字段:
整个项目图如下:
在其他项目引入该项目包:
application.yml文件同上
直接注入使用,就不用每次都配置bean:
下面说下详细改造过程:
首先是SnowflakeIdWorker类,用于生成全局id
/** * Twitter_Snowflake<br> * SnowFlake的结构如下(每部分用-分开):<br> * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - * 000000000000 <br> * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br> * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T * = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br> * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br> * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br> * 加起来刚好64位,为一个Long型。<br> * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。 */ public class SnowflakeIdWorker { // ==============================Fields=========================================== /** 开始时间截 (2015-01-01) */ private final long twepoch = 1420041600000L; /** 机器id所占的位数 */ private final long workerIdBits = 5L; /** 数据标识id所占的位数 */ private final long datacenterIdBits = 5L; /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */ private final long maxWorkerId = -1L ^ (-1L << workerIdBits); /** 支持的最大数据标识id,结果是31 */ private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); /** 序列在id中占的位数 */ private final long sequenceBits = 12L; /** 机器ID向左移12位 */ private final long workerIdShift = sequenceBits; /** 数据标识id向左移17位(12+5) */ private final long datacenterIdShift = sequenceBits + workerIdBits; /** 时间截向左移22位(5+5+12) */ private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */ private final long sequenceMask = -1L ^ (-1L << sequenceBits); /** 工作机器ID(0~31) */ private long workerId; /** 数据中心ID(0~31) */ private long datacenterId; /** 毫秒内序列(0~4095) */ private long sequence = 0L; /** 上次生成ID的时间截 */ private long lastTimestamp = -1L; // ==============================Constructors===================================== /** * 构造函数 * * @param workerId * 工作ID (0~31) * @param datacenterId * 数据中心ID (0~31) */ public SnowflakeIdWorker(long workerId, long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException( String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException( String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } // ==============================Methods========================================== /** * 获得下一个ID (该方法是线程安全的) * * @return SnowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 if (timestamp < lastTimestamp) { throw new RuntimeException(String.format( "Clock moved backwards. Refusing to generate id for %d milliseconds 4000 ", lastTimestamp - timestamp)); } // 如果是同一时间生成的,则进行毫秒内序列 if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; // 毫秒内序列溢出 if (sequence == 0) { // 阻塞到下一个毫秒,获得新的时间戳 timestamp = tilNextMillis(lastTimestamp); } } // 时间戳改变,毫秒内序列重置 else { sequence = 0L; } // 上次生成ID的时间截 lastTimestamp = timestamp; // 移位并通过或运算拼到一起组成64位的ID return ((timestamp - twepoch) << timestampLeftShift) // | (datacenterId << datacenterIdShift) // | (workerId << workerIdShift) // | sequence; } /** * 阻塞到下一个毫秒,直到获得新的时间戳 * * @param lastTimestamp * 上次生成ID的时间截 * @return 当前时间戳 */ protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 返回以毫秒为单位的当前时间 * * @return 当前时间(毫秒) */ protected long timeGen() { return System.currentTimeMillis(); } }
没改造前直接读取配置文件后使用:
@Bean public SnowflakeIdWorker snowflakeIdWorker(@Value("${snowflakeIdWorker.workerId}") long workerId, @Value("${snowflakeIdWorker.datacenterId}") long datacenterId) { return new SnowflakeIdWorker(workerId, datacenterId); } @Autowired private SnowflakeIdWorker snowflakeIdWorker; snowflakeIdWorker.nextId();
application.yml 配置
#唯一编号生成器 snowflakeIdWorker: workerId: 0 datacenterId: 1
下面开始改造成spring boot start:
首先新建立一个maven model,pom.xml主要配置如下:
<groupId>com.weemambo.saas</groupId> <artifactId>component-util</artifactId> <name>component-util</name> <description>component-util for server</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> </parent> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependencyManagement> <dependencies> <dependency> <!-- Import dependency management from Spring Boot --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>1.5.8.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
建立一个SnowflakeIdWorkerProperties类,用于读取需要的属性:
import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("snowflakeIdWorker") public class SnowflakeIdWorkerProperties { /** 工作机器ID(0~31) */ private long workerId; /** 数据中心ID(0~31) */ private long datacenterId; public long getWorkerId() { return workerId; } public void setWorkerId(long workerId) { this.workerId = workerId; } public long getDatacenterId() { return datacenterId; } public void setDatacenterId(long datacenterId) { this.datacenterId = datacenterId; } }
下面是重点:建立SnowflakeIdWorkerAutoConfigure类用于springboot自动配置
@Configuration @ConditionalOnClass(SnowflakeIdWorker.class) @EnableConfigurationProperties(SnowflakeIdWorkerProperties.class) public class SnowflakeIdWorkerAutoConfigure { @Autowired private SnowflakeIdWorkerProperties snowflakeIdWorkerProperties; @Bean @ConditionalOnMissingBean SnowflakeIdWorker snowflakeIdWorker() { return new SnowflakeIdWorker(snowflakeIdWorkerProperties.getWorkerId(), snowflakeIdWorkerProperties.getDatacenterId()); } }
然后在resources/META-INF 下建立一个additional-spring-configuration-metadata.json用于描述需要的字段:
{"properties": [ { "name": "snowflake-id-worker.worker-id", "type": "java.lang.Long", "description": "工作机器ID(0~31)" }, { "name": "snowflake-id-worker.datacenter-id", "type": "java.lang.Long", "description": "数据中心ID(0~31)" }
整个项目图如下:
在其他项目引入该项目包:
<dependency> <groupId>com.weemambo.saas</groupId> <artifactId>component-util</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
application.yml文件同上
直接注入使用,就不用每次都配置bean:
@Autowired private SnowflakeIdWorker snowflakeIdWorker; snowflakeIdWorker.nextId();
相关文章推荐
- 一个全局ID生成器
- 如何快速开发一个支持高效、高并发的分布式ID生成器(一)
- 如何快速开发一个支持高效、高并发的分布式ID生成器(二)
- 编写spring boot的一个starter
- 细聊分布式ID生成方法的实现(一个可以使用10年的序列生成器)
- spring boot / cloud (十六) 分布式ID生成服务
- Spring Boot 微框架学习(利用Spring Boot编写一个访问数据库的helloword)
- Spring boot多模块(moudle)中的一个注入错误(Unable to start embedded container; nested exception is org)
- Spring AOP 一个类实现接口时遇到id类型转换错误
- 如何使用MongoDB+Springboot实现分布式ID?
- Spring-boot+分布式下高性能全局对象唯一ID生成器代码+实例演示
- 分布式全局ID生成器设计
- 如何使用MongoDB+Springboot实现分布式ID?
- Spring Boot使用全局类型转换器(全局日期转换器为例)
- 如何快速开发一个支持高效、高并发的分布式ID生成器(三)
- spring boot / cloud (十六) 分布式ID生成服务
- 从头开始搭建一个Spring boot+ActiveMQ高可用分布式环境
- 如何使用MongoDB+Springboot实现分布式ID
- Objective-C 中,id 类型是一个独特的数据类型
- IDEA启动Spring Boot项目提示Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServlet...