雪花算法ID生成器:IdAssignUtil详解
package com.csot.kms.common.utill;
import lombok.extern.slf4j.Slf4j;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
/**
* @Author dj
* @Date 2021/12/1
**/
@Slf4j
public class IdAssignUtil {
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 static long workerMask = -1L ^ (-1L << workerIdBits);
private static long processMask = -1L ^ (-1L << datacenterIdBits);
private static IdAssignUtil snowFlake = null;
static {
snowFlake = new IdAssignUtil();
}
//序列
private long sequence = 0L;
//服务器ID
private long workerId = 1L;
//进程编码
private long processId = 1L;
private IdAssignUtil() {
//获取机器编码
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 static synchronized long nextId() {
return snowFlake.getNextId();
}
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;
}
}
在分布式系统中,生成唯一的ID是一个常见的需求。为了解决这一问题,Twitter开发了雪花算法(Snowflake Algorithm),它可以在不依赖数据库的情况下生成唯一的ID。本文将介绍一个基于雪花算法的Java工具类IdAssignUtil,它可以帮助我们在分布式系统中生成唯一的ID。
概述
IdAssignUtil是一个使用雪花算法实现的ID生成器。它通过组合时间戳、机器ID和序列号来生成唯一的ID。这种算法的优点是性能高、依赖少,并且可以保证在分布式系统中生成不重复的ID。
核心概念
时间戳:使用当前时间戳来保证ID的时序性。
机器ID:标识ID生成器所在的机器。
序列号:在同一毫秒内,如果有多个ID生成请求,序列号用于区分它们。
类设计
IdAssignUtil类中定义了一些常量和变量来支持算法的实现:
twepoch:时间戳的起始点,用于减少ID的存储空间。
workerIdBits、datacenterIdBits、sequenceBits:分别表示机器ID、数据中心ID和序列号的位数。
workerIdShift、datacenterIdShift、timestampLeftShift:用于在64位ID中定位各个部分的位置。
sequenceMask、workerMask、processMask:用于确保各个部分的值不会超出预设的位数。
lastTimestamp:上一次生成ID的时间戳。
sequence:同一毫秒内的序列号。
workerId、processId:机器ID和进程ID。
方法详解
构造函数
IdAssignUtil的构造函数是私有的,确保不能直接实例化。它在静态初始化块中创建了一个单例对象。
getMachineNum()
这是一个私有方法,用于获取机器的标识码。它通过遍历网络接口来生成一个基于机器MAC地址的字符串,然后将这个字符串转换为哈希值作为机器ID。
timeGen()
这是一个私有方法,用于生成当前的时间戳。
tilNextMillis()
这是一个私有方法,用于等待直到获取到下一个不同的时间戳。
getNextId()
这是一个公共方法,用于生成下一个ID。它首先获取当前的时间戳,然后检查是否与上一次的时间戳相同。如果相同,它会尝试增加序列号。如果序列号也达到了上限,它会等待直到下一个毫秒。
使用场景
IdAssignUtil适用于需要生成唯一ID的分布式系统,如订单ID、用户ID、会话ID等。它可以确保在高并发环境下生成不重复的ID。
结论
IdAssignUtil是一个基于雪花算法的高效ID生成器。它通过合理的设计和实现,确保了ID的唯一性和时序性。使用这个工具类,我们可以在分布式系统中轻松地生成唯一的ID,从而提高系统的可扩展性和可维护性。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)