雪花算法
什么是雪花算法
雪花算法(Snowflake Algorithm)是一种用于生成全局唯一标识符(Globally Unique Identifiers,简称GUID)的算法。它最初由Twitter开发,用于在分布式系统中生成唯一的ID。雪花算法的设计目标是在大规模分布式系统中高效生成ID,并保证生成的ID是递增有序的。
雪花算法生成的ID是64位二进制的整数,可以分解成以下几个部分:
1.时间戳(Timestamp):占用42位,记录生成ID的时间戳,精确到毫秒级别。由于使用了42位,所以雪花算法可以支持约69年的时间戳。
2.节点ID(Worker ID):占用10位,用于标识不同的节点或机器。在分布式系统中,每个节点或机器需要有唯一的节点ID。
3.序列号(Sequence Number):占用12位,表示在同一毫秒内生成的ID的序列号。如果同一毫秒内生成的ID超过了4096个(2的12次方),则会等待下一毫秒再生成。
通过将时间戳、节点ID和序列号组合在一起,雪花算法可以生成全局唯一的ID,且在一定程度上保持了递增有序性,这对于分布式系统的排序和索引非常有用。
需要注意的是,雪花算法对节点ID的分配和生成ID的调用需要进行合理的管理和控制,以确保生成的ID是唯一且正确的。
如何实现雪花算法呢
要实现雪花算法,可以按照以下步骤进行:
定义相关参数:
时间戳位数:41位(+ 1 个前零位)
节点ID位数:10位
序列号位数:12位
初始化相关变量:
上次生成ID的时间戳 lastTimestamp 初始化为 0
序列号 sequence 初始化为 0
实现生成ID的函数 generateID():
获取当前时间戳 currentTimestamp
如果当前时间戳小于上次生成ID的时间戳 lastTimestamp,说明时钟回拨(clock drift)发生,需要等待时钟追赶到上次时间戳,避免生成重复的ID。在这种情况下,可以选择等待一段时间或抛出异常,具体处理方式根据需求而定。
如果当前时间戳等于上次生成ID的时间戳 lastTimestamp,表示在同一毫秒内生成ID,需要增加序列号 sequence。
判断序列号 sequence 是否已达到最大值(2的12次方减1),如果是,则等待下一毫秒再生成ID,将序列号 sequence 重置为0。
如果序列号 sequence 未达到最大值,递增序列号 sequence。
如果当前时间戳大于上次生成ID的时间戳 lastTimestamp,表示进入下一毫秒,将序列号 sequence 重置为0。
更新上次生成ID的时间戳 lastTimestamp 为当前时间戳 currentTimestamp。
将各部分的值进行位移和逻辑运算,组合成64位的ID:
时间戳部分左移 22 位(64 - 42):
timestampShifted = currentTimestamp << 22
节点ID部分左移 12 位(64 - 42 - 10):
workerIdShifted = workerId << 12
组合时间戳、节点ID和序列号:
snowflakeId = timestampShifted | workerIdShifted | sequence返回生成的唯一ID snowflakeId。
需要注意的是,在实际应用中,节点ID可以根据具体情况分配,可以使用机器的MAC地址、IP地址或其他唯一标识符来标识不同的节点。另外,时钟回拨问题需要根据具体的编程语言和环境来处理,确保生成的ID是唯一且正确的。
// 雪花算法实现
class Snowflake {
constructor(workerId) {
this.workerIdBits = 10;
this.sequenceBits = 12;
this.maxWorkerId = -1 ^ (-1 << this.workerIdBits);
this.sequenceMask = -1 ^ (-1 << this.sequenceBits);
this.workerId = workerId;
this.lastTimestamp = -1;
this.sequence = 0;
}
generateID() {
let timestamp = this.currentTimestamp();
if (timestamp < this.lastTimestamp) {
throw new Error('Clock moved backwards. Refusing to generate ID.');
}
if (timestamp === this.lastTimestamp) {
this.sequence = (this.sequence + 1) & this.sequenceMask;
if (this.sequence === 0) {
timestamp = this.nextMillis(this.lastTimestamp);
}
} else {
this.sequence = 0;
}
this.lastTimestamp = timestamp;
const id =
(BigInt(timestamp) << BigInt(this.workerIdBits + this.sequenceBits)) |
(BigInt(this.workerId) << BigInt(this.sequenceBits)) |
BigInt(this.sequence);
return id.toString();
}
currentTimestamp() {
return Date.now();
}
nextMillis(lastTimestamp) {
let timestamp = this.currentTimestamp();
while (timestamp <= lastTimestamp) {
timestamp = this.currentTimestamp();
}
return timestamp;
}
}
// 使用示例
const workerId = 1; // 假设节点ID为1
const snowflake = new Snowflake(workerId);
// 生成ID
const id = snowflake.generateID();
console.log(id);
在上述代码中,我们定义了一个 Snowflake 类,构造函数中传入节点ID workerId。然后,通过调用 generateID 方法即可生成唯一的ID。注意,这里使用 BigInt 类型来处理大整数,确保在 JavaScript 中能够正确处理64位的ID。
请注意,上述代码仅为示例,实际使用时,你可能需要根据具体情况进行适当调整和优化。
雪花算法的优缺点!
- 生成单调自增的唯一ID,在innodb的b+数表中顺序插入不会造成页的分裂,性能高。(uuid的话每个id是随机的,大量的随机IO效率不但低,还会使innodb页造成分裂和合并,使得插入效率低)
- 生成64位id,只占用8个字节节省存储空间。
缺点: - 每台数据库的本地时间都要设置相同,否则会导致全局不递增
- 如果时钟回拨,会产生重复id。
时钟回拨的解决方案
存储每一毫秒产生的最后一个id的序列值,回拨到当前毫秒从序列值+1开始继续产生id即可!