一、概述
1、SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图:
1位,不用。二进制中最高位为1的都是负数,但是我们生成的id一般都使用整数,所以这个最高位固定是0
● 41位,用来记录时间戳(毫秒)。
○ 41位可以表示
2
41
−
1
2^{41}-1
241−1个数字,
○ 如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至
2
41
−
1
2^{41}-1
241−1,减1是因为可表示的数值范围是从0开始算的,而不是1。
○ 也就是说41位可以表示
2
41
−
1
2^{41}-1
241−1个毫秒的值,转化成单位年则是
(
2
41
−
1
)
/
(
1000
∗
60
∗
60
∗
24
∗
365
)
=
69
(2^{41}-1) / (1000 * 60 * 60 * 24 * 365) = 69
(241−1)/(1000∗60∗60∗24∗365)=69年
● 10位,用来记录工作机器id。
○ 可以部署在
2
10
=
1024
2^{10} = 1024
210=1024个节点,包括 5位datacenterId 和 5位workerId
○ 5位(bit)可以表示的最大正整数是
2
5
−
1
=
31
2^{5}-1 = 31
25−1=31,即可以用0、1、2、3、…31这32个数字,来表示不同的datecenterId或workerId
● 12位,序列号,用来记录同毫秒内产生的不同id。
○ 12位(bit)可以表示的最大正整数是
2
12
−
1
=
4095
2^{12}-1 = 4095
212−1=4095,即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号
由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的。
SnowFlake可以保证:
● 所有生成的id按时间趋势递增
● 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)
测试:
用 setnx 往 redis 里插 100万条 看是否有重复的 后面又往数据库插 100万,id为主键
package com.teaching.common.core.utils;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
/**
* @author Monster
* @since 2022/4/1 9:47
*/
public class XHID {
// 开始时间截
private final static long twepoch = 12888349746579L;
// 机器标识位数
private final static long workerIdBits = 5L;
// 数据中心标识位数
private final static long datacenterIdBits = 5L;
// 毫秒内自增位数
private final static long sequenceBits = 12L;
// 机器ID偏左移12位
private final static long workerIdShift = sequenceBits;
// 数据中心ID左移17位
private final static long datacenterIdShift = sequenceBits + workerIdBits;
// 时间毫秒左移22位
private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
//sequence掩码,确保sequnce不会超出上限
private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
//上次时间戳
private static long lastTimestamp = -1L;
//序列
private long sequence = 0L;
//服务器ID
private long workerId = 1L;
private static long workerMask = -1L ^ (-1L << workerIdBits);
//进程编码
private long processId = 1L;
private static long processMask = -1L ^ (-1L << datacenterIdBits);
private static XHID xhid = null;
static{
xhid = new XHID();
}
public static synchronized long nextId(){
return xhid.getNextId();
}
private XHID() {
//获取机器编码
this.workerId=this.getMachineNum();
//获取进程编码
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
this.processId=Long.valueOf(runtimeMXBean.getName().split("@")[0]).longValue();
//避免编码超出最大值
this.workerId=workerId & workerMask;
this.processId=processId & processMask;
}
public synchronized long getNextId() {
//获取时间戳
long timestamp = timeGen();
//如果时间戳小于上次时间戳则报错
if (timestamp < lastTimestamp) {
try {
throw new Exception("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
} catch (Exception e) {
e.printStackTrace();
}
}
//如果时间戳与上次时间戳相同
if (lastTimestamp == timestamp) {
// 当前毫秒内,则+1,与sequenceMask确保sequence不会超出上限
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒内计数满了,则等待下一秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
lastTimestamp = timestamp;
// ID偏移组合生成最终的ID,并返回ID
long nextId = ((timestamp - twepoch) << timestampLeftShift) | (processId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
return nextId;
}
/**
* 再次获取时间戳直到获取的时间戳与现有的不同
* @param lastTimestamp
* @return 下一个时间戳
*/
private long tilNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
/**
* 获取机器编码
* @return
*/
private long getMachineNum(){
long machinePiece;
StringBuilder sb = new StringBuilder();
Enumeration<NetworkInterface> e = null;
try {
e = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e1) {
e1.printStackTrace();
}
while (e.hasMoreElements()) {
NetworkInterface ni = e.nextElement();
sb.append(ni.toString());
}
machinePiece = sb.toString().hashCode();
return machinePiece;
}
public static void main(String[] args) {
for(int i=0;i<1000000;i++){
Long userId = XHID.nextId();
// System.out.println(userId);
String id = userId.toString();
int n = i;
if(id.length()!=19){
System.out.println(id);
}
if(n==999999){
System.out.println("999999: "+id);
}
try {
//用 setnx 往 redis 里插 100万条 看是否有重复的
boolean serchRedis = RedisUtil.setnx(userId.toString(),userId.toString());
} catch (Exception e) {
e.printStackTrace();
System.out.println(userId);
}
}
}
}