话接上文《Sonyflake 分布式 ID 生成方案》,Machine ID 需要借助数据库来实现自动分配。
需求:
- 自动分配 Machine ID
- 最大限度保证 IP 和端口不变时复用原 Machine ID
1. 数据结构
CREATE TABLE system_nodes (
service VARCHAR(64) NOT NULL,
machine_id SMALLINT NOT NULL,
host INET NOT NULL,
port INTEGER NOT NULL,
version INTEGER NOT NULL, -- 防止并发修改,更重要的是让旧节点发现所有权已经变化
last_seen TIMESTAMPTZ NOT NULL DEFAULT now(), -- 判断是否可以回收
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (service, machine_id),
UNIQUE (service, host, port)
);
- 承担节点注册表和简单租约的作用。
- 节点根据
IP + Port标识自身。这是一个唯一键,也就是说每个节点只对应一条注册记录。 - 每 5 分钟更新一次
last_seen,记录最近一次心跳,用于简单判断节点是否仍然活跃。
2. 节点启动
整个 Machine ID 的查找、分配和回收过程放在同一个事务中。
-
程序启动时使用
IP + Port查询节点记录:SELECT machine_id, version FROM system_nodes WHERE service = $1 AND host = $2 AND port = $3;如果记录存在,直接使用原 Machine ID 和
version;否则进入分配流程。 -
获取当前未占用的 Machine ID:
SELECT m.machine_id FROM generate_series(0, 255) AS m(machine_id) LEFT JOIN system_nodes n ON n.service = $1 AND n.machine_id = m.machine_id WHERE n.machine_id IS NULL ORDER BY m.machine_id LIMIT 1获取到 Machine ID 后执行:
INSERT INTO system_nodes(service, machine_id, host, port, version) VALUES ($1, $2, $3, $4, 1);如果 INSERT 因唯一约束冲突失败,重新进入分配流程。
-
如果获取不到,也就是所有 Machine ID 都已经分配出去了,则进入复用流程:
-
先尝试获取超过 7 天没有使用的 ID:
SELECT * FROM system_nodes WHERE service = $1 AND last_seen < now() - interval '7 day' ORDER BY machine_id LIMIT 1 FOR UPDATE SKIP LOCKED; -
如果没有,再尝试获取超过 1 天没有使用的 ID:
SELECT * FROM system_nodes WHERE service = $1 AND last_seen < now() - interval '1 day' ORDER BY machine_id LIMIT 1 FOR UPDATE SKIP LOCKED; -
如果仍然没有,有点异常,推送一个告警,然后尝试获取超过 1 小时没有心跳的 ID:
SELECT * FROM system_nodes WHERE service = $1 AND last_seen < now() - interval '1 hour' ORDER BY machine_id LIMIT 1 FOR UPDATE SKIP LOCKED;异常情况:还是找不到可复用 ID,则属于极端情况,报启动失败,需人工介入排查。正常情况下顶多十几台机器,不应该在 1 小时内耗尽 256 个 Machine ID。
-
获取到可用 Machine ID 后,更新节点信息,并递增
version:UPDATE system_nodes SET host = $1, port = $2, version = version + 1, last_seen = now() WHERE service = $3 AND machine_id = $4 RETURNING version;
-
3. 节点心跳(5 分钟一次)
UPDATE system_nodes SET last_seen = now()
WHERE service = $1 AND machine_id = $2 AND host = $3 AND port = $4 AND version = $5;
异常情况:更新失败,说明 Machine ID 的所有权已经发生变化。此时查询当前节点信息,打印 ERROR 日志并退出,避免旧节点继续使用已经被重新分配的 Machine ID。