分布式中生成唯一性ID的一种算法 。
为啥不用数据库的自增主键呢?
唯一性: 如果数据库数据特别多,你会同一张表建立不同节点上,数据也在不同节点上存,那么如果俩ID都是 001 违背主键 定义吗?
顺序性: 雪花算法计算出来ID 有顺序 如果你了解数据库 B+树 ,对于索引来说 字段是 数字类型 ,有顺序, 唯一 在查找以及插入效率很高 而UUID是字符串没顺序不适合做数据库主键了
雪花算法 由64位构成ID,对应java数据类型的话 long类型 这里位是二进制位
时钟回拨: 一种情况管理员手动把时间调整当前系统之前时间,这样的话生成ID和之前ID可能冲突了。虽然上边时间戳插值减轻该问题但是插值仍有可能为正值,
首先移动好说的 。数据中心ID datacenterID (属于instance) 假设 1L 号 用long表示可以 (二进制)
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
将它左移动12位(序列号最长位)代码 datacenterID << 12 结果
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000
机器ID 左移动 12位(序列号) + 5位(数据中心ID)= 17 workID << 17
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0000 0000 0000 0000
我们随便用时间戳差值 1110 0101 1010 0001 0001 10111 11 那么他图中右移12 + 5 + 5 = 22位
0000 0000 0000 0000 1110 0101 1010 0001 0001 10111 1100 0000 0000 0000 0000 0000
序列号最右边所以不用移动
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
接下来如何合并时间戳,机器ID,数据中心ID,序列号呢?
可以将两个long类型 按位或 进行合并 (按位或 只要有1 结果1 否则是0)
拿数据中心ID和机器ID举例子
(或 | )结果 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0001 0000 0000 0000
这样进行合并了 3.1问题解决了。
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
该时间再生成ID就是
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000 0000
对于这个我们构建一个类似于掩码数字 二进制表示
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
进行按位与 (&)两个都为1结果是1 否则为0
上边两个& 结果刚好 0 这个结果可以判断是否溢出
对于构建掩码那个数字 可以 -1 ^ (-1 << 12) ^ 是异或符号 两个二进制位 不同为1 相同为0
-1 的二进制如下
1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
Clock clock = Clock.systemUTC(); long currentTimestamp = clock.millis();
代码如下(示例):
package com; public class SnowFlakeUtil{ //起始时间戳 private static long startTimeStamp; //机器ID private static long workID; //数据中心ID private static long dataCenterID; //序列号 private static long sequence; //数据中心ID移动位数 private static long dataCenterIndex; //机器ID移动位数 private static long workIDIndex; //时间戳移动位数 private static long timeStampIndex; //记录上一次时间戳 private static long lastTimeStamp; //序列号掩码 private static long sequenceMask; //序列号长度12位 private static long sequenceLength; //初始化数据 static { startTimeStamp = 1577808000000L; //设置机器编号 1 workID = 1L; //设置数据中心ID 1 dataCenterID = 1L; //起始序列号 0开始 sequence = 0L; //数据中心位移位数 dataCenterIndex = 12L; //机器ID位移位数 workIDIndex = 17L; //时间戳位移位数 timeStampIndex = 22L; //记录上次时间戳 lastTimeStamp = -1L; //序列号长度 sequenceLength = 12L; //序列号掩码 sequenceMask = -1L ^ (-1L << sequenceLength); } public synchronized static long getID(){ //获得当前时间 long now = System.currentTimeMillis(); //当前系统时间小于上一次记录时间 if (now < lastTimeStamp){ throw new RuntimeException("时钟回拨异常"); } //相同时间 要序列号进制增量 if (now == lastTimeStamp){ //防止溢出 sequence = (sequence + 1) & sequenceMask; if (sequence == 0L){ //溢出处理 try { Thread.sleep(1L); } catch (InterruptedException e) { throw new RuntimeException(e); } //获取下一毫秒时间 (有锁) now = System.currentTimeMillis(); } }else{ //置0 之前序列号同一时间并发后自增 到这里说明时间不同了 版本号所以置0 sequence = 0L; } //记录当前时间 lastTimeStamp = now; //当前时间和起始时间插值 右移 22位 long handleTimeStamp = (now - startTimeStamp) << timeStampIndex; // 数据中心数值 右移动 17位 并且 按位或 long handleWorkID = (dataCenterID << dataCenterIndex) | handleTimeStamp; // 机器ID数值 右移动 12位 并且 按位或 long handleDataCenterID = (workID << workIDIndex) | handleWorkID; // 序列号 按位或 long ID = handleDataCenterID | sequence; return ID; } }
//用法调用工具类方法即可 long id = SnowFlakeUtil.getID();
org.springframework.boot spring-boot-configuration-processor true
注意包名和你项目包路径要一致 该文件放在启动类同级目录或者子目录
package com; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "sf") public class SnowFlakeProperties { //起始时间戳 private long startTimeStamp; //机器ID private long workID; //数据中心ID private long dataCenterID; //序列号 private long sequence; //数据中心ID移动位数 private long dataCenterIndex; //机器ID移动位数 private long workIDIndex; //时间戳移动位数 private long timeStampIndex; //记录上一次时间戳 private long lastTimeStamp; //序列号掩码 private long sequenceMask; //序列号长度12位 private long sequenceLength; public SnowFlakeProperties() { //默认时间 2020-1-1 00:00:00 this.startTimeStamp = 1577808000L; //设置机器编号 1 this.workID = 1L; //设置数据中心ID 1 this.dataCenterID = 1; //起始序列号 0开始 this.sequence = 0L; //数据中心位移位数 this.dataCenterIndex = 12L; //机器ID位移位数 this.workIDIndex = 17L; //时间戳位移位数 this.timeStampIndex = 22L; //记录上次时间戳 this.lastTimeStamp = -1L; //序列号长度 this.sequenceLength = 12L; //序列号掩码 等同于 -1L ^ (-1L << 12) this.sequenceMask = ~ (-1L << sequenceLength); } public SnowFlakeProperties(long startTimeStamp, long workID, long dataCenterID, long sequence, long dataCenterIndex, long workIDIndex, long timeStampIndex, long lastTimeStamp, long sequenceMask, long sequenceLength) { this.startTimeStamp = startTimeStamp; this.workID = workID; this.dataCenterID = dataCenterID; this.sequence = sequence; this.dataCenterIndex = dataCenterIndex; this.workIDIndex = workIDIndex; this.timeStampIndex = timeStampIndex; this.lastTimeStamp = lastTimeStamp; this.sequenceMask = sequenceMask; this.sequenceLength = sequenceLength; } public long getStartTimeStamp() { return startTimeStamp; } public void setStartTimeStamp(long startTimeStamp) { this.startTimeStamp = startTimeStamp; } public long getWorkID() { return workID; } public void setWorkID(long workID) { this.workID = workID; } public long getDataCenterID() { return dataCenterID; } public void setDataCenterID(long dataCenterID) { this.dataCenterID = dataCenterID; } public long getSequence() { return sequence; } public void setSequence(long sequence) { this.sequence = sequence; } public long getDataCenterIndex() { return dataCenterIndex; } public void setDataCenterIndex(long dataCenterIndex) { this.dataCenterIndex = dataCenterIndex; } public long getWorkIDIndex() { return workIDIndex; } public void setWorkIDIndex(long workIDIndex) { this.workIDIndex = workIDIndex; } public long getTimeStampIndex() { return timeStampIndex; } public void setTimeStampIndex(long timeStampIndex) { this.timeStampIndex = timeStampIndex; } public long getLastTimeStamp() { return lastTimeStamp; } public void setLastTimeStamp(long lastTimeStamp) { this.lastTimeStamp = lastTimeStamp; } public long getSequenceMask() { return sequenceMask; } public void setSequenceMask(long sequenceMask) { this.sequenceMask = sequenceMask; } public long getSequenceLength() { return sequenceLength; } public void setSequenceLength(long sequenceLength) { this.sequenceLength = sequenceLength; } }
package com; public class SnowFlake { private SnowFlakeProperties properties; public SnowFlake() { } public SnowFlake(SnowFlakeProperties properties) { this.properties = properties; } public synchronized long getID(){ //获得当前时间 long now = System.currentTimeMillis(); long lastTimeStamp = properties.getLastTimeStamp(); //当前系统时间小于上一次记录时间 if (now < lastTimeStamp){ throw new RuntimeException("时钟回拨异常"); } //相同时间 要序列号进制增量 if (now == lastTimeStamp){ //防止溢出 long sequence = properties.getSequence(); sequence = (sequence + 1) & properties.getSequenceMask(); //更新sequence 的值 properties.setSequence(sequence); if (sequence == 0L){ //溢出处理 try { Thread.sleep(1L); } catch (InterruptedException e) { throw new RuntimeException(e); } //获取下一毫秒时间 (有锁) now = System.currentTimeMillis(); } }else { //置0 properties.setSequence(0L); } //记录当前时间 properties.setLastTimeStamp(now); return ((now - properties.getStartTimeStamp()) << properties.getTimeStampIndex()) | (properties.getDataCenterID() << properties.getDataCenterIndex())| (properties.getWorkID() << properties.getWorkIDIndex()) | properties.getSequence(); } }
@EnableConfigurationProperties(SnowFlakeProperties.class)
@Bean public SnowFlake snowFlake(SnowFlakeProperties properties) { return new SnowFlake(properties); }
package com; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(SnowFlakeProperties.class) public class Config { @Bean public SnowFlake snowFlake(SnowFlakeProperties properties) { return new SnowFlake(properties); } }
package com; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class Controller { //注入即可 @Autowired public SnowFlake snowFlake; @RequestMapping("/hello") public Map getID(){ //这里直接调用该方法 获取雪花算法生成ID long id = snowFlake.getID(); HashMap
当然你不设置也可以 默认1号机器 1号数据中心
配置文件
如果没有提示重新构建项目
可以通过配置文件来设置
雪花算法 41位时间戳 不要动 剩下(53-41=12) 剩下12位 可以分6位的序列号 ,数据中心分2位 机器ID分4位 配置文件如
sf: work-i-d: 3 #序列号长度 sequence-length: 6 #数据中心位移移 位数 data-center-index: 6 #机器ID位移移 位数 work-i-d-index: 8 #时间戳位移移 位数 time-stamp-index: 12
结果如图
可以看到序列号进行递增
参考:博客 请叫我小叶子
如果觉得有帮助 求一个攒赞 感谢!!!
上一篇:MySQL知识总结