Twitter的分布式自增ID算法snowflake(有改动Java版)

时间:2022-12-21 22:03:06

分布式ID生成器
全局唯一ID生成
分布式纯数字ID

其实这也不是Twitter独有的,mongodb也采用类似的方法生产自增ID。对于全局唯一ID的说明请参考我另一篇文章 : 高并发分布式环境中获取全局唯一ID[分布式数据库全局唯一主键生成]

该算法最大的好处就是:纯数字基本有序递增

为了简单起见,我这里对snowflake算法进行了一点点修改,修改后的格式为:41位时间戳 |10位进程号 |12位计数器。共计63位(为什么不是64位:第一位是符号位

加锁实现

具体逻辑情况先忙代码中的注释:

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* 64位生成规则【首位是符号位,代表正副,所以实际有效是63位】:
* <p>
* 41位时间戳 |10位进程号 |12位计数器
* <p>
* 41位时间戳:
* 注意:这里没有直接使用时间戳,因为直接使用的话只能用到2039-09-07 23:47:35
* 这里采用(当前时间戳-初始时间戳)。最多这样可以使用69年【(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69】
* 10位机器:最多1023台机器
* 12位计数器:每毫秒最多生成4095条记录
* <p>
* 这里可以根据项目中实际情况,调整每个位置的长度,比如分布式集群机器数量比较少,可以缩减机器的位数,增加计数器位数
* <p>
* <p>
* 使用注意事项:1、必须关闭时钟同步;2、currentTimeMillis一经定义,不可修改;3、并发量太高的时候,超过了4095,未做处理;4、最大不超过64位
*<p>
* 自测性能:一秒能有三四十万的数据产生。
*<p>
* 无锁没写出来......
*<p>
* Created by hzliubenlong on 2017/6/28.
*/

public class IDGeneratorUtil {


/**
* 初始时间戳:2017-1-1 0:0:0
* 一经定义,不可修改
*/

private static final long initTimeMillis = 1483200000929L;
/**
* 进程。
* 这里也可以手动指定每台实例的ID号;或者通过ZK的临时递增节点自动获取。
* 固定值
*/

private static final int pid = 3;
/**
* 计数器
* 需要保证线程安全
*/

private static volatile long counter;
private static volatile long currentTimeMillis = System.currentTimeMillis() - initTimeMillis;
private static volatile long lastTimeMillis = currentTimeMillis;


/**
* 无锁模式暂时没有写出来
*
* @return
*/

public static synchronized long nextId() {
long series = counter++;
if (series >= (1 << 12) - 1) {//单位毫秒内计时器满了,需要重新计时
while (lastTimeMillis == currentTimeMillis) {//等待到下一秒
currentTimeMillis = System.currentTimeMillis() - initTimeMillis;
}

lastTimeMillis = currentTimeMillis;
counter = 0;
series = counter++;
}
return (currentTimeMillis << 22) | (pid << 12) | series;
}
}

测试代码:

static Set<Long> set = new ConcurrentSkipListSet<>();


ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
fixedThreadPool.execute(() -> {
while (true) {
long l = nextId();
boolean add = set.add(l);
if (!add) {
System.out.println(System.currentTimeMillis() + " " + l + " : " + Long.toBinaryString(l));
System.exit(0);
}
}
})
;
}

这种加锁的实现,自测,每秒钟可以产生30W条记录。足够了

无锁实现

没写出来,下面的代码在高并发的情况下会产生重复的数据 ,不要使用。哪位大侠可以帮忙更正一下不胜感激。

public class IDGeneratorUtilNoLock {


/**
* 初始时间戳:2017-1-1 0:0:0
* 一经定义,不可修改
*/

private static final long initTimeMillis = 1483200000929L;
/**
* 进程。
* 这里也可以手动指定每台实例的ID号;或者通过ZK的临时递增节点自动获取。
* 固定值
*/

private static final int pid = 3;
/**
* 计数器
* 需要保证线程安全
*/

private static volatile AtomicLong counter = new AtomicLong(0);
private static volatile long currentTimeMillis = System.currentTimeMillis() - initTimeMillis;
private static volatile long lastTimeMillis = currentTimeMillis;
/**
* 无锁模式
* @return
*/

public static long nextId() {
long series;
while (true) {
if (lastTimeMillis == currentTimeMillis) {
series = counter.incrementAndGet();
if (series >= (1 << 12) - 1) {//同一毫秒内可能达到series最大值
while (lastTimeMillis == currentTimeMillis) {//等待到下一毫秒
currentTimeMillis = System.currentTimeMillis() - initTimeMillis;
}
counter.compareAndSet(series, 0);
continue;
}
break;
}
lastTimeMillis = currentTimeMillis;
}
return (currentTimeMillis << 22) | (pid << 12) | series;
}
}