学习雪花算法
package com.yupi.cicd.test;import org.springframework.stereotype.Component;/*** 雪花算法ID生成器* 技术亮点:解决分布式环境下的全局唯一ID生成问题*/
@Component
public class SnowflakeIdGenerator {// 起始时间戳 (2024-01-01)private final long START_TIMESTAMP = 1704067200000L;// 机器ID位数private final long MACHINE_ID_BITS = 5L;// 数据中心ID位数private final long DATACENTER_ID_BITS = 5L;// 序列号位数private final long SEQUENCE_BITS = 12L;// 最大机器IDprivate final long MAX_MACHINE_ID = ~(-1L << MACHINE_ID_BITS);// 最大数据中心IDprivate final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);// 最大序列号private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);// 机器ID左移位数private final long MACHINE_ID_SHIFT = SEQUENCE_BITS;// 数据中心ID左移位数private final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS;// 时间戳左移位数private final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS + DATACENTER_ID_BITS;private long machineId;private long datacenterId;private long sequence = 0L;private long lastTimestamp = -1L;public SnowflakeIdGenerator() {this(1L, 1L);}public SnowflakeIdGenerator(long machineId, long datacenterId) {if (machineId > MAX_MACHINE_ID || machineId < 0) {throw new IllegalArgumentException("机器ID超出范围");}if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {throw new IllegalArgumentException("数据中心ID超出范围");}this.machineId = machineId;this.datacenterId = datacenterId;}/*** 生成下一个ID* 技术难点:确保在高并发环境下ID的唯一性*/public synchronized long nextId() {long timestamp = System.currentTimeMillis();// 时钟回拨检查if (timestamp < lastTimestamp) {throw new RuntimeException("时钟回拨异常,拒绝生成ID");}// 同一毫秒内序列号递增if (timestamp == lastTimestamp) {sequence = (sequence + 1) & MAX_SEQUENCE;if (sequence == 0) {// 序列号溢出,等待下一毫秒timestamp = waitNextMillis(lastTimestamp);}} else {sequence = 0L;}lastTimestamp = timestamp;// 组装ID:时间戳 + 数据中心ID + 机器ID + 序列号return ((timestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)| (datacenterId << DATACENTER_ID_SHIFT)| (machineId << MACHINE_ID_SHIFT)| sequence;}/*** 等待下一毫秒*/private long waitNextMillis(long lastTimestamp) {long timestamp = System.currentTimeMillis();while (timestamp <= lastTimestamp) {timestamp = System.currentTimeMillis();}return timestamp;}
}
package com.yupi.cicd.test;public class testSonw {public static void main(String[] args) {System.out.println("hello world");SnowflakeIdGenerator snowflakeIdGenerator = new SnowflakeIdGenerator();for (int i = 0; i < 19; i++) {long l = snowflakeIdGenerator.nextId();System.out.println(l);}}
}
为什么不推荐使用数据库自增主键?也不推荐使用UUID作主键,用雪花算法会有什么问题?
自增
在分库分表的时候会出现问题
每一个表都有一个自增,这个时候ID就不能保证唯一性了
也可以用步长来解决问题(类似于分库分表的时候强制唯一)但是后续如果要扩容就是由三个表变成四个表,数据工作量很大,划分规则可能要变,旧数据怎么办
UUID
1.影响查询性能
UUID比较长占用的空间比较大,每行数据也大,那么同样的数据量需要page页 page页越多
索引树的高度也就越高,遍历次数也多,遍历page页也多,page也又是内存和磁盘交互的最最小单位,IO次数多
2.操作性能
UUID无序,非趋势递增
B+树内部株建索引是要进行id 的排序,添加一个新数据的时候,需要对数据的内容进行重排序
3.雪花算法
符号位为最高位,用long来存储,64位
是趋势递增的,是64bit的二进制,占用的空间小很多
一个典型的64位ID结构如下:
- 1位符号位(固定为0)
- 41位时间戳(毫秒级,可以使用约69年)(2的1次方/1000*60*60*24)
- 10位机器ID(可以配置,通常包括5位数据中心ID和5位机器ID)(2的10次方=1024台机器)
数据中心可以代表机房,比如使用云服务器,华南华北还有不用的机房
机器ID:每一个机房里面有不同的这个机器
- 12位序列号(同一毫秒内生成的序列号,每毫秒最多4096个)
也有三个问题
1.时间回拨的问题
系统时间变了不正确,如果改成过去的时候就乱了,ID就不是趋势递增的了
解决:
1.直接抛出异常,发现生成的id时间戳比之前的要小的时候直接抛出异常
2.采用备用方案:比如随机
2.机器ID的管理问题
集群部署的时候,100台机器,要去维护机器ID不重复是成本比较大
解决
1.机器ID一般通过配置文件去配置,
3.序列号一直是0的问题
分库分表场景数据不均匀
当为好为0的时候获取时间错的最后一位换一个