您的位置:首页 > 其它

分布式ID生成器-雪花算法(snowflake)

2021-06-23 16:45 447 查看
背景

当下绝大部分互联网公司采用的是分布式的架构系统,而分布式系统中有一些场景需要使用到全局性唯一ID,例如:订单编号、付款单编号、交易流水号等等,在这之前,我们可以使用UUID、数据库自增ID等去实现它,但是要么生成的ID是无序的,要么ID生成效率低下。
所以在该背景下,twitter公司提出了snowflake算法,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID生成机制,为了满足Twitter每秒上万条消息的请求,每条消息都必须分配一条唯一的id,这些id还需要一些大致的顺序(方便客户端排序),并且在分布式系统中不同机器产生的id必须不同,所以twitter开发了这样一套全局唯一ID生成服务。

介绍
snowflake算法生成的64位ID = 1位符号位 + 41位时间戳 + 10位机器id + 12位序列号。


  64位ID组成结构.png

1、符号位:一般默认为0,表明该ID为正数。

2、时间戳:该时间戳并不是指当前的时间戳,而是当前时间戳-初始时间戳(

初始时间戳离当前时间越近越好,而且必须小于当前时间戳
)的值。这样做的好处是有更多的数值可以使用,最多可以使用(1L << 41)/ (1000毫秒 * 60秒 * 60 分钟 * 24小时 * 365天) = 69年。

3、机器id:取值在0 ~ 1023之间,最多支持1024个节点。
假设你们公司的机器节点超过了1024个,而你们的业务没有达到1毫秒需要2408个序列号的并发的情况下,可以将序列号的位数减少,给机器id增加位数。

4、序列号,取值在0~4096之间,最多支持一毫秒内生成4096个序列号。
假设你们公司的机器节点没有超过512个,而你们的业务1毫秒需要超过4096个序列号的并发的情况下,可以将机器id的位数减少,给序列号增加位数。

优点:
1、完全基于内存,ID生成效率高。
2、生成的ID基于时间戳和序列号,具有有序性的特点,方便排序、查询。
3、可以根据生产机器节点数和业务并发量的情况,调整ID的生成策略。

代码:

package com.jiepos.api.demo;

/**
* 雪花算法
* 64位ID = 1位符号位(固定为0,表示正数) + 41位时间戳 + 10位工作机器id + 12位序列号
* @author shuyan.qi
* @date 2020/5/3 9:42 下午
*/
public class SnowFlakeID {
//12位序列号
private long sequence;//序列号
private long sequenceBits = 12L;//序列号位数
private long maxSequence = -1 ^ (-1 << sequenceBits);//序列号最大值

//10位工作机器id = 5位机房id + 5位机器id
private long workerId;//机器id
private long workerIdBits = <
ad8
span class="token number">5L;//机器id位数
private long maxWorkerId = -1 ^ (-1 << workerIdBits);//机器id最大值
private long workerIdMoveBits = sequenceBits;//机器id左移位数 = 序列号位数
private long workerIdAfterMove = 0L;//左移后的机器id

private long workerCenterId;//机房id
private long workerCenterIdBits = 5L;//机房id位数
private long maxWorkerCenterId = -1 ^ (-1 << workerCenterIdBits);//机房id最大值
private long workerCenterIdMoveBits = workerIdBits + workerIdMoveBits;//机房id左移位数 = 机器id位数 + 序列号位数
private long workerCenterIdAfterMove = 0L;//左移后的机房id

//41位时间戳
private long lastTimestamp = -1L;//默认-1L
private long initTimestamp = 1588518046057L;//初始时间戳
private long timestampMoveBits = workerCenterIdBits + workerCenterIdMoveBits;//时间戳左移位数 = 机房id位数 + 机器id位数 + 序列号位数

public SnowFlakeID(long workerCenterId , long workerId){
if(workerCenterId < 0 || workerCenterId > maxWorkerCenterId){
throw new IllegalArgumentException("workerCenterId is illegal");
}
if(workerId < 0 || workerId > maxWorkerId){
throw new IllegalArgumentException("workerId is illegal");
}
this.workerCenterId = workerCenterId;
this.workerId = workerId;
this.workerCenterIdAfterMove = this.workerCenterId << this.workerCenterIdMoveBits;
this.workerIdAfterMove = this.workerCenterId << this.workerCenterIdMoveBits;
}

/**
* 生成ID的核心方法
*/
public synchronized long nextId(){
long currentTimestamp = timestamp();
if(currentTimestamp < lastTimestamp){
String s = String.format("currentTimestamp is earlier than lastTimestamp,lastTimestamp=%s,currentTimestamp=%s",lastTimestamp,currentTimestamp);
System.out.println(s);
//throw new RuntimeException(s);
// 时钟回拨后手动拨正。
// 因为依赖lastTimestamp,所以重启后第一次就发生时钟回拨的情况无法处理。
// 可以将lastTimestamp存放到redis之类第三方缓存中,但这样生成id的效率会降低,请开发者根据实际情况去选择。
currentTimestamp = lastTimestamp;
}
if(currentTimestamp == lastTimestamp){
//同一时间戳,序列号加1
sequence = (sequence + 1) & maxSequence;
if(sequence == 0L){
//如果序列号加1后的值为0,表示当前时间戳内的序列号已用完,需要获取下一个时间戳
currentTimestamp = nextTimestamp(currentTimestamp);
}
}else{
sequence = 0L;//不同时间戳,重置序列号
}
lastTimestamp = currentTimestamp;//更新成功生成id的最新时间戳
return ((currentTimestamp - initTimestamp) << timestampMoveBits) | workerCenterIdAfterMove | workerIdAfterMove | sequence;
}

/**
* 获取timestamp的下一毫秒数
* @param timestamp 当前毫秒数
* @return
*/
public long nextTimestamp(long timestamp){
long timestamp1 = 0L;
do{
timestamp1 = timestamp();

23d2
}while (timestamp >= timestamp1);
return timestamp1;
}

/**
* 获取当前时间戳
* @return
*/
public long timestamp(){
return System.currentTimeMillis();
}

public static void main(String[] args) throws InterruptedException {

/*
//测试并发
Map<String,Object> map = new ConcurrentHashMap<>();
SnowFlakeID snowFlakeID = new SnowFlakeID(1, 1);
for(int i = 0;i < 100;i++){
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0 ;j < 100000;j++){
map.put(String.valueOf(snowFlakeID.nextId()),1);
}
}).start();;
}

for(;;){
TimeUnit.SECONDS.sleep(1);
System.out.println("size="+map.size());
}

*/

/*
//测试速度
SnowFlakeID snowFlakeID = new SnowFlakeID(1, 1);
long startTime = System.currentTimeMillis();
for(int i = 0;i < 3000000;i++){
snowFlakeID.nextId();
}
System.out.println("耗时:"+(System.currentTimeMillis() - startTime)/1000.0d + "秒");
*/
}


作者:钢铁加鲁鲁_d59c
链接:https://www.jianshu.com/p/d230443d0e60
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: