基于Snowflake算法的全局唯一ID

741 阅读3分钟

什么是Snowflake算法

雪花算法(Snowflake Algorithm)是一种用于生成唯一标识符(ID)的算法,特别在分布式系统中被广泛使用。它由Twitter公司开发,旨在解决分布式环境下生成全局唯一ID的需求。

Snowflake算法的原理

雪花算法的ID是一个64位整数,由以下几个部分组成:

  1. 时间戳(Timestamp):占用41位,精确到毫秒级。时间戳部分表示生成ID的时间,可以支持一定的时间回拨。
  2. 工作机器ID(Worker ID):占用10位。工作机器ID标识了生成ID的机器,每台机器需要分配一个唯一的工作机器ID。
  3. 序列号(Sequence):占用12位。序列号部分在同一毫秒内生成的ID自增,以防止在同一毫秒内生成的ID发生冲突。

使用雪花算法生成唯一ID的过程如下:

  1. 首先,记录一个起始时间戳(比如某个固定的时间点)。
  2. 每次生成ID时,获取当前的时间戳,并计算与起始时间戳之间的时间差,将时间差转换为毫秒级。
  3. 如果在同一毫秒内生成多个ID,需要通过序列号部分来区分它们。序列号部分每次生成ID时自增。
  4. 将时间戳、工作机器ID和序列号按位组合,形成一个64位的ID。

雪花算法生成的ID具有以下特点:

  • 全局唯一性:在分布式环境下生成的ID基本上是全局唯一的,由时间戳、工作机器ID和序列号组合而成。
  • 有序递增:生成的ID在时间上是递增的,可以基于时间进行排序。
  • 高性能:雪花算法生成ID的过程简单高效,不依赖于网络和外部存储。

需要注意的是,雪花算法需要保证每台机器的工作机器ID唯一,并且系统的时钟要保持同步,以避免时间回拨导致生成的ID出现重复或无序。

雪花算法可以根据具体的需求进行调整和扩展,例如调整位数分配、添加数据中心ID等,以满足不同的分布式系统需求。

Ruby代码实现

以下是使用Ruby实现雪花算法生成唯一ID的示例代码:

class Snowflake
  attr_reader :start_time, :worker_id_bits, :sequence_bits

  def initialize(worker_id, worker_id_bits = 5, sequence_bits = 12)
    @start_time = Time.new(2020, 1, 1).to_i * 1000  # 设置起始时间戳,这里以202011日为例,单位为毫秒
    @worker_id = worker_id  # 工作机器ID
    @worker_id_bits = worker_id_bits  # 工作机器ID的位数
    @sequence_bits = sequence_bits  # 序列号的位数
    @max_worker_id = (1 << worker_id_bits) - 1  # 最大工作机器ID
    @max_sequence = (1 << sequence_bits) - 1  # 最大序列号
    @last_timestamp = 0  # 上次生成ID的时间戳
    @sequence = 0  # 序列号
  end

  def generate_id
    timestamp = current_timestamp

    if timestamp < @last_timestamp
      raise "Clock moved backwards. Refusing to generate ID for #{last_timestamp - timestamp} milliseconds."
    end

    if timestamp == @last_timestamp
      @sequence = (@sequence + 1) & @max_sequence
      if @sequence.zero?
        timestamp = til_next_millis(@last_timestamp)
      end
    else
      @sequence = 0
    end

    @last_timestamp = timestamp

    ((timestamp - start_time) << (@worker_id_bits + @sequence_bits)) |
      (@worker_id << @sequence_bits) |
      @sequence
  end

  private

  def current_timestamp
    (Time.now.to_f * 1000).to_i  # 获取当前时间戳,单位为毫秒
  end

  def til_next_millis(last_timestamp)
    timestamp = current_timestamp
    while timestamp <= last_timestamp
      timestamp = current_timestamp
    end
    timestamp
  end
end

# 示例用法
snowflake = Snowflake.new(1)  # 使用worker_id1,可以根据需要设置不同的worker_id
id = snowflake.generate_id
puts id

雪花算法和直接使用UUID对比

雪花算法和直接使用UUID(Universally Unique Identifier)生成唯一ID之间存在一些区别和优缺点。

  1. 区别:

    • 雪花算法是一种自增的算法,生成的ID是按照时间有序递增的。它包含了时间戳、机器ID和序列号等信息,具有较高的可读性和可排序性。
    • UUID是一种标准化的唯一标识符,生成的ID是随机的,没有明确的顺序或可读性。UUID采用128位的格式,通常表示为32个十六进制数字的字符串,可以通过各种算法生成。
  2. 优点:

    • 雪花算法的优点在于生成的ID是有序的,可以根据时间戳进行排序,适用于需要按照时间顺序存储和查询的场景。
    • UUID的优点在于生成的ID是全局唯一的,几乎可以保证在全球范围内不会重复,适用于分布式系统和多节点环境中的唯一标识需求。
  3. 缺点:

    • 雪花算法的缺点之一是依赖于系统时钟的正确性和唯一性,如果系统时钟发生回拨或存在时钟偏差,可能会导致生成的ID不唯一或无序。
    • UUID的缺点在于生成的ID较长,占用的存储空间相对较大,并且没有明确的顺序性,不适合作为数据库索引或排序字段。

综上所述,选择雪花算法还是UUID生成唯一ID取决于具体的需求。如果需要有序的ID或按时间顺序进行排序,可以选择雪花算法;如果需要全局唯一的ID,并且不需要排序或索引的功能,可以选择UUID。

基于redis自增序列的全局唯一id

# 在 64 位系统上,Ruby 中的整数使用的是 64 位无符号整数类型,范围是从 0 到 2^64-1。在 32 位系统上,则使用 32 位或 64 位有符号整数类型,其范围分别是从 -2^31 到 2^31-1 或者从 -2^63 到 2^63-1。

class Util::RedisId

  # 时间戳 31位,以秒为单位,可以使用69年
  # 每秒提供2的32次方个id

  # 开始时间戳

  # Time.parse("2020-10-10").to_i  => 1602259200
  BEGIN_TIMESTAMP = 1602259200
  # 序列号位数
  COUNT_BITS = 32

  def self.generate_unique_id(keyPrefix ,step = 1)
    # 当前时间戳
    nowSecond = Time.now.to_i
    #时间戳差值
    timeStamp = nowSecond - BEGIN_TIMESTAMP
    #序列号部分
    dateStr = Time.now.strftime("%Y%m%d")
    # "app:order:20230518"
    _key = "app:" + keyPrefix + ":" + dateStr
    number = Util::Redis.cache_redis.incrby( _key,step)
    # 时间戳左移 << 32位,空出来给number,空位都是0与number 或 |运算
    timeStamp << COUNT_BITS | number
  end
end
50.times {|x| puts Util::RedisId.generate_unique_id('order',1)}