雪花算法(SnowFlake)-java
程序员文章站
2022-05-03 14:48:51
...
雪花算法
简介: 雪花算法 是 Twitter 开源的分布式id生成算法。使用一个64bit 的long型数字作为全局id。
优点:ID 自增,不会产生重复ID,在本地生成不会消耗网络效率高,存入数据库索引效率高。
缺点:依赖于系统时间的强一致性,如果系统时间回拨,或者改变,可能会造成生成重复id。
结构图如下:
-
1bit :该bit 不使用
因为二进制中,最高位是符号位,1标识负数,0 标识 正数,生成的id 都是使用 正数标识,所以 该位 不使用,固定为 0. -
41bit - 时间戳:
41bit - 时间戳 = (当前时间戳(nowTimeMillis ) - 开始时间戳(beginTimeMillis))
开始时间戳 :初始化的时候 是固定的,必须必nowTimeMillis 小。
41bit 位 最多可表示 (2^41 - 1 )个数字,
也就是 时间差为: (2^41 - 1 )个毫秒值,大概 69年多。 -
10bit : 包含 5 bit 数据中心 位 和 5 bit 机器位。
即:最多可部署 2^5 (32)个 机房,
每个机房 可以代表 2^5 (32)台机器。 -
12 bit:这个是用来记录同一个毫秒内产生的不同 id。
即:同一毫秒 内 最多可以生产 2^12 -1 = 4095 个不同id,超过该值,必须要等倒下一毫秒才能继续生产id。 -
最后,将 以上 四部分,通过位移 加上 或 运算 组拼成一个 64 bit 位 的数值 即可。
-
核心 算法
具体实现 如下
package com.nn.tools;
/**
* @ClassName Snow
* @Author nn
* @Date 2020/7/24 10:33
*/
public class MySnowFlake {
/**
* 开始时间
*/
private long beginTimeMillis = 1585644268888L;
/**
* 上一次 生产id 的时间
*/
private long lastTimeMillis = -1L;
/**
* 当前时间
*/
private long nowTimeMillis;
/**
* 机房id
*/
private long machineRoomId;
/**
* 机房 id 所占 位数(总 64位)
*/
private long machineRoomIdBits = 5L;
/**
* 最大 支持机房id值 即:2^5 -1 = 31 (0-31)
*/
private long maxMachineRoomId = -1L ^ (-1L << machineRoomIdBits);
/**
* 机器id
*/
private long machineId;
/**
* 机器id 所占 位数(总 64位)
*/
private long machineIdBits = 5L;
/**
* 最大 支持机器id值 即:2^5 -1 = 31 (0-31)
*/
private long maxMachineId = -1L ^ (-1L << machineRoomIdBits);
/**
* 一毫秒内 最多允许生成id 个数 所占位数
*/
private long sequenceBits = 12L;
/**
* 一毫秒内 最多允许生成id 个数
* (2^12 - 1) = 4095 , 一毫秒内 最多允许生产 4095 个id
* 超过该值时 进入等待,下一毫秒才可以继续生产
* -1L ---> 2进制为:1...(62位)...1 64位都为1
* -1L ^ (-1L << sequenceBits) = (2^12 - 1) = 4095
*/
private long MaxSequenceNumInOneMs = -1L ^ (-1L << sequenceBits);
/**
* 代表一毫秒内生成的最新序号
*/
private long sequenceNum;
/**
* 时间戳部分 需要左移 的位数;42
*/
private long timeLeftBits = machineRoomIdBits + machineIdBits + sequenceBits;
/**
* 机房部分 需要左移 的位数;17
*/
private long machineRoomLeftBits = machineIdBits + sequenceBits;
/**
* 机器部分 需要左移 的位数;12
*/
private long machineLeftBits = sequenceBits;
public MySnowFlake(long machineRoomId, long machineId) {
/**
* 判断 输入的 machineRoomId 是否在[0-31]范围内
*/
if (machineRoomId > maxMachineRoomId || machineRoomId < 0) {
throw new IllegalArgumentException(
String.format("worker Id can't be greater than %d or less than 0", machineRoomId));
}
/**
* 判断 输入的 machineId 是否在[0-31]范围内
*/
if (machineId > maxMachineId || machineId < 0) {
throw new IllegalArgumentException(
String.format("datacenter Id can't be greater than %d or less than 0", machineId));
}
this.machineRoomId = machineRoomId;
this.machineId = machineId;
}
public synchronized long produceNextId() {
/**
* 获取 当前 时间戳
*/
nowTimeMillis = getNowTimeStamp();
if (lastTimeMillis == nowTimeMillis) {
/**
* MaxSequenceNumInOneMs:sequenceNum 的 最大值 4095(2^12 - 1)其二进制为:0000 1111 1111 1111
* 当 sequenceNum = 4095 时(0000 1111 1111 1111) 加 1 得到-->2^12 (0001 0000 0000 0000)
* 0001 0000 0000 0000 & 0000 1111 1111 1111 = 0
*
* 即,当 sequenceNum = 0 的时候 就不能继续生产id,需要等待下一毫秒才可以继续生产id,即进入等待。
*/
sequenceNum = (sequenceNum + 1) & MaxSequenceNumInOneMs;
/**
* 当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
*/
if (sequenceNum == 0) {
nowTimeMillis = getNowTimeStamp();
while (nowTimeMillis == lastTimeMillis) {
nowTimeMillis = getNowTimeStamp();
}
}
} else {
sequenceNum = 0;
}
lastTimeMillis = nowTimeMillis;
/**
* id = 时间戳部分(41位) | 机房id(5位) | 机器id(5位) | 1 毫秒内 id***(12位) = 64 二进制位 值
*/
long a = ((nowTimeMillis - beginTimeMillis) << timeLeftBits) |
(machineRoomId << machineRoomLeftBits) |
(machineId << machineLeftBits) |
sequenceNum;
return a;
}
/**
* 获取当前时间戳
*
* @return TimeMillis long
*/
private long getNowTimeStamp() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
MySnowFlake snow = new MySnowFlake(1, 1);
for (int i = 0; i < 10000; i++) {
long l = snow.produceNextId();
System.out.println(l);
}
}
}
上一篇: JS实现在文本指定位置插入内容的简单示例