雪花算法
话不多说,直接上代码,看注释:
import java.sql.Date;
import java.time.DateTimeException;
import java.util.concurrent.CountDownLatch;
/*
* @Description 雪花算法实践
* @author fangqi
* @date 2021/10/17
*/
public class SnowFlake {
//雪花算法:组成ID的由64位二进制(8字节=Long的长度)来表示,主要由以下部分组成:
//第一部分:1bit = 0,表示正整数
//第二部分:41bit ,表示时间戳(毫秒),最大可以表示(2^41-1)/(365 * 24 * 60 * 60 * 1000) = 69年
//第三部分:10bit ,又可以分为 5bit,表示机房号,5bit表示机房中的机器号,由于机房号和机器号都可以从0开始,所以 10bit能够表示的最大机器数 = 2^5*2^5=2^10=1024
//第四部分:12bit ,表示同一毫秒内生成的id数,最大可以生成 2^12-1=4095 个ID(id必须大于0)
private static final int fixBit = 1;
private static final int timeStampBit = 41;
private static final int dataCenterIdBit = 5;
private static final int workIdBit = 5;
private static final int sequenceIdBit = 12;
private static final int zero = 0;
//最大的机房ID
private static final long maxDataCenterId = -1 ^ (-1 << dataCenterIdBit);
//最大的机器ID
private static final long maxWorkId = -1 ^ (-1 << workIdBit);
//序列码,即组成雪花算法的最后12bit表示的最大值,该值与任何值进行'按位于'操作,使得序列ID始终小于等于4095
private static final long sequenceMask = -1L ^ (-1L << sequenceIdBit);
//默认偏移时间毫秒为1分钟
private static final long defaultOffsetTimeMillis = 1 * 60 * 1000;
//偏移时间大于该值,这告警,打印warn日志
private static final long warningOffsetTimeMillis = 2 * 60 * 1000;
//默认的起始时间
private static final long defaultTimeEpoch = Date.valueOf("2021-10-19").getTime();
//机房ID>=0
private long dataCenterId;
//机器ID>=0
private long workId;
//序列号ID
private long sequenceId;
//最近一次生成ID的时间戳,用于跟当前时间戳对比,如果相等则sequenceId加1,表示统一毫秒内生成的ID
private long lastTimeMillis;
//起始时间,最大可以记录2^41 - 1的时间戳,即69年,不能大于当前时间戳
//注意:该值不能随时变动,如果是针对同一个业务获取ID,那么该值设置后就不能在变动,否则会导致最终生成的ID可能会出现重复
//假设:
// 第一次设置起始时间=1000,当执行一段时间后的时间为5000,那么5000-1000=4000;
// 第二次设置起始时间=6000,执行一段时间后的时间为10000,那么10000-6000=4000;
// 这样就会导致41位时间戳出现重复
private long timeEpoch=defaultTimeEpoch;
//允许当前lastTimeMillis跟当前时间戳的最大偏移毫秒数(lastTimeMillis-currentTimeMillis<=lastTimeMillis)
//如果在允许范围内,可以一直循环获取当前时间,知道当前时间大于最后一次生成ID的时间
//避免因为相差太大,导致获取下一个时间时,一直在循环中,所以该值需要慎重
private long offsetTimeMills=defaultOffsetTimeMillis;
public SnowFlake(long dataCenterId, long workId, long randomNum){
this.validateDataCenterId(dataCenterId);
this.validateWorkId(workId);
if(randomNum < 0){
throw new IllegalArgumentException("随机值不能小于0");
}
this.dataCenterId = dataCenterId;
this.workId = workId;
this.sequenceId = randomNum;
}
//设置起始时间
public void setTimeEpoch(long timeEpoch){
if(timeEpoch > this.curerrentTimeMillis()){
throw new IllegalArgumentException("初始时间戳不能大于当前时间戳");
}
this.timeEpoch = timeEpoch;
}
//设置时间偏移量
public void setOffsetTimeMills(long offsetTimeMills){
if(offsetTimeMills < 0){
throw new IllegalArgumentException("偏移时间戳不能小于0");
}
if(offsetTimeMills > warningOffsetTimeMillis){
System.out.println("当前偏移时间值偏大,请确认是否进行修改......");
}
this.offsetTimeMills = offsetTimeMills;
}
/**
* 获取下个ID
* @return
*/
public synchronized Long nextId(){
int timeLeftMoveBits = dataCenterIdBit + workIdBit + sequenceIdBit;
int dataCenterIdLeftMoveBits = workIdBit + sequenceIdBit;
int workIdLeftMoveBits = sequenceIdBit;
long currentTimeMillis = this.curerrentTimeMillis();
if(lastTimeMillis < currentTimeMillis){
sequenceId = 0;
}else{
//如果时间戳的差值大于了允许的最大偏移值,那么抛出异常,否则循环获取时间戳
if(lastTimeMillis - currentTimeMillis > offsetTimeMills) {
throw new DateTimeException(String.format("最后生成ID的时间戳与当前时间戳的差值已经大于设置的offsetTimeMills=%d", offsetTimeMills));
}else{
//该操作保证sequenceId始终是小于等于sequenceMask
sequenceId = (sequenceId + 1) & sequenceMask;
if(sequenceId == zero || lastTimeMillis - currentTimeMillis <= offsetTimeMills) {
currentTimeMillis = this.nextTimeMillis();
}
}
}
lastTimeMillis = currentTimeMillis;
//计算最终生成的ID:
//lastTimeMillis-timeEmpoch,目的是为了可以让41位二进制表示的时间更久(当然也可以不用减去timeEmpoch)
//上面已经将对应每个部分的值都算出了,只需要往左移动响应的位数,然后将左移后的值一次做'按位或'操作,即可得到最终的ID
return ((lastTimeMillis - timeEpoch) << timeLeftMoveBits) |
(this.dataCenterId << dataCenterIdLeftMoveBits) |
(this.workId << workIdLeftMoveBits) |
(this.sequenceId);
}
private void validateDataCenterId(long dataCenterId){
if(dataCenterId > maxDataCenterId || dataCenterId < zero){
throw new IllegalArgumentException(String.format("传入的机房ID不能大于%d或小于%d", maxDataCenterId, zero));
}
}
private void validateWorkId(long workId){
if(workId > maxWorkId || workId < zero){
throw new IllegalArgumentException(String.format("传入的机器ID不能大于%d或小于%d", maxWorkId, zero));
}
}
private long nextTimeMillis(){
long currentTimeMillis = 0L;
do{
currentTimeMillis = this.curerrentTimeMillis();
}while (currentTimeMillis <= lastTimeMillis);
return currentTimeMillis;
}
private long curerrentTimeMillis(){
return System.currentTimeMillis();
}
public static void main(String[] args) throws InterruptedException {
// System.out.println(4095 & 4099);
// System.out.println(4095 & 4);
// System.out.println( -1 ^ (-1 << 12) );
// System.out.println(String.format("传入的机房ID不能大于%d或小于%d", 1, 2));
// System.out.println(Date.valueOf("2021-10-19").getTime());
SnowFlake snowFlake = new SnowFlake(0, 0, 3);
CountDownLatch countDownLatch = new CountDownLatch(10000);
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(snowFlake.nextId());
}
}).start();
countDownLatch.countDown();
}
countDownLatch.await();
}
参考文献:https://blog.csdn.net/lq18050010830/article/details/89845790