引言
在现代分布式系统中,生成唯一的标识符(ID)至关重要。雪花算法作为一种流行的 ID 生成技术,广泛应用于大规模的数据库和微服务架构中。本文将介绍如何通过雪花算法安全地生成唯一 ID,并在高并发环境中避免重复 ID 的问题。
雪花算法简介
雪花算法生成的 ID 结构如下:
+---------------------+-----------+-------------------+
| Timestamp (41 bits) | Machine ID (10 bits) | Sequence (12 bits) |
+---------------------+-----------+-------------------+
时间戳:41 位,表示从自定义起始时间到当前时间的毫秒数,支持约 69 年的时长。
机器 ID:10 位,表示工作节点的标识,最多支持 1024 个不同的节点。
序列号:12 位,同一毫秒内生成的 ID 数量最大为 4096。
在高并发场景中,如果多个请求在同一毫秒内生成 ID,由于序列号的增量管理无法同步,可能会导致 ID 重复。这会严重影响系统的可靠性,特别是在需要唯一标识的场合。
为了确保生成的 ID 唯一性,建议在数据库中创建一个状态表,来持久化存储 last_timestamp
和 sequence
,以下是实现步骤:
1. 创建状态表
首先,在数据库中创建一个表来存储 ID 生成过程中的状态信息:
CREATE TABLE snowflake_state (
id INT PRIMARY KEY,
last_timestamp BIGINT DEFAULT 0,
sequence INT DEFAULT 0
);
-- 初始化表格
INSERT INTO snowflake_state (id) VALUES (1);
2. 修改 ID 生成函数
接下来,修改生成雪花 ID 的函数,以便从状态表中读取并更新状态:
DELIMITER $$
CREATE FUNCTION generate_snowflake_id(machine_id INT)
RETURNS BIGINT
DETERMINISTIC
BEGIN
DECLARE epoch BIGINT DEFAULT 1577836800000; -- 自定义的起始时间戳(1970-01-01 00:00:00)
DECLARE current_time_ms BIGINT;
DECLARE current_sequence INT;
DECLARE last_time BIGINT;
DECLARE snowflake_id BIGINT;
-- 获取当前时间戳(毫秒)
SET current_time_ms = UNIX_TIMESTAMP(NOW(3)) * 1000;
-- 从状态表中获取最新的 last_timestamp 和 sequence
SELECT last_timestamp, sequence INTO last_time, current_sequence FROM snowflake_state WHERE id = 1;
-- 如果当前时间戳和上一次生成 ID 的时间相同,则增加序列号
IF current_time_ms = last_time THEN
SET current_sequence = (current_sequence + 1) % 4096; -- 4096 是 12 位序列数的最大值
ELSE
SET current_sequence = 0; -- 重置序列号
END IF;
SET snowflake_id = ((current_time_ms - epoch) << 22) | (machine_id << 12) | current_sequence;
-- 更新状态表中的 last_timestamp 和 sequence
UPDATE snowflake_state SET last_timestamp = current_time_ms, sequence = current_sequence WHERE id = 1;
RETURN snowflake_id;
END$$
DELIMITER ;
3. 函数调用
使用函数生成唯一的雪花 ID 如下:
SELECT generate_snowflake_id(1) FROM table; -- 使用 'table' 表,来测试一个内容
虽然上述方法能有效避免重复 ID,但在高并发时,读取和更新数据库表可能会引入性能瓶颈。可以通过加锁机制优化并发访问,确保在同一时刻仅有一个进程在更新状态。
评论区