什么是Snowflake算法
雪花算法(Snowflake Algorithm)是一种用于生成唯一标识符(ID)的算法,特别在分布式系统中被广泛使用。它由Twitter公司开发,旨在解决分布式环境下生成全局唯一ID的需求。
Snowflake算法的原理
雪花算法的ID是一个64位整数,由以下几个部分组成:
- 时间戳(Timestamp):占用41位,精确到毫秒级。时间戳部分表示生成ID的时间,可以支持一定的时间回拨。
- 工作机器ID(Worker ID):占用10位。工作机器ID标识了生成ID的机器,每台机器需要分配一个唯一的工作机器ID。
- 序列号(Sequence):占用12位。序列号部分在同一毫秒内生成的ID自增,以防止在同一毫秒内生成的ID发生冲突。
使用雪花算法生成唯一ID的过程如下:
- 首先,记录一个起始时间戳(比如某个固定的时间点)。
- 每次生成ID时,获取当前的时间戳,并计算与起始时间戳之间的时间差,将时间差转换为毫秒级。
- 如果在同一毫秒内生成多个ID,需要通过序列号部分来区分它们。序列号部分每次生成ID时自增。
- 将时间戳、工作机器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 # 设置起始时间戳,这里以2020年1月1日为例,单位为毫秒
@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_id为1,可以根据需要设置不同的worker_id
id = snowflake.generate_id
puts id
雪花算法和直接使用UUID对比
雪花算法和直接使用UUID(Universally Unique Identifier)生成唯一ID之间存在一些区别和优缺点。
-
区别:
- 雪花算法是一种自增的算法,生成的ID是按照时间有序递增的。它包含了时间戳、机器ID和序列号等信息,具有较高的可读性和可排序性。
- UUID是一种标准化的唯一标识符,生成的ID是随机的,没有明确的顺序或可读性。UUID采用128位的格式,通常表示为32个十六进制数字的字符串,可以通过各种算法生成。
-
优点:
- 雪花算法的优点在于生成的ID是有序的,可以根据时间戳进行排序,适用于需要按照时间顺序存储和查询的场景。
- UUID的优点在于生成的ID是全局唯一的,几乎可以保证在全球范围内不会重复,适用于分布式系统和多节点环境中的唯一标识需求。
-
缺点:
- 雪花算法的缺点之一是依赖于系统时钟的正确性和唯一性,如果系统时钟发生回拨或存在时钟偏差,可能会导致生成的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)}