Sonyflake MachineID 分配方案

话接上文《Sonyflake 分布式 ID 生成方案》,Machine ID 需要借助数据库来实现自动分配。

需求:

  1. 自动分配 Machine ID
  2. 最大限度保证 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)
);
  1. 承担节点注册表和简单租约的作用。
  2. 节点根据 IP + Port 标识自身。这是一个唯一键,也就是说每个节点只对应一条注册记录。
  3. 每 5 分钟更新一次 last_seen,记录最近一次心跳,用于简单判断节点是否仍然活跃。

2. 节点启动

整个 Machine ID 的查找、分配和回收过程放在同一个事务中。

  1. 程序启动时使用 IP + Port 查询节点记录:

    SELECT machine_id, version FROM system_nodes
    WHERE service = $1 AND host = $2 AND port = $3;
    

    如果记录存在,直接使用原 Machine ID 和 version;否则进入分配流程。

  2. 获取当前未占用的 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 因唯一约束冲突失败,重新进入分配流程。

  3. 如果获取不到,也就是所有 Machine ID 都已经分配出去了,则进入复用流程:

    1. 先尝试获取超过 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;
      
    2. 如果没有,再尝试获取超过 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;
      
    3. 如果仍然没有,有点异常,推送一个告警,然后尝试获取超过 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。

    4. 获取到可用 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。

如果你有魔法,你可以看到一个评论框~