什么是雪花算法

455 阅读4分钟

雪花算法

什么是雪花算法

雪花算法(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即可!