前言
雪花算法呈现是为了解决分布式系统的生成惟一主键问题。
主键具备唯一性,递增性。
雪花算法
1. 第一位 占用 1bit,其值始终是 0,没有理论作用。
2. 工夫戳 占用 41bit,准确到毫秒,总共能够包容约 69 年的工夫。
3. 工作机器 id 占用 10bit,做多能够包容 1024 个节点。
4. 序列号 占用 12bit,每个节点每毫秒 0 开始一直累加,最多能够累加到 4095,一共能够产生 4096 个 ID。
代码
接下来咱们看一下代码就了解了。
public class SnowflakeIdWorker {
/**
* 开始工夫截 (2015-01-01)
*/
private final long twepoch = 1420041600000L;
/**
* 机器 id 所占的位数
*/
private final long workerIdBits = 5L;
/**
* 数据标识 id 所占的位数
*/
private final long datacenterIdBits = 5L;
/**
* 反对的最大机器 id,后果是 31 (这个移位算法能够很快的计算出几位二进制数所能示意的最大十进制数)
*/
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/**
* 反对的最大数据标识 id,后果是 31
*/
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/**
* 序列在 id 中占的位数
*/
private final long sequenceBits = 12L;
/**
* 机器 ID 向左移 12 位
*/
private final long workerIdShift = sequenceBits;
/**
* 数据标识 id 向左移 17 位 (12+5)
*/
private final long datacenterIdShift = sequenceBits + workerIdBits;
/**
* 工夫截向左移 22 位 (5+5+12)
*/
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/**
* 生成序列的掩码,这里为 4095 (0b111111111111=0xfff=4095)
*/
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/**
* 工作机器 ID(0~31)
*/
private long workerId;
/**
* 数据中心 ID(0~31)
*/
private long datacenterId;
/**
* 毫秒内序列 (0~4095)
*/
private long sequence = 0L;
/**
* 上次生成 ID 的工夫截
*/
private long lastTimestamp = -1L;
/**
* 构造函数
* @param workerId 工作 ID (0~31)
* @param datacenterId 数据中心 ID (0~31)
*/
public SnowflakeIdWorker(long workerId, long datacenterId) {if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 取得下一个 ID (该办法是线程平安的)
* @return SnowflakeId
*/
public synchronized long nextId() {long timestamp = timeGen();
// 如果以后工夫小于上一次 ID 生成的工夫戳,阐明零碎时钟回退过这个时候该当抛出异样
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
// 如果是同一时间生成的,则进行毫秒内序列
if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;
// 毫秒内序列溢出
if (sequence == 0) {
// 阻塞到下一个毫秒, 取得新的工夫戳
timestamp = tilNextMillis(lastTimestamp);
}
}
// 工夫戳扭转,毫秒内序列重置
else {sequence = 0L;}
// 上次生成 ID 的工夫截
lastTimestamp = timestamp;
// 移位并通过或运算拼到一起组成 64 位的 ID
return ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) //
| (workerId << workerIdShift) //
| sequence;
}
/**
* 阻塞到下一个毫秒,直到取得新的工夫戳
* @param lastTimestamp 上次生成 ID 的工夫截
* @return 以后工夫戳
*/
protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();
while (timestamp <= lastTimestamp) {timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒为单位的以后工夫
* @return 以后工夫 (毫秒)
*/
protected long timeGen() {return System.currentTimeMillis();
}
public static void main(String[] args) throws InterruptedException {SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
for (int i = 0; i < 10; i++) {long id = idWorker.nextId();
Thread.sleep(1);
System.out.println(id);
}
}
}