雪花算法
用一个64比特位的long类型来作为生成id的类型,首先我们要了解哪些位置对应的意义,其中在本项目中10位的工作机器id被细分位5bit的机房id与5bit的机器id。雪花算法支持每毫秒生成2的12次方-1个id。
用一个64比特位的long类型来作为生成id的类型,首先我们要了解哪些位置对应的意义,其中在本项目中10位的工作机器id被细分位5bit的机房id与5bit的机器id。雪花算法支持每毫秒生成2的12次方-1个id。
视频
External Player - 哔哩哔哩嵌入式外链播放器
实现思路
在实现时首先需要准备一些必须的字段,我们需要记录一个初始的时间戳这个时间戳不能修改,最好使用部署时间或者开发当前时间,其次我们要记录时间戳、机房id、机器id、序列化分别占多少位,然后我们还有记录机器码与机房码等
在实现时,首先我们要获取当前的时间戳,如果当前时间戳比上一次获取时,如果比上一次小说明这是一次非法调用抛出异常,如果等于上一次的时间戳说明这是同一毫秒,我们需要让序列化+1,但 同时我们需要判断序列化+1以后是否到达该毫秒内能生成的最大id如果等于了则获取下一毫秒的时间。如果当前时间戳大于上一次调用时的时间戳,说明是另外的一毫秒于是序列化重置为0,最好根据上述图中规则进行组装后返回
代码实现
package com.cloud.cloud_oj_common.utils;
/**
* Created with IntelliJ IDEA.
* Description:
* User: PG
* Date: 2024-04-17
* Time: 19:11
*/
/**
* 雪花算法
*/
public class SnowFlake {
private static final long START_TIME = 1609459200000L; // 设置起始时间戳
private static final long SEQUENCE_BIT = 12L; // 设置序列号所占比特位
private static final long MACHINE_BIT = 5L; // 设置机器码所占比特位
private static final long DATACENTER_BIT = 5L;// 设置机房码所占比特位
private static final long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT); // 机器码最大值
private static final long MAX_DATACENTER_NUM = ~(-1L << DATACENTER_BIT); // 机房码最大值
private static final long MAX_SEQUENCE_NUM = ~(-1L << SEQUENCE_BIT); // 序列号最大值,1毫秒能生成的最多id数
private static long machineId; // 机器码
private static long datacenterId; // 机房码
private static long sequence = 0L; // 序列号
private static long lastTimestamp = -1L; // 上一次时间戳
/**
* 配置机器码与机房码
* @param machine 机器码
* @param datacenter 机房码
*/
public static void setConfig(long machine, long datacenter) {
if (machine >= MAX_MACHINE_NUM || datacenter >= MAX_DATACENTER_NUM) {
throw new RuntimeException("机器码异常");
}
machineId = machine;
datacenterId = datacenter;
}
/**
* 获取下一个id
* @return
*/
public static synchronized long generateId() {
long timestamp = System.currentTimeMillis(); // 获取当前时间戳
if (timestamp < lastTimestamp) { // 如果比上一次小则说明是异常
throw new RuntimeException("时间戳错误");
}
if (timestamp == lastTimestamp) { // 如果与上一次相同,则说明是需要同一毫秒生成的
sequence = (sequence + 1) & MAX_SEQUENCE_NUM; // 让序列化加1以后与序列化的最大值,如果是最大值那么结果就是0,如果不是最大值结果就是原序列号+1
if (sequence == 0) { // 说明当前毫秒内id生成以及达到上限
timestamp = tilNextMillis(lastTimestamp); // 获取下一毫秒即可
}
} else { // 如果当前时间戳大于上一次时间戳说明是新的1毫秒,序列化置为0即可
sequence = 0L;
}
lastTimestamp = timestamp; // 修改记录上一次调用的时间戳
return (timestamp - START_TIME) << (SEQUENCE_BIT + MACHINE_BIT + DATACENTER_BIT) // 将时间戳左移到合适位置
| datacenterId << (SEQUENCE_BIT + MACHINE_BIT) // 将机房码左移合适的位置
| machineId << (SEQUENCE_BIT)
| sequence;
}
/**
* 获取下一毫秒的时间戳
* @param lastTimestamp 上一毫秒
* @return 下一毫秒
*/
private static long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}