手撸一个简单的雪花算法生成自增id
简介
雪花算法(Snowflake)是为了解决分布式系统中生成全局唯一ID的需求而设计的。在分布式系统中,如果多个节点同时生成ID,传统的自增ID或随机数ID可能会导致ID冲突,因此需要一种算法来确保在不同节点、不同时间生成的ID都是唯一的。
雪花算法通过将ID分为不同的部分,包括时间戳、数据中心ID、工作节点ID和序列号,来保证生成的ID的唯一性。每个部分的位数可以根据实际需求进行调整,以平衡ID的长度和唯一性要求。
雪花算法的应用场景包括但不限于以下情况:
- 分布式系统中需要生成全局唯一ID的场景,如分布式数据库、分布式缓存、消息队列等。
- 分布式系统中需要对大量数据进行唯一标识的场景,如日志分析系统、用户行为跟踪系统等。
- 分布式系统中需要对事件进行排序或时间戳的场景,如分布式事务、分布式锁等。
通过使用雪花算法,可以在分布式系统中生成全局唯一的ID,避免了ID冲突的问题,并且ID的生成是有序的,可以根据时间戳进行排序。这使得雪花算法成为了一种常用的分布式ID生成方案。
具体代码实现
public class SnowflakeIdGenerator {
private static final long EPOCH = 1609459200000L; // 起始时间戳,这里设置为 2021-01-01 00:00:00 的时间戳
private static final long WORKER_ID_BITS = 5L; // 工作节点ID所占的位数
private static final long DATA_CENTER_ID_BITS = 5L; // 数据中心ID所占的位数
private static final long SEQUENCE_BITS = 12L; // 序列号所占的位数
private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); // 工作节点ID的最大值
private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS); // 数据中心ID的最大值
private static final long WORKER_ID_SHIFT = SEQUENCE_BITS; // 工作节点ID的位移量
private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; // 数据中心ID的位移量
private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS; // 时间戳的位移量
private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS); // 序列号的掩码,用于获取序列号部分的值
private final long workerId; // 工作节点ID
private final long dataCenterId; // 数据中心ID
private long sequence = 0L; // 序列号
private long lastTimestamp = -1L; // 上一次生成ID的时间戳
/**
* 构造函数
*
* @param workerId 工作节点ID
* @param dataCenterId 数据中心ID
*/
public SnowflakeIdGenerator(long workerId, long dataCenterId) {
if (workerId < 0 || workerId > MAX_WORKER_ID) {
throw new IllegalArgumentException("Worker ID must be between 0 and " + MAX_WORKER_ID);
}
if (dataCenterId < 0 || dataCenterId > MAX_DATA_CENTER_ID) {
throw new IllegalArgumentException("Data Center ID must be between 0 and " + MAX_DATA_CENTER_ID);
}
this.workerId = workerId;
this.dataCenterId = dataCenterId;
}
/**
* 生成唯一ID
*
* @return 唯一ID
*/
public synchronized long generateId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Invalid system clock");
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & SEQUENCE_MASK;
if (sequence == 0) {
timestamp = nextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - EPOCH) << TIMESTAMP_LEFT_SHIFT) |
(dataCenterId << DATA_CENTER_ID_SHIFT) |
(workerId << WORKER_ID_SHIFT) |
sequence;
}
/**
* 获取下一个毫秒的时间戳
*
* @param lastTimestamp 上一次生成ID的时间戳
* @return 下一个毫秒的时间戳
*/
private long nextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
上述代码中,SnowflakeIdGenerator类实现了雪花算法生成唯一ID的逻辑。以下是实现的详细细节和思路:
定义了一些常量,包括时间戳的起始时间(即雪花算法的epoch),以及各个部分的位数和位移。
构造函数接收workerId和dataCenterId作为参数,并进行参数校验。
generateId方法是生成唯一ID的核心逻辑。首先获取当前时间戳,如果时间戳小于上一次生成ID的时间戳,则抛出异常,因为时间戳应该是单调递增的。如果时间戳与上一次相同,则递增序列号(sequence),并检查序列号是否溢出。如果序列号溢出,则等待下一毫秒再生成ID。如果时间戳不同,则重置序列号为0。
生成ID的公式为:((timestamp - EPOCH) << TIMESTAMP_LEFT_SHIFT) | (dataCenterId << DATA_CENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence。其中,时间戳部分左移相应的位数,然后与数据中心ID、工作节点ID和序列号进行按位或操作,得到最终的唯一ID。
nextMillis方法用于获取下一个毫秒的时间戳,如果下一个时间戳小于等于上一次的时间戳,则一直循环等待,直到获取到合适的时间戳。
这样,通过使用SnowflakeIdGenerator类的实例,你可以在分布式系统中生成全局唯一的ID。每个实例需要指定不同的workerId和dataCenterId,以确保生成的ID在整个系统中唯一。
雪花id存在的问题
时钟回溯是指系统时钟发生了向后调整(回溯)的情况,即当前时间戳比上一次生成ID的时间戳要小。时钟回溯可能会导致雪花算法生成的ID出现重复或无序的情况,因为雪花算法依赖于时间戳的递增来保证ID的唯一性和有序性。
时钟回溯可能发生在以下情况下:
- 手动调整系统时钟:管理员或系统维护人员手动调整了系统时钟,将时间设置为过去的某个时间点。
时钟同步问题:在分布式系统中,不同节点的时钟可能存在微小的差异。当一个节点的时钟比其他节点的时钟快了一段时间时,如果发生时钟同步操作,可能会导致该节点的时钟回溯。 - 时钟回溯对雪花算法的影响取决于具体的实现方式。一般情况下,雪花算法会检测到时钟回溯并抛出异常,以避免生成重复的ID。常见的处理方式包括抛出异常、等待时钟追上、记录异常等。
处理时钟回溯的方法有以下几种:
- 抛出异常:当检测到时钟回溯时,可以抛出异常,让应用程序捕获并处理该异常。这样可以防止生成重复的ID,但需要应用程序处理异常情况。
- 等待时钟追上:当检测到时钟回溯时,可以等待时钟追上当前时间,然后再生成ID。这样可以保证ID的有序性,但会造成一定的延迟。
- 记录异常:当检测到时钟回溯时,可以记录异常情况,以便后续分析和处理。这种方式可以在一定程度上保证ID的唯一性和有序性,但需要额外的处理逻辑。
- 处理时钟回溯是使用雪花算法时需要考虑的重要问题之一。根据具体的应用场景和需求,可以选择合适的处理方式来处理时钟回溯带来的影响。
更多推荐

所有评论(0)