雪花算法worker id管理方案

摘要

本文沉淀自 ChatGPT 历史素材,已整理为正式方案形态,归属于 数据可观测性与稳定性治理详细方案目录

适用场景

适用于 雪花算法worker id管理方案 相关的架构设计、标准制定、方案评审、落地实施和后续治理。

方案正文

{“open”:[{“ref_id”:“turn474389search5”},{“ref_id”:“turn474389search2”},{“ref_id”:“turn474389search3”},{“ref_id”:“turn308258search11”},{“ref_id”:“turn308258search18”},{“ref_id”:“turn308258search1”},{“ref_id”:“turn308258search16”}],“response_length”:“short”}

{“open”:[{“ref_id”:“turn474389search9”},{“ref_id”:“turn474389search21”},{“ref_id”:“turn474389search17”}],“response_length”:“short”}

{“find”:[{“ref_id”:“turn512734view2”,“pattern”:“machine id”},{“ref_id”:“turn512734view2”,“pattern”:“private IP”},{“ref_id”:“turn512734view2”,“pattern”:“lower 16 bits”}],“response_length”:“short”}

先给一个中间结论:worker id 最合理的管理思路,不是把它当“永久机器编号”,而是当“带租约的节点身份”。我已经看到几类典型实现:美团 Leaf 偏向 ZK 分配 + 本地缓存兜底,百度 uid-generator 偏向 启动时从 DB 领号,还有一类是直接用机器属性推导 machine id。下面我把这几类的优缺点和适用场景收敛成一版建议。

更合理的做法是:

worker id 当成“带租约的运行时节点身份”,不要当成“永久机器编号”。 也就是:权威状态放中心协调存储,运行时靠租约/会话持有,本地只做兜底缓存。 X 最早公开 Snowflake 时提到,worker number 是在启动时经由 ZooKeeper 选择的,也可以被配置覆盖;这本质上就是“启动绑定,而不是写死在代码里”。

你可以把生命周期设计成 6 个阶段:

  1. 分配:实例启动时,去协调中心申请一个空闲 worker id
  2. 绑定:把 worker id 绑定到当前实例指纹,例如 cluster/zone/app/instanceId/bootUuid
  3. 续约:实例存活期间持续 heartbeat 或 renew lease。
  4. 重启处理:优先尝试“认领自己上次的 id”,认领失败再领新号。
  5. 释放/回收:正常停机主动释放;异常退出由 TTL/会话失效自动回收。
  6. 防重号护栏:回收后不要立刻无脑复用,至少要校验 lastTimestamp 与当前时钟关系。这个点很关键。ZooKeeper 的 ephemeral node、etcd 的 lease、Consul 的 session/KV lock 都天然适合承载这种生命周期:会话失效或 TTL 到期后,绑定关系会自动消失。

存在哪里最合理

建议分成两层:

第一层:权威存储(source of truth) 优先放在协调型存储里,而不是只放本地文件。

  • ZooKeeper / etcd / Consul:最适合弹性扩缩容、容器化、多机房。
    • ZooKeeper 可以用 EPHEMERAL + SEQUENCE 这种模式来拿到唯一序号,并且会话断开后节点自动删除。
    • etcd 的 key 可以绑 lease,TTL 到期后 key 自动删除。
    • Consul 可以用 session + KV 的 acquire/release 做分布式锁或租约绑定。

第二层:本地缓存(fallback cache) 本地磁盘可以缓存上次成功分配的 worker id,用于协调中心短时不可用时兜底启动,但本地缓存不能作为唯一真相。美团 Leaf 就是这种思路:首次从 ZooKeeper 取到 workerID 后,会在本机文件系统缓存一个 workerID 文件,以降低对 ZooKeeper 的强依赖。

本地缓存里到底该存什么

不要只存 worker_id。更合理的是至少存:

  • worker_id
  • instance_fingerprint
  • boot_uuid
  • last_timestamp
  • version
  • updated_at

原因很直接:Leaf 的 issue 里就有人指出,本地缓存如果只存 workId 而不存上次最大时间戳,那么在 ZooKeeper 不可用且机器时钟被拨回时,存在重复 ID 风险。


业界常见做法,可以分成 4 类

1)协调中心分配型:最推荐

典型参考:Twitter Snowflake、Meituan Leaf

  • X/Twitter 当年公开 Snowflake 时写得很清楚:Snowflake 是一个内部服务worker number 在启动时通过 ZooKeeper 选择,也可以通过配置文件覆盖。
  • 美团 Leaf 的做法更接近今天企业里的常见落地:用 ZooKeeper 分配 workerID,本地文件做兜底缓存

这类方案适合:

  • K8s / 容器弹性环境
  • 多实例频繁扩缩容
  • 多机房部署
  • 对重复号容忍度极低

2)数据库分配型:简单好落地

典型参考:Baidu uid-generator

Baidu 的 uid-generator README 明确写了:默认内置一个基于数据库的 worker id assigner,在启动时分配 worker id,并且重启后会 dispose previous worker node id,也就是偏向“重启重新领号,而不是原号续用”。

这类方案适合:

  • 节点数不大
  • 没有 ZooKeeper/etcd/Consul
  • 希望快速上线
  • 对启动时访问 DB 可接受

缺点也明显:

  • 领号是“启动时集中动作”,扩缩容频繁时 DB 会变成控制点
  • 做租约、续约、自动回收,不如协调组件自然

3)外部配置注入型:适合静态集群

典型参考:ShardingSphere

ShardingSphere 的文档里,Snowflake key generator 的 worker.id 是一个外部属性,默认值甚至是 0,这意味着如果你不自己管理唯一性,就很危险。 它的企业案例里,Energy Monster 是通过 Apollo 配置中心下发 ${dcc.node.id}worker.id 的。

这类方案适合:

  • 机器相对固定
  • 有明确节点编号体系
  • 运维管控较强
  • 不经常弹性扩缩容

本质上它不是“自动分配”,而是“外部唯一值注入”。

4)机器属性推导型:只适合非常受控的环境

典型参考:Sonyflake

Sonyflake 默认 MachineID私网 IP 的低 16 位,同时允许自定义 CheckMachineID 做校验;README 也明确说,如果不提供校验函数,默认不做唯一性校验

这类做法只适合:

  • 私网 IP 稳定且唯一
  • 实例生命周期长
  • 网络模型非常受控

不太适合:

  • K8s Pod IP 漂移明显
  • 多集群、多 VPC、混合云
  • 机器重建频繁

结论

最优原则

权威状态放中心协调系统;本地只缓存,不拍板。

选型建议

  • 动态环境 / 容器 / 多机房: 选 Consul / etcd / ZooKeeper 的租约式分配
  • 传统 VM、小规模集群: 可以先用 DB 表分配
  • 很固定的少量节点: 可以用 配置中心静态注入
  • 仅靠 IP/MAC/hostname 推导: 只能作为简化方案,不应作为企业默认标准。

一个比较稳的企业级设计

如果你问我“应该怎么定规范”,我会建议直接定成下面这样:

1. 权威记录放协调中心

例如 key/value 或节点记录:

  • worker_id
  • biz
  • cluster
  • zone
  • instance_id
  • boot_uuid
  • lease_id/session_id
  • last_timestamp
  • status
  • updated_at

2. 启动流程

  • 优先按 instance_id 查自己上次绑定记录
  • 如果记录存在且 lease 已失效,并且 now >= last_timestamp - tolerateSkew,尝试认领原 worker_id
  • 否则申请新的 worker_id
  • 成功后写本地缓存

3. 运行流程

  • 周期续约
  • 周期上报 last_timestamp
  • 暴露 /idgen/worker 运维接口和监控指标

4. 异常退出

  • 不依赖优雅停机
  • 主要依赖 lease/session TTL 自动失效回收

5. 启动护栏

只要出现以下任一情况,拒绝启动发号

  • 本地时钟明显回拨
  • 发现同一 worker_id 仍被活跃 lease 占用
  • 本地缓存与中心记录不一致
  • 无法确认 last_timestamp 安全

针对你这个问题,我的建议

最合理的答案是:

  • worker id生命周期由协调中心管理,不是由机器自己永久保存。
  • worker id权威副本ZooKeeper / etcd / Consul / DB work_node 表
  • worker id本地副本只存文件或本地盘,作为兜底,并且必须带 last_timestamp
  • 业界参考可以看成四派:
    • Twitter Snowflake:启动时通过 ZooKeeper 选 worker number,可配置覆盖。
    • Meituan Leaf:ZooKeeper 分配 + 本地文件缓存兜底。
    • Baidu uid-generator:DB 启动领号,重启后旧号作废。
    • ShardingSphere / Sonyflake:一个偏外部注入,一个偏机器属性推导。

结合你一贯的微服务/多环境治理语境,我更推荐团队定成:Consul Session + KVZooKeeper/etcd Lease 的租约式 worker id 管理规范,并把本地缓存作为 SLA 兜底,而不是主存储。

你要的话,我下一条可以直接给你补一版 《Snowflake Worker ID 生命周期管理规范 v1.0》,包括:

  • 状态机
  • 数据模型
  • 启动/续约/回收时序图
  • Consul / DB 两套落地实现。

我已经把 Consul 侧能用的原语确认了:session + KV acquire/release + TTL 续约 + blocking query 这套就够把 worker id 做成一个完整的租约系统。接下来我会把它抽象成企业里的两个面:数据面发号 SDK/服务,以及 控制面申请、审批、配额、审计、运维

{“find”:[{“ref_id”:“turn837521search0”,“pattern”:“LockDelay”},{“ref_id”:“turn800940search0”,“pattern”:“LockDelay”}],“response_length”:“short”}

  1. 总体架构:控制面 / 数据面 / Consul 职责边界
  2. 实例从启动、申请、持有、使用、续约、释放、回收的全生命周期
  3. 控制面如何做:模型、API、审批、配额、审计、运维
  4. 业务方接入标准与流程:申请材料、准入门槛、发布流程、验收标准

一、先定几个关键原则

1)Consul 只做运行时租约协调

Consul 的 session + KV acquire/release 很适合做分布式租约锁:先创建 session,再对某个 KV key 执行 acquire;session 失效后,锁会自动释放;KV 还支持 blocking query,可以用 X-Consul-Index 做 watch。

所以我的建议是:

  • Consul:存“谁当前持有哪个 worker id”的运行时真相
  • 控制面数据库:存“应用、池子、审批、配额、策略、审计”的管理面真相
  • 本地文件:只存“上次拿到的 worker id + 本地最大时间戳 checkpoint”的兜底状态

不要把审批、申请单、容量规划、操作审计全部塞进 Consul KV。 Consul 负责租约,控制面负责治理。


2)不要做“全公司一个全局 worker id 池”

Consul session 的 dc 参数跨 datacenter 使用并不推荐,所以不要设计成一个跨多机房/多地域统一抢同一把锁的大池子。更合理的是:

  • 每个环境 + 每个机房/DC 一套独立 worker 池
  • 全局唯一性通过 Snowflake bit 里的 region / dc / env bits 保证
  • Consul 只负责本 DC 内的 worker id 排他租约

这是因为 Consul 官方对 session 相关接口明确写了:跨 datacenter 使用 dc 参数不推荐


3)业务应用不要直接拿大权限操作 Consul

Consul 的锁本身不强制客户端必须先持锁才能写 KV;如果 ACL 管不好,任何客户端都可能直接改 key。官方 leader election 文档也明确提醒了这一点。

所以企业里要这么做:

  • 业务应用不直接拿 Consul 高权限 token
  • 通过标准 SDK / Sidecar / Agent
  • 由控制面下发最小权限 ACL
  • 最好通过 auth method / binding rule 动态换取短期 ACL token,而不是手工发长期 token。Consul 的 auth method 和 binding rule 就是为这种“按身份自动绑定权限”的模式准备的。

二、推荐的总体架构

建议采用 “控制面 + 运行时 Sidecar/SDK + Consul” 三层设计。

flowchart LR
    subgraph CP["控制面 Control Plane"]
        APP["接入门户 / 申请审批"]
        META["元数据服务\n(app/pool/policy/quota)"]
        ACL["ACL/Token 下发服务"]
        OPS["运维控制台\n(租约/告警/审计/回收)"]
        DB["MySQL/PostgreSQL\n管理面元数据"]
    end

    subgraph DP["数据面 Data Plane"]
        SDK["Worker ID SDK / Sidecar"]
        GEN["Snowflake Generator"]
        FILE["本地 checkpoint\n(workerId/maxTs)"]
    end

    subgraph CONSUL["Consul"]
        SES["Session"]
        KV["KV Leases"]
        WATCH["Blocking Query Watch"]
    end

    APP --> META
    META --> DB
    META --> ACL
    OPS --> DB
    OPS --> CONSUL

    SDK --> ACL
    SDK --> SES
    SDK --> KV
    SDK --> WATCH
    SDK --> FILE
    GEN --> SDK

职责边界

控制面

负责:

  • 应用接入申请
  • worker 池规划
  • 配额与限额
  • ACL / token / namespace 绑定
  • 接入审批和审计
  • 运维可视化、人工回收、强制摘除、告警

数据面

负责:

  • 实例启动时申请 worker id
  • 建立 Consul session
  • 抢占租约 key
  • 周期 renew
  • 将拿到的 worker id 提供给本地生成器
  • 本地 checkpoint 与时钟回拨保护

Consul

负责:

  • 当前谁持有哪一个 worker id
  • session TTL / 失效自动释放
  • prefix watch / 变更监听

三、Consul 中怎么建模

建议把 Consul KV 只用于运行时对象,路径保持极简。

1)Key 目录设计

租约 key

/idgen/leases/{env}/{dc}/{appCode}/{workerId}

value 示例:

{
  "appCode": "order-center",
  "instanceId": "pod-7f9d8c6fcb-2xk9m",
  "bootId": "8a2c4d7e-...",
  "sessionId": "adf4238a-...",
  "node": "k8s-node-a12",
  "issueTime": "2026-04-09T13:30:00Z",
  "generatorEpoch": "2026-01-01T00:00:00Z",
  "sdkVersion": "1.2.0"
}

这个 key 的核心意义不是存很多业务数据,而是:

  • key 存在且 Session=xxx:说明这个 worker id 正被某个实例持有
  • ?acquire=sessionId 成功:说明当前实例拿到了这个 worker id
  • session 失效:这个 worker id 自动释放

Consul KV 的 acquire/release 与 session 是官方支持的标准模式。


2)Session 设计

每个实例启动后创建一个 session:

  • Behavior = release
  • TTL = 15s ~ 30s
  • LockDelay = 5s ~ 15s
  • 关联 NodeChecks,至少保留默认 serfHealth
  • 不允许创建“无 TTL 永不失效”的 session

原因:

  • Behavior=release:session 失效时自动释放锁。
  • TTL 官方要求在 10s ~ 86400s 之间;且官方建议使用“尽量低但实用”的 TTL,因为强制过期后锁的清理可能延迟到最多约 2 倍 TTL,因此不要用很长 TTL。
  • LockDelay 默认是 15s,并且必须大于 0。
  • 如果 session 没有 TTL,就不会自动过期,需要手动删除;这不适合 worker id。

推荐默认值

  • TTL = 20s
  • renew interval = 6s
  • LockDelay = 5s
  • graceful shutdown release timeout = 2s

四、从启动到回收:全生命周期设计

以下按实例视角讲完整生命周期。


阶段 A:接入前准备(控制面完成)

在实例真正启动前,控制面已经给它准备好了:

  • 所属 appCode
  • 可用的 worker 池范围,例如 0~127
  • 所属 env/dc
  • 允许使用的 Snowflake bit schema
  • Consul key prefix
  • 短期 ACL token 获取方式
  • 本地 SDK 配置

也就是说,运行时不再做审批,只做租约获取


阶段 B:实例启动

步骤 B1:身份认证

实例启动时,Sidecar/SDK 先凭工作负载身份向控制面换取运行时凭证:

  • K8s:ServiceAccount / workload identity
  • VM:机器身份、实例证书、节点注册身份
  • 也可以直接走 Consul auth method + binding rule

Consul 的 auth method 可以把应用身份映射成 ACL token,并通过 binding rule 自动挂载角色/服务身份。

步骤 B2:拉取 bootstrap 配置

控制面返回:

  • appCode
  • env
  • dc
  • workerPoolStart / workerPoolEnd
  • epoch
  • clockRollbackStrategy
  • checkpointPath
  • consulNamespace(Enterprise 可选)
  • consulToken 或 token exchange 方式

如果团队用 Consul Enterprise,还可以基于 namespace / admin partitions 做多租户隔离。官方文档明确把 namespace 和 admin partitions 作为多租户能力的一部分。


阶段 C:创建 Session

实例在本地 Consul agent 上创建 session:

PUT /v1/session/create
{
  "Name": "idgen-order-center-pod-xxx",
  "Node": "node-abc",
  "Behavior": "release",
  "TTL": "20s",
  "LockDelay": "5s",
  "NodeChecks": ["serfHealth"]
}

说明:

  • session 必须关联到一个已注册 node。
  • renew 时 Consul 可能返回比原来更高的 TTL,表示服务端负载高,希望客户端降低 renew 频率,所以 SDK 不能把 TTL 写死。

阶段 D:申请 Worker ID

优先级 1:尝试认领上次的 worker id

SDK 先读本地 checkpoint:

{
  "workerId": 37,
  "lastTimestamp": 1744183200123,
  "bootId": "prev-boot-uuid",
  "appCode": "order-center",
  "dc": "sh-idc-a"
}

然后尝试重新 acquire:

PUT /v1/kv/idgen/leases/prod/sh-idc-a/order-center/37?acquire={sessionId}

如果返回 true,说明成功认领原 worker id。 这样做的好处是:

  • 减少 worker id 抖动
  • 缩短重启恢复时间
  • 降低小池子场景下的竞争

优先级 2:认领失败,再扫描空闲池

如果原 id 被占用,则在自己被授权的池范围内线性探测或随机探测:

  • 37 -> 38 -> 39 -> ...
  • 逐个 ?acquire=sessionId
  • 首个成功的就是当前 worker id

对于典型 Snowflake 的 worker bits(例如 5~10 bits),单 app / 单 dc 池规模通常不会大到不可扫描。 控制面也可以把空闲候选列表缓存在内存里,通过 watch prefix 来刷新,而不是每次全量列举。

为什么可行

Consul 官方 leader election 示例就是:

  1. create session
  2. 对指定 key ?acquire=<session>
  3. 其他实例 watch key 变化,锁释放后再抢占。

本质上,worker id 分配就是把“leader key”扩展成“一组编号 key”。


阶段 E:开始使用 Worker ID 发号

一旦 acquire 成功,SDK 把 workerId 注入给本地 Snowflake 生成器:

finalId = timestampBits + region/dc bits + workerId + sequence

注意几个关键约束:

1)Consul 不在发号热路径

每次发号绝不能访问 Consul。 Consul 只参与:

  • 启动拿号
  • 周期 renew
  • 少量状态上报

真正发号必须是本地内存逻辑。

2)本地必须维护 maxTimestampSeen

每次成功生成 ID,都更新内存中的 maxTimestampSeen,并周期落盘到 checkpoint 文件。

建议:

  • 每 1 秒 flush 一次
  • 或每生成 1 万个 ID flush 一次
  • 关停时再强制 flush 一次

3)必须做时钟回拨保护

规则建议如下:

  • 当前时间 now >= maxTimestampSeen:正常发号
  • 0 < maxTimestampSeen - now <= 2s:阻塞等待追平
  • > 2s:拒绝发号并告警

这个规则是平台规范,不是 Consul 能替你兜底的。


阶段 F:续约

实例持有 worker id 后,后台线程周期调用:

PUT /v1/session/renew/{sessionId}

Consul 文档明确说明:renew 会把有效期向后延长一个 TTL;同时在高负载下返回的 TTL 可能比创建时更大。

续约策略建议

  • renew interval = min(returnedTTL / 3, 6s)
  • 连续 3 次 renew 失败:
    • 立即进入 READ_ONLY / FAIL_CLOSED
    • 停止发号
    • 等待 session 恢复或进程退出

不要在 session 状态不确定时继续发号。


阶段 G:锁变化监控

数据面和控制面都可以对租约前缀做 watch:

  • /idgen/leases/prod/sh-idc-a/order-center/

Consul 的 blocking query 可以利用 X-Consul-Index 做长轮询。

watch 的用途

  • 控制面刷新“当前租约视图”
  • 同 app 的其他实例知道哪个 id 已被占用
  • 运维面板实时显示池使用率
  • 发现异常释放 / 抖动

阶段 H:优雅下线

应用收到 SIGTERM / preStop:

  1. 停止接收业务流量
  2. 停止发号
  3. flush maxTimestampSeen 到本地文件
  4. 主动 release key
  5. destroy session
  6. 退出

可用接口:

  • PUT /v1/kv/... ?release=<sessionId>
  • PUT /v1/session/destroy/{sessionId}

KV 的 release 会清空 key 上的 Session;destroy session 是幂等的,即使 session 已不存在也返回成功语义。


阶段 I:异常退出

如果实例宕机、Pod 被杀、节点失联:

  • 不会走优雅 release
  • 依赖 session TTL 超时 + Behavior=release 自动释放
  • 由于官方说明锁被强制过期后,清理最多可能延迟到约 2 倍 TTL
  • 再叠加 LockDelay
  • 所以 worker id 不能认为是“立刻可复用”

阶段 J:回收与再分配

这里是最容易埋重复号风险的地方。

回收规则建议

一个 worker id 被释放后,进入 cooldown,不要马上重用。 建议最短冷却时间:

cooldown = max(2 * TTL + LockDelay, 最大允许时钟偏差, 本地checkpoint落盘周期)

按推荐值估算:

  • TTL = 20s
  • LockDelay = 5s
  • clock skew budget = 3s
  • checkpoint flush = 1s

则:

  • cooldown >= 45s

再分配前检查

控制面或 SDK 在重用某个 worker id 前,至少检查:

  • 该 lease key 当前无 Session
  • 已超过 cooldown
  • 申请方仍在授权池内
  • 本地 now >= checkpoint.lastTimestamp

如果做得更稳,还可以把最近一次 checkpoint 的 lastTimestamp 同步到控制面数据库,用于回收前二次校验。


五、控制面怎么做

控制面不是“发号服务本身”,而是治理系统。 建议拆成 6 个模块。


模块 1:应用注册中心

管理对象:

  • appCode
  • appName
  • ownerTeam
  • ownerUser
  • bizDomain
  • env
  • dcScope
  • deployMode(K8s / VM)
  • idSchema
  • expectedReplicas
  • peakQPS
  • poolMode(dedicated / shared)
  • status

表设计建议

idgen_app

  • app_id
  • app_code
  • owner_team
  • owner_user
  • biz_domain
  • env
  • dc_scope
  • deploy_mode
  • worker_bits
  • region_bits
  • sequence_bits
  • epoch
  • pool_mode
  • max_replicas
  • peak_id_qps
  • status
  • created_at
  • updated_at

模块 2:Worker 池管理

两种池模式

A. Dedicated Pool

某应用独享一个 worker 范围,例如:

  • order-center@prod@sh-idc-a = 0~63

适合:

  • 核心交易系统
  • 容量预测清晰
  • 不希望与别的系统抢号

B. Shared Pool

多个低频应用共享一个大池,由控制面按 app 授权子范围或上限。

适合:

  • 小系统
  • 非核心链路
  • 测试环境

控制面要做的事

  • 池规划
  • 冲突检测
  • 扩容 / 缩容
  • 预留号段管理
  • 环境隔离

模块 3:ACL / 凭证管理

Consul ACL token 是访问控制核心;启用 ACL 后,请求方必须带 token,token 通过 policy / service identity / node identity 授权。

推荐做法

  • 控制面为每个 app/environment 生成最小权限 policy
  • 运行时通过 auth method 登录,换短期 token
  • token 只允许:
    • session:write/read
    • 指定 prefix 的 key:write/read

示例权限思路

只允许访问:

/idgen/leases/prod/sh-idc-a/order-center/*

不允许访问:

/idgen/leases/prod/*/*
/idgen/leases/*/*/*

这样业务方即使接错配置,也抢不到别人的 worker id。


模块 4:接入审批与策略引擎

审批不只是“批不批”,而是判断该应用有没有资格接入统一 worker id 平台

审批规则建议

必须校验:

  • 是否已完成 NTP/Chrony 校时
  • 是否使用标准 SDK / Sidecar
  • 是否具备本地持久化 checkpoint
  • 是否有 graceful shutdown hook
  • 是否明确最大副本数
  • 是否明确单 DC 部署边界
  • 是否完成压测与时钟回拨测试
  • 是否接入监控与告警
  • 是否禁止自研私有 snowflake 变体

不满足的,不能放行到生产。


模块 5:运维台

需要看见四类视图:

1. 应用视图

  • 哪些 app 已接入
  • 每个 app 的池范围
  • 每个环境 / DC 的使用率

2. 租约视图

  • 当前活跃 lease
  • worker id app instance session 映射
  • 最近 acquire/release 时间
  • TTL / renew 状态

3. 风险视图

  • session renew 连续失败
  • worker 池使用率 > 80%
  • 同 app 重启抖动频繁
  • 时钟回拨告警
  • checkpoint 落盘失败

4. 操作视图

  • 强制摘除 lease
  • 池扩容
  • 冻结 app
  • 切换只读 / 熔断

模块 6:审计与修复任务

审计

记录:

  • 谁申请接入
  • 谁审批
  • 谁扩容池
  • 谁手工释放了 lease
  • 哪个实例在哪个时刻拿到了哪个 worker id

修复任务

定时任务做:

  • 孤儿状态检查
  • 冷却期检查
  • 池使用率巡检
  • checkpoint 与控制面元数据对账

六、业务方“从申请到使用”的标准流程

以下一版标准接入流程


阶段 1:提出接入申请

业务方提交《Worker ID 平台接入单》,至少填写:

基本信息

  • 应用名 / appCode
  • 业务域
  • owner 团队 / owner 人
  • 环境:dev / test / prod
  • 部署形态:K8s / VM
  • 所在 DC / region

容量信息

  • 常驻副本数
  • 峰值副本数
  • 峰值 ID QPS
  • 是否多活
  • 是否有批处理尖峰

技术信息

  • 使用语言 / SDK 版本
  • 是否启用本地持久化目录
  • 是否接入标准 shutdown hook
  • NTP/Chrony 状态
  • 监控接入方式

设计信息

  • Snowflake bit 规划
  • 是否独享池
  • 需要的 worker 数量
  • 回拨容忍策略

阶段 2:平台评审

平台 / 架构组评审以下事项:

A. bit 位规划是否合理

例如:

  • 41 bits timestamp
  • 2 bits region
  • 3 bits dc
  • 7 bits worker
  • 10 bits sequence

B. 池子是否够

比如:

  • prod/sh-idc-a/order-center:64 个 worker
  • 当前峰值副本 30,扩容冗余 2x,足够

C. 部署边界是否清晰

  • 是否跨 DC 共用同一池
  • 是否混淆 test/prod
  • 是否将共享库误当独立 app 申请

阶段 3:平台开通

控制面执行:

  1. 创建 app 元数据
  2. 分配池范围
  3. 下发接入配置
  4. 配置 ACL policy / binding rule
  5. 打开监控模板
  6. 给出 SDK 接入文档和验收清单

阶段 4:业务集成

业务方只做四件事:

  1. 引入 SDK / Sidecar
  2. 配置 appCode / env / dc
  3. 使用统一 nextId() 接口
  4. 接入 shutdown hook 和健康检查

业务代码不应该:

  • 直接调用 Consul API
  • 自己维护 worker id
  • 自己设计回收逻辑
  • 自己保存长期 Consul token

阶段 5:测试验收

上线前至少要过 6 项测试:

  1. 并发唯一性测试
  2. 实例重启重认领测试
  3. 异常 kill -9 自动回收测试
  4. 时钟回拨测试
  5. Consul 短时不可用测试
  6. 池耗尽测试

未通过,不允许进生产。


阶段 6:生产启用

启用后控制面持续观测:

  • lease_acquire_latency
  • session_renew_failures
  • worker_pool_utilization
  • clock_rollback_events
  • idgen_checkpoint_flush_failures

七、推荐的运行标准

这是建议团队平台直接固化成规范的部分。

1. 统一约束

  • 一个 app 在一个 env + dc 下有且仅有一个 worker 池配置
  • 不允许跨 DC 共用同一 Consul 锁池
  • 所有实例必须通过标准 SDK/Sidecar 获取 worker id
  • 不允许直接操作 /idgen/leases/* 以外的 KV

2. 推荐参数

  • TTL = 20s
  • renew = 6s
  • LockDelay = 5s
  • checkpoint flush = 1s
  • cooldown >= 45s

3. 失败策略

  • renew 连续失败 3 次:停止发号
  • 时钟回拨 > 2s:停止发号
  • worker 池耗尽:启动失败,不降级为随机 worker id
  • ACL/token 获取失败:启动失败

4. 监控阈值

  • 池使用率 > 70%:预警
  • 85%:告警并要求扩容

  • renew 失败率 > 1%:告警
  • 同 app 5 分钟内重启 > 3 次:告警

八、建议

如果你是企业级平台来做,建议直接定成下面这套口径:

架构口径

  • 控制面:负责申请、审批、池规划、策略、ACL、审计、运维
  • 数据面:标准 SDK / Sidecar,负责运行时 session 和 lease
  • Consul:只承载 worker id 租约,不承载审批流程

生命周期口径

  • 启动:鉴权 拉配置 create session 尝试重认领 扫描抢占
  • 使用:本地发号、本地 checkpoint、后台 renew
  • 下线:停发号 flush release destroy session
  • 异常:依赖 TTL 自动释放
  • 回收:带 cooldown,不立即复用

接入口径

  • 业务方申请的是**“接入统一 worker id 平台”**
  • 不是“申请一个固定 worker id”
  • worker id 是运行时租约资源
  • 平台按 app / env / dc 分配池和权限
  • 业务只用标准组件,不直接碰 Consul

这套方案的核心优点是:

  • 与 Consul 官方 session/KV/ACL 机制完全一致,没有反模式。

  • 控制面和数据面边界清晰,不把 Consul 当业务数据库。

  • 能支撑团队这种企业场景下的多环境、审批、运维、审计、容量治理

  • 控制面 API 清单

  • MySQL 表结构

  • Consul Key 目录规范

  • Java SDK/Sidecar 启动时序图

  • 异常场景处理矩阵。

我先按“正式设计文档”的结构来组织:目标与边界、总体架构、控制面/数据面、数据模型、时序、异常处理、接入规范、运维监控。为了把 Consul 相关机制写准,我会先核对 session、KV acquire/release、TTL、ACL 这些官方能力,再收敛成企业级落地方案。

{“open”:[{“ref_id”:“https://developer.hashicorp.com/consul/docs/automate/session”},{“ref_id”:“https://developer.hashicorp.com/consul/api-docs/session”},{“ref_id”:“https://developer.hashicorp.com/consul/api-docs/kv”},{“ref_id”:“https://developer.hashicorp.com/consul/api-docs/features/blocking”},{“ref_id”:“https://developer.hashicorp.com/consul/docs/secure/acl/auth-method”},{“ref_id”:“https://developer.hashicorp.com/consul/docs/secure/acl/token”},{“ref_id”:“https://blog.x.com/engineering/en_us/a/2010/announcing-snowflake”}],“response_length”:“short”}

{“open”:[ {“ref_id”:“turn599981view1”,“lineno”:205}, {“ref_id”:“turn599981view1”,“lineno”:248}, {“ref_id”:“turn599981view2”,“lineno”:245}, {“ref_id”:“turn599981view3”,“lineno”:111}, {“ref_id”:“turn599981view4”,“lineno”:198}, {“ref_id”:“turn599981view5”,“lineno”:201}, {“ref_id”:“turn599981view6”,“lineno”:100} ],“response_length”:“short”}

{“find”:[{“ref_id”:“turn599981view6”,“pattern”:“ZooKeeper”},{“ref_id”:“turn599981view6”,“pattern”:“worker number”},{“ref_id”:“turn599981view6”,“pattern”:“Snowflake is a network service”},{“ref_id”:“turn599981view6”,“pattern”:“41 bits”}],“response_length”:“short”}

{“find”:[ {“ref_id”:“turn599981view1”,“pattern”:“TTL”}, {“ref_id”:“turn599981view1”,“pattern”:“lock-delay”}, {“ref_id”:“turn599981view1”,“pattern”:“between 0 and 60 seconds”}, {“ref_id”:“turn599981view1”,“pattern”:“between ”10s” and “86400s""}, {“ref_id”:“turn599981view1”,“pattern”:“renew”}, {“ref_id”:“turn599981view2”,“pattern”:“acquire”}, {“ref_id”:“turn599981view5”,“pattern”:“ACL tokens”} ],“response_length”:“short”}

我已经把 Consul 的关键约束核对到了:session 失效条件、TTL 下限/上限、LockDelayKV acquire/releaseblocking query、以及 auth method/ACL 的边界。文档里我会把这些约束直接转成平台规范,避免方案停留在概念层。

以下一版可直接作为内部方案基线的文档草案。

《基于 Consul 和雪花算法的唯一 ID 生成平台详细设计》v1.0

1. 文档目标

建设一个企业级唯一 ID 生成平台,面向公司内部应用统一提供:

  • 全局唯一
  • 趋势递增
  • 高性能、低延迟
  • 多实例并发安全
  • 多环境 / 多机房可治理
  • 具备申请、审批、配额、审计、运维能力

本方案采用:

  • ID 算法层:Snowflake 变体
  • 运行时协调层:Consul Session + KV Lock
  • 管理治理层:独立控制面(Portal + API + Metadata DB + Ops Console)

Snowflake 最早公开的核心思想就是把 64 位 ID 组合为时间戳、worker number、sequence number,其中 worker number 在启动时选定;Consul 原生提供 session、TTL、lock-delay、KV acquire/release、blocking query 等能力,适合承载 worker id 的“租约式生命周期管理”。


2. 设计范围

2.1 方案覆盖范围

  • ID bit 位规划
  • worker id 生命周期管理
  • 控制面设计
  • SDK / Sidecar 设计
  • 申请接入标准与流程
  • 数据模型
  • 监控、告警、审计、运维
  • 异常处理与风险控制

2.2 不在本期范围

  • 业务主键语义设计
  • DB 分库分表策略
  • ULID / UUID / Segment 双栈统一平台
  • 跨公司外部 ID 交换标准

3. 核心设计原则

3.1 worker id 不是永久机器号,而是运行时租约

worker id 不与机器、Pod、VM 永久绑定,而是实例启动后动态申请、持有、续约、释放、回收。

3.2 Consul 只做运行时协调,不做管理面真相库

Consul 存当前谁持有哪个 worker id;审批、接入单、配额、策略、审计、池规划放在控制面数据库。

3.3 业务不直连高权限 Consul

业务必须通过标准 SDK / Sidecar 使用平台,不直接拿高权限 Consul token,也不允许自行读写任意租约 key。Consul 的锁是advisory,并不会强制阻止未持锁客户端写 KV,所以 ACL 和接入组件必须收口。

3.4 发号热路径绝不访问 Consul

Consul 只参与:

  • 启动申请 worker id
  • 周期 renew session
  • 状态 watch
  • 下线 release

真正 nextId() 必须完全在本地内存执行。

3.5 按 env + region + dc 隔离 worker 池

不设计“全公司一个全局 worker 池”。Consul session API 对跨 datacenter 参数本身就注明不推荐,因此运行时锁池应以本地 datacenter 为边界。


4. 总体目标与非功能指标

4.1 可用性目标

  • 平台控制面可用性:99.9%
  • 数据面发号可用性:99.99%
  • 单实例本地发号不依赖控制面在线

4.2 性能目标

  • 单节点发号延迟:P99 < 1ms
  • 单节点吞吐:按 bit 位设计,理论可达 100 万级 ID/s
  • worker id 启动申请:通常 < 1s

4.3 一致性目标

  • 同一 (env, region, dc, workerId) 任一时刻最多只能有一个活跃持有者
  • session 不确定、时钟回拨超阈值、checkpoint 损坏时,宁可拒绝发号,不允许冒险继续

5. ID 结构设计

推荐采用 64 bit 有符号 long,最高位固定为 0。

5.1 推荐 bit 分配

  • 1 bit:sign,固定 0
  • 41 bits:毫秒时间戳
  • 2 bits:region
  • 3 bits:dc
  • 7 bits:worker id
  • 10 bits:sequence

5.2 含义

  • 41 bits 时间戳:约可用 69 年
  • 2 bits region:最多 4 个大区
  • 3 bits dc:每个大区最多 8 个机房/单元
  • 7 bits worker:每个 (env, region, dc, app/pool) 最多 128 个并发 worker
  • 10 bits sequence:单 worker 单毫秒最多 1024 个号

5.3 设计理由

  • 通过 region + dc 保证跨机房隔离
  • worker id 只在本 dc 池内唯一
  • sequence 提供高并发本地发号能力
  • bit 位分配可通过控制面做版本化管理,支持未来切换 schema v2

Snowflake 的原始思想就是用 timestamp + worker number + sequence number 组合 64 位趋势递增 ID;这里是在企业环境下对 worker 维度进一步标准化。


6. 总体架构

flowchart TB
    subgraph ControlPlane["控制面"]
        Portal["接入门户 / 申请审批"]
        Meta["元数据服务"]
        Policy["池规划 / 策略 / 配额"]
        Token["身份映射 / ACL Token 服务"]
        Ops["运维控制台"]
        Audit["审计与报表"]
        DB[(MySQL / PostgreSQL)]
    end

    subgraph DataPlane["数据面"]
        SDK["ID SDK / Sidecar"]
        Agent["本地 Agent 接入适配"]
        Generator["Snowflake Generator"]
        Checkpoint["本地 Checkpoint 文件"]
    end

    subgraph Consul["Consul"]
        Session["Session"]
        KV["KV Lease Keys"]
        Watch["Blocking Query Watch"]
        ACL["ACL / Auth Method"]
    end

    Portal --> Meta
    Meta --> DB
    Policy --> DB
    Token --> DB
    Ops --> DB
    Audit --> DB

    SDK --> Agent
    SDK --> Session
    SDK --> KV
    SDK --> Watch
    SDK --> Checkpoint
    Generator --> SDK

    Token --> ACL
    SDK --> ACL
    Ops --> Consul

6.1 分层职责

控制面

负责:

  • 应用注册
  • worker 池配置
  • 配额与限流策略
  • 身份映射与 ACL 策略
  • 接入审批
  • 运维、告警、审计

数据面

负责:

  • 启动时申请 worker id
  • 建立 session
  • 周期 renew
  • 本地发号
  • checkpoint 持久化
  • 安全下线

Consul

负责:

  • 持有期租约状态
  • 自动失效释放
  • 锁竞争
  • 长轮询 watch

Consul session 在失效场景下会被 invalidated;若创建时指定 Behavior=release,对应锁会被释放;KV 的 acquire/release 正是配合 session 构建分布式锁的标准方式。


7. 逻辑对象模型

7.1 管理面对象

Application

业务接入主体,最小治理单元。 字段:

  • app_code
  • app_name
  • owner_team
  • owner_user
  • biz_domain
  • env
  • region_scope
  • dc_scope
  • deploy_mode
  • id_schema_version
  • status

Worker Pool

worker 号段池。 字段:

  • pool_code
  • env
  • region
  • dc
  • worker_start
  • worker_end
  • pool_mode(dedicated/shared)
  • status

App-Pool Binding

应用与池的绑定关系。 字段:

  • app_code
  • pool_code
  • max_replicas
  • peak_id_qps
  • quota_worker_count

Access Request

接入申请单。 字段:

  • request_no
  • app_code
  • requested_by
  • requested_worker_count
  • deploy_desc
  • risk_level
  • status

Lease Runtime Snapshot

控制面缓存的运行态快照。 字段:

  • env/region/dc/app_code/worker_id
  • instance_id
  • boot_id
  • session_id
  • lease_state
  • last_seen_at

8. Consul Key 与 Session 设计

8.1 KV 路径规范

/idgen/leases/{env}/{region}/{dc}/{appCode}/{workerId}

value 推荐 JSON:

{
  "appCode": "order-center",
  "instanceId": "pod-7f9d8c6fcb-2xk9m",
  "bootId": "4fd5f7f2-6d6f-4d80-9d26-cc0f63e91cc1",
  "sessionId": "adf4238a-882b-9ddc-4a9d-5b6758e4159e",
  "node": "k8s-node-a12",
  "issueTime": "2026-04-09T21:30:00Z",
  "schemaVersion": "v1",
  "sdkVersion": "1.0.0"
}

8.2 Session 创建参数

推荐:

  • Behavior = release
  • TTL = 20s
  • LockDelay = 5s
  • NodeChecks = ["serfHealth"]

依据:

  • Session 支持 node、health checks、behavior、TTL、lock-delay。
  • TTL 官方约束为 10s 到 86400s,且建议用尽可能低的实用 TTL;锁被强制过期后,回收可能最多延迟到约 2 倍 TTL
  • LockDelay 默认示例为 15s;HTTP API 要求它必须大于 0。
  • 推荐保留 serfHealth,官方文档对自定义 checks 时也建议包含默认的 serfHealth

8.3 Renew 策略

  • renew interval = min(returnedTTL / 3, 6s)
  • 连续 3 次 renew 失败,进入 READ_ONLY 并停止发号
  • SDK 必须接受 Consul 可能返回高于创建值的 TTL,因为这表示服务端负载高,要求客户端降低 renew 频率。

8.4 Watch 策略

控制面和数据面都支持按 prefix 长轮询:

  • 接口返回 X-Consul-Index
  • 下次请求带 index=<lastIndex> 实现 blocking query

这是 Consul 官方定义的标准长轮询模式。


9. ACL 与身份设计

9.1 基本原则

  • 业务实例不持有管理 token
  • 运行时 token 最小权限化
  • token 尽量短期化
  • 权限范围限定到自身租约 prefix

9.2 推荐实现

  • K8s:ServiceAccount / workload identity
  • VM:节点证书 / 实例身份
  • 通过控制面或 Consul auth method 登录后,换取本地 datacenter 可用的 ACL token

Consul auth method 的作用就是对接外部可信身份源,并授权在本地 datacenter 创建可用 ACL token。

9.3 最小权限策略示例

仅允许:

session_prefix "" {
  policy = "write"
}
 
key_prefix "idgen/leases/prod/cn-east-1/sh-a/order-center/" {
  policy = "write"
}

不允许:

  • 访问别的应用租约 prefix
  • 访问任意全局 KV
  • 创建非本应用 session 之外的管理操作

9.4 企业隔离增强

如果使用 Consul Enterprise,可按 namespace / partition 进一步隔离租约范围。Session 和 KV API 均支持 ns,KV 还支持 partition


10. 生命周期设计

10.1 生命周期状态机

stateDiagram-v2
    [*] --> INIT
    INIT --> AUTHENTICATING
    AUTHENTICATING --> BOOTSTRAP_LOADED
    BOOTSTRAP_LOADED --> SESSION_CREATED
    SESSION_CREATED --> RECLAIMING
    RECLAIMING --> ACTIVE: reclaim success
    RECLAIMING --> ACQUIRING: reclaim fail
    ACQUIRING --> ACTIVE: acquire success
    ACQUIRING --> FAILED: pool exhausted / auth fail
    ACTIVE --> RENEWING
    RENEWING --> ACTIVE: renew ok
    RENEWING --> DEGRADED: renew failed
    DEGRADED --> ACTIVE: recovered
    DEGRADED --> READ_ONLY: retry exhausted
    ACTIVE --> DRAINING: shutdown
    DRAINING --> RELEASED
    RELEASED --> [*]
    FAILED --> [*]
    READ_ONLY --> [*]

10.2 启动到发号的时序

sequenceDiagram
    participant App as 业务应用
    participant SDK as ID SDK/Sidecar
    participant CP as 控制面
    participant Consul as Consul
    participant File as Checkpoint

    App->>SDK: init()
    SDK->>CP: 身份认证/拉取bootstrap
    CP-->>SDK: appCode,pool,region,dc,policy,epoch
    SDK->>File: 读取上次checkpoint
    SDK->>Consul: create session
    Consul-->>SDK: sessionId

    alt 有历史workerId
        SDK->>Consul: acquire 历史workerId
        Consul-->>SDK: true/false
    end

    alt 重认领失败
        loop 扫描worker池
            SDK->>Consul: acquire(workerId)
            Consul-->>SDK: true/false
        end
    end

    SDK-->>App: workerId ready
    App->>SDK: nextId()
    SDK-->>App: id
    par 后台线程
        SDK->>Consul: session renew
    and 后台线程
        SDK->>File: flush maxTimestampSeen
    end

11. 详细生命周期流程

11.1 启动前

控制面必须已完成:

  • 应用注册
  • 池分配
  • ACL 策略准备
  • 接入审批通过
  • SDK 配置生成

11.2 启动

  1. 进程启动
  2. SDK/Sidecar 完成身份认证
  3. 拉取 bootstrap 配置
  4. 读取本地 checkpoint
  5. 创建 Consul session
  6. 优先重认领历史 worker id
  7. 失败则扫描可用池
  8. 成功后进入 ACTIVE,开放 nextId()

11.3 使用期

  • 所有 ID 纯本地生成
  • 后台定时 renew session
  • 后台定时 flush maxTimestampSeen
  • 定时向控制面上报心跳与 runtime snapshot

11.4 优雅下线

  1. 进入 DRAINING
  2. 停止对外提供 nextId()
  3. flush checkpoint
  4. release KV lock
  5. destroy session
  6. 进程退出

Consul 的 session destroy 是幂等的;不存在或已过期时也返回 200 成功语义。

11.5 异常退出

如果实例被 kill、Pod 丢失、节点失联:

  • 不走优雅 release
  • 依赖 TTL 到期 + session invalidation 自动释放
  • 由于官方说明锁回收可能延迟到约 2 倍 TTL,不能把释放视为“立即可重用”

11.6 回收与再分配

worker id 释放后进入 COOLDOWN

cooldown = max(2 * TTL + LockDelay, 最大时钟偏差预算, checkpoint flush 周期)

推荐默认:

  • TTL = 20s
  • LockDelay = 5s
  • 时钟偏差预算 = 3s
  • flush = 1s

cooldown >= 45s


12. 本地 Snowflake Generator 设计

12.1 核心内存状态

  • workerId
  • regionId
  • dcId
  • lastTimestamp
  • sequence
  • maxTimestampSeen
  • state(ACTIVE / READ_ONLY / DRAINING)

12.2 nextId() 逻辑

1. 检查 state 必须为 ACTIVE
2. now = currentTimeMillis()
3. 若 now < lastTimestamp:
      diff = lastTimestamp - now
      - diff <= 2s: 阻塞等待追平
      - diff > 2s: 拒绝发号并告警
4. 若 now == lastTimestamp:
      sequence++
      若 sequence 溢出,等待下一毫秒
5. 若 now > lastTimestamp:
      sequence = 0
6. 组装 ID
7. 更新 lastTimestamp 和 maxTimestampSeen

12.3 强制规则

  • 绝不允许 session 不确定时继续发号
  • 绝不允许 worker id 丢失后降级为“随机 worker id”
  • 绝不允许回拨超阈值后继续发号

13. Checkpoint 设计

13.1 本地文件内容

{
  "version": 1,
  "appCode": "order-center",
  "env": "prod",
  "region": "cn-east-1",
  "dc": "sh-a",
  "workerId": 37,
  "bootId": "4fd5f7f2-6d6f-4d80-9d26-cc0f63e91cc1",
  "lastTimestamp": 1744230005123,
  "maxTimestampSeen": 1744230005123,
  "updatedAt": "2026-04-09T21:40:05Z"
}

13.2 flush 策略

  • 每 1 秒定时刷盘
  • 或每生成 10000 个 ID 强制刷盘
  • 进程退出前再刷一次

13.3 作用

  • 启动时优先重认领原 worker id
  • 提供时钟安全下界
  • 降低短时控制面不可用影响

14. 控制面详细设计

14.1 模块划分

A. 接入门户

提供:

  • 应用申请
  • 审批流
  • 接入文档
  • 环境开通
  • 号段查看

B. 元数据服务

提供:

  • app 注册
  • pool 管理
  • app-pool 绑定
  • schema 管理
  • 发布配置查询

C. 策略中心

提供:

  • worker 池规划
  • 配额控制
  • 环境限制
  • state policy
  • 熔断/冻结策略

D. Token 服务

提供:

  • 身份认证
  • ACL policy 组装
  • 短期 token 获取
  • token 轮换

E. 运维控制台

提供:

  • 活跃租约查询
  • 池使用率
  • 手工摘除
  • 冻结应用
  • 强制回收
  • 审计检索

F. 审计服务

记录:

  • 谁申请
  • 谁审批
  • 谁改池
  • 谁强制释放
  • 何时哪个实例持有过哪个 worker id

15. 管理面数据库设计

15.1 idgen_app

create table idgen_app (
  id bigint primary key auto_increment,
  app_code varchar(64) not null unique,
  app_name varchar(128) not null,
  owner_team varchar(128) not null,
  owner_user varchar(64) not null,
  biz_domain varchar(128),
  env varchar(32) not null,
  deploy_mode varchar(32) not null,
  region_scope varchar(256),
  dc_scope varchar(256),
  schema_version varchar(32) not null,
  status varchar(32) not null,
  created_at datetime not null,
  updated_at datetime not null
);

15.2 idgen_pool

create table idgen_pool (
  id bigint primary key auto_increment,
  pool_code varchar(64) not null unique,
  env varchar(32) not null,
  region varchar(32) not null,
  dc varchar(32) not null,
  worker_start int not null,
  worker_end int not null,
  pool_mode varchar(32) not null,
  status varchar(32) not null,
  created_at datetime not null,
  updated_at datetime not null
);

15.3 idgen_app_pool_binding

create table idgen_app_pool_binding (
  id bigint primary key auto_increment,
  app_code varchar(64) not null,
  pool_code varchar(64) not null,
  quota_worker_count int not null,
  max_replicas int not null,
  peak_id_qps bigint,
  status varchar(32) not null,
  created_at datetime not null,
  updated_at datetime not null,
  unique key uk_app_pool(app_code, pool_code)
);

15.4 idgen_access_request

create table idgen_access_request (
  id bigint primary key auto_increment,
  request_no varchar(64) not null unique,
  app_code varchar(64) not null,
  request_type varchar(32) not null,
  requested_by varchar(64) not null,
  requested_worker_count int,
  reason text,
  status varchar(32) not null,
  approved_by varchar(64),
  created_at datetime not null,
  updated_at datetime not null
);

15.5 idgen_audit_log

create table idgen_audit_log (
  id bigint primary key auto_increment,
  event_type varchar(64) not null,
  app_code varchar(64),
  operator_user varchar(64),
  target varchar(256),
  detail_json json,
  created_at datetime not null
);

15.6 idgen_runtime_lease_snapshot

create table idgen_runtime_lease_snapshot (
  id bigint primary key auto_increment,
  env varchar(32) not null,
  region varchar(32) not null,
  dc varchar(32) not null,
  app_code varchar(64) not null,
  worker_id int not null,
  instance_id varchar(128) not null,
  boot_id varchar(64) not null,
  session_id varchar(64) not null,
  lease_state varchar(32) not null,
  last_seen_at datetime not null,
  unique key uk_lease(env, region, dc, app_code, worker_id)
);

16. 控制面 API 设计

16.1 管理面 API

应用接入

  • POST /api/idgen/apps
  • GET /api/idgen/apps/{appCode}
  • POST /api/idgen/access-requests
  • POST /api/idgen/access-requests/{requestNo}/approve
  • POST /api/idgen/access-requests/{requestNo}/reject

池管理

  • POST /api/idgen/pools
  • POST /api/idgen/pools/{poolCode}/bind-app
  • POST /api/idgen/pools/{poolCode}/expand
  • POST /api/idgen/pools/{poolCode}/freeze

运维

  • GET /api/idgen/runtime/leases
  • POST /api/idgen/runtime/leases/{leaseId}/force-release
  • POST /api/idgen/apps/{appCode}/set-readonly
  • GET /api/idgen/metrics/pool-usage

16.2 数据面 bootstrap API

  • POST /api/idgen/bootstrap

    • 入参:应用身份、env、region、dc、instanceId
    • 出参:schema、epoch、pool ranges、clock policy、checkpoint policy、token 获取信息
  • POST /api/idgen/runtime/heartbeat

    • 入参:sessionId、workerId、instanceId、status
    • 用途:控制面视图补齐,不参与热路径

17. 业务接入标准与流程

17.1 准入标准

业务方接入生产前必须满足:

基础要求

  • 已完成应用注册
  • 已指定 owner 团队与 owner 人
  • 已明确部署模式与环境边界
  • 已明确最大副本数和峰值 ID QPS

技术要求

  • 使用平台标准 SDK / Sidecar
  • 接入优雅停机 hook
  • 节点启用 NTP / Chrony
  • 具备本地持久化目录
  • 已接平台监控

验证要求

  • 并发唯一性测试通过
  • 异常 kill 测试通过
  • 时钟回拨测试通过
  • Consul 短时抖动测试通过
  • 池耗尽测试通过

17.2 标准流程

flowchart LR
    A[业务提交接入申请] --> B[架构/平台评审]
    B --> C[池规划与配额审批]
    C --> D[控制面开通应用与池]
    D --> E[下发SDK配置与ACL策略]
    E --> F[业务联调]
    F --> G[验收测试]
    G --> H[生产启用]

17.3 申请单字段

  • 应用编码
  • 应用名称
  • 所属业务域
  • owner 团队 / owner 人
  • env / region / dc
  • 部署模式
  • 常驻副本数 / 峰值副本数
  • 峰值 ID QPS
  • 是否独享池
  • 上线窗口
  • 风险说明

18. 池规划策略

18.1 Dedicated Pool

应用独享固定 worker 范围。

适合:

  • 核心交易域
  • 订单、支付、库存等高价值系统
  • 流量稳定、容量预估明确

18.2 Shared Pool

多个低频应用共享池,但由控制面限制可用范围和副本上限。

适合:

  • 中后台系统
  • 工具型应用
  • 非核心链路

18.3 容量估算公式

所需 worker 数 = 峰值副本数 × 安全系数
安全系数建议 = 1.5 ~ 2

例如:

  • 峰值副本数 30
  • 安全系数 2

则建议最少 60 个 worker


19. 监控与告警设计

19.1 SDK 指标

  • idgen_next_id_qps
  • idgen_next_id_latency_ms
  • idgen_clock_rollback_count
  • idgen_sequence_exhaust_count
  • idgen_session_renew_fail_count
  • idgen_state
  • idgen_checkpoint_flush_fail_count

19.2 控制面指标

  • idgen_pool_usage_ratio
  • idgen_active_leases
  • idgen_bootstrap_fail_count
  • idgen_force_release_count
  • idgen_access_request_pending_count

19.3 告警规则

  • 池使用率 > 70%:预警
  • 池使用率 > 85%:告警
  • renew 连续失败 >= 3:告警
  • 时钟回拨 > 2s:严重告警
  • 5 分钟内多次 acquire/release 抖动:告警
  • checkpoint 写失败持续 1 分钟:告警

20. 异常场景处理矩阵

20.1 Consul 短时不可用

  • 已持有 worker id 的实例继续本地发号,前提是 session 仍在可控状态窗口内
  • renew 连续失败达到阈值,切换 READ_ONLY,停止发号

20.2 Session renew 失败

  • 首次失败:重试
  • 连续 3 次失败:停止发号
  • 不允许“乐观继续”

20.3 worker 池耗尽

  • 启动失败
  • 控制面发告警
  • 不允许随机降级

20.4 时钟回拨

  • <= 2s:阻塞等待
  • > 2s:拒绝发号 + 告警

20.5 checkpoint 丢失

  • 若仍可从 Consul 安全获取新 worker id,则允许启动
  • 但启动后立即标记风险事件
  • 若同时无法确认时钟安全边界,则拒绝启动

20.6 锁被误释放 / 误操作

由于 Consul 锁是 advisory,未持锁客户端并非被协议强制阻止,因此 ACL 限权、SDK 收口、运维操作审计必须作为平台强约束。


21. 安全设计

21.1 身份

  • 运行时身份来自 K8s SA / VM 实例身份
  • 控制面映射到 appCode 和环境范围

21.2 授权

  • 使用最小权限 ACL
  • 仅允许本 app prefix
  • 禁止默认全局写 token

21.3 审计

  • 所有强制回收、池扩容、冻结操作都必须记录审计日志
  • 运维 API 必须鉴权并留痕

21.4 配置防错

  • env/region/dc/appCode 不允许由业务随意覆盖
  • 这些值应以控制面 bootstrap 下发为准

22. 推荐默认参数

Session

  • TTL = 20s
  • renew interval = 6s
  • LockDelay = 5s
  • Behavior = release

Generator

  • rollback wait threshold = 2s
  • checkpoint flush interval = 1s
  • renew fail threshold = 3
  • cooldown = 45s

Control Plane

  • pool usage warn threshold = 70%
  • pool usage critical threshold = 85%

这些默认值基于 Consul session/TTL/lock-delay 官方约束和租约回收特性进行了保守选型。


23. 实施分期建议

Phase 1:MVP

  • 控制面应用注册
  • Dedicated Pool
  • Java SDK
  • Consul Session + KV Lock
  • 基础运维台

Phase 2:增强

  • Shared Pool
  • K8s 身份映射自动化
  • 自动审批规则
  • 审计报表
  • 灰度发布联动

Phase 3:企业级

  • 多语言 SDK
  • Sidecar 模式
  • Consul Enterprise namespace/partition 支持
  • 池容量预测
  • 自动扩容建议

24. 最终结论

本方案的核心结论是:

  1. Snowflake 负责本地高性能发号
  2. Consul 负责 worker id 的运行时租约
  3. 控制面负责申请、审批、配额、审计、运维
  4. 业务只接 SDK / Sidecar,不直接碰 Consul 细节
  5. 出现 session 不确定、时钟回拨、池耗尽时,平台必须 fail closed,而不是冒险继续发号

这套设计与 Consul 官方提供的 session、TTL、KV acquire/release、blocking query、auth method/ACL 能力是一致的,也符合 Snowflake “启动选 worker、本地无协调发号”的原始思路。

《控制面 API + MySQL 表结构详细版》、Java SDK 详细设计与伪代码、或《运维与异常处置 Runbook》 。

我先给结论:DB 替代 Consul 是可行的,但“由业务系统自己提供那张协调表”不适合作为企业级默认方案。 更稳妥的是:平台拥有自己的协调库/Schema,业务方只申请接入,不拥有运行时租约表。

一、先给结论

1)用 DB 替代 Consul:可行

对唯一 ID 平台来说,worker id 的核心诉求是唯一分配、持有、续约、回收。这件事不一定非要 Consul 做,DB 完全可以做成“租约表 + 心跳 + 超时回收” 的协调方案。 数据库层面,PostgreSQL 和 MySQL 都支持 SELECT ... FOR UPDATE SKIP LOCKED 这类行级锁定读取,适合做“抢占空闲槽位”的并发分配;其中 MySQL 官方也明确说 SKIP LOCKED 适合 queue-like table 这类避免竞争的场景。

2)“协调表由业务方自己的库来提供”:不建议作为企业默认方案

这不是技术上不能做,而是治理边界不合理

  • 平台级协调状态放在业务库,会把 ID 平台可用性 绑到 业务库维护窗口、变更节奏、权限边界、容灾策略 上。
  • 多个业务共用时,平台会被迫依赖某个业务团队的库治理。
  • 未来做审计、配额、冻结、强制回收、跨应用可视化时,业务库不是合适的权威边界。

所以更合理的口径是:

可以用 DB 替代 Consul,但这个 DB 应该由“唯一 ID 平台”拥有,而不是由某个业务系统拥有。

3)什么时候业务方自带表可以接受

只在下面场景我才会放行:

  • 单一 BU / 单一业务线自建 ID 服务
  • 只服务本业务,不打算平台化复用
  • 业务团队同时拥有应用、DB、运维、容灾责任
  • 不要求统一审批、统一审计、统一池规划

如果你现在要做的是企业级统一 ID 平台,那建议直接按平台自有协调库设计。


二、推荐方案:DB 版唯一 ID 平台总体架构

建议采用:

  • 控制面:Portal + API + 审批 + 配额 + 审计
  • 数据面:ID Generator SDK / Sidecar
  • 协调存储:平台自有 RDBMS(MySQL / PostgreSQL)
  • 本地状态:checkpoint 文件(worker_id / max_timestamp / fencing_token)
flowchart TB
    subgraph CP["控制面"]
        Portal["接入门户 / 审批"]
        Meta["元数据服务"]
        Policy["池规划 / 配额 / 策略"]
        Ops["运维控制台"]
        Audit["审计服务"]
        DB[(平台协调库)]
    end

    subgraph DP["数据面"]
        SDK["ID SDK / Sidecar"]
        GEN["Snowflake Generator"]
        CKPT["本地 Checkpoint"]
    end

    Portal --> Meta
    Meta --> DB
    Policy --> DB
    Ops --> DB
    Audit --> DB

    SDK --> DB
    SDK --> CKPT
    GEN --> SDK

三、DB 版方案的核心设计思想

1)不要用“固定 worker_id 配置表”,要用“租约表”

如果只是把 worker_id=37 写死到配置表里,那不是动态协调,只是静态配置。

企业级可用的做法应该是:

  • 预先定义一组可分配槽位 slot
  • 实例启动时去抢占一个未被持有或已过期的 slot
  • 持有期间通过 heartbeat 续租
  • 异常退出后靠 lease timeout 自动回收
  • 回收后再被其他实例重用

本质上,DB 替代的是 Consul 的 lease/session 机制。


2)不要优先依赖 DB session lock,优先用“表中租约”

数据库确实也有 session 级锁:

  • PostgreSQL 有 advisory locks,并且官方文档说明它们是 local to each database
  • MySQL 的 GET_LOCK() 在 session 终止时自动释放,而且不会因为事务 commit/rollback 自动释放

但我不建议把它们作为主方案,原因是:

  • 它们强绑定数据库连接/session
  • 遇到连接池、驱动重连、主从切换、代理层时,行为不如“显式租约表”可观测
  • 跨 MySQL / PostgreSQL 可移植性一般
  • 运维台很难直接看出“谁持有、何时过期、为什么过期”

所以我的建议是:

主方案:租约表 + 行锁并发控制 备选方案:advisory lock / GET_LOCK,只用于小规模内部服务


四、详细方案设计


4.1 控制面与数据面职责

控制面负责

  • 应用接入申请
  • worker 池规划
  • 配额审批
  • 环境/机房隔离
  • 元数据管理
  • 运维、审计、报表

数据面负责

  • 启动时申请 worker id
  • 周期 heartbeat/renew
  • 使用本地 snowflake 发号
  • 保存 checkpoint
  • 下线释放

DB 负责

  • 当前租约真相
  • 池定义
  • 应用-池绑定
  • 审批与审计记录

4.2 逻辑对象模型

建议至少有 6 张核心表。

1. idgen_app

应用注册表。

2. idgen_worker_pool

worker 池定义表。

3. idgen_app_pool_binding

应用与池绑定表。

4. idgen_worker_slot

槽位表,每一行代表一个可分配 worker id。

5. idgen_access_request

接入申请单。

6. idgen_audit_log

审计日志。


五、最关键的一张表:idgen_worker_slot

这是 DB 版方案的核心。

5.1 表结构建议

create table idgen_worker_slot (
  id bigint primary key auto_increment,
  env varchar(32) not null,
  region varchar(32) not null,
  dc varchar(32) not null,
  app_code varchar(64) not null,
  worker_id int not null,
 
  slot_status varchar(16) not null default 'FREE',
  -- FREE / LEASED / FROZEN / DISABLED
 
  holder_instance_id varchar(128),
  holder_boot_id varchar(64),
  holder_host varchar(128),
 
  lease_token bigint not null default 0,
  lease_version bigint not null default 0,
 
  lease_acquired_at datetime(3),
  lease_last_heartbeat_at datetime(3),
  lease_expires_at datetime(3),
 
  last_max_timestamp bigint,
  checkpoint_updated_at datetime(3),
 
  created_at datetime(3) not null,
  updated_at datetime(3) not null,
 
  unique key uk_slot (env, region, dc, app_code, worker_id),
  key idx_lease_scan (env, region, dc, app_code, slot_status, lease_expires_at)
);

5.2 关键字段解释

slot_status

  • FREE:空闲可抢占
  • LEASED:当前被占用
  • FROZEN:平台冻结,禁止分配
  • DISABLED:永久停用

lease_token

强烈建议保留。 每次成功获取租约时自增,作为 fencing token

作用:

  • 防止旧实例“自认为还活着”继续写入 checkpoint 或上报状态
  • 控制面可以判断谁是“新持有者”
  • 与本地 checkpoint 联合校验,避免陈旧实例误操作

last_max_timestamp

保存这个槽位最近一次持有者上报的最大时间戳,用来做回收后的安全复用判断


六、worker id 全生命周期设计


6.1 启动前准备

控制面提前完成:

  • 注册应用 app_code
  • 分配 env/region/dc
  • 绑定 worker 池,例如 0~63
  • 配置最大副本数
  • 下发 SDK bootstrap 配置

6.2 启动:优先重认领,再抢新槽位

步骤 1:读取本地 checkpoint

{
  "appCode": "order-center",
  "env": "prod",
  "region": "cn-east-1",
  "dc": "sh-a",
  "workerId": 37,
  "leaseToken": 1052,
  "maxTimestampSeen": 1744230005123
}

步骤 2:优先尝试重认领原 worker_id

思路是:

update idgen_worker_slot
set
  slot_status = 'LEASED',
  holder_instance_id = ?,
  holder_boot_id = ?,
  holder_host = ?,
  lease_token = lease_token + 1,
  lease_version = lease_version + 1,
  lease_acquired_at = now(3),
  lease_last_heartbeat_at = now(3),
  lease_expires_at = date_add(now(3), interval ? second),
  updated_at = now(3)
where
  env = ?
  and region = ?
  and dc = ?
  and app_code = ?
  and worker_id = ?
  and slot_status in ('FREE', 'LEASED')
  and (
    holder_instance_id is null
    or lease_expires_at < now(3)
    or holder_boot_id = ?
  );

如果更新成功,说明重认领成功。

为什么优先重认领

  • 降低 worker id 抖动
  • 缩短重启恢复时间
  • 对小池子更友好

6.3 若重认领失败,再扫描抢占空闲槽位

这里推荐两种实现。

方案 A:SELECT ... FOR UPDATE SKIP LOCKED

这是我更推荐的。

PostgreSQL 和 MySQL 都支持 FOR UPDATE SKIP LOCKED;PostgreSQL 文档明确说它会跳过不能立刻加锁的行,MySQL 文档也明确说 SKIP LOCKED 不等待行锁,适用于 queue-like table。

示意 SQL:

begin;
 
select id, worker_id
from idgen_worker_slot
where env = ?
  and region = ?
  and dc = ?
  and app_code = ?
  and slot_status = 'FREE'
for update skip locked
limit 1;
 
-- 拿到行后更新为 LEASED
update idgen_worker_slot
set
  slot_status = 'LEASED',
  holder_instance_id = ?,
  holder_boot_id = ?,
  holder_host = ?,
  lease_token = lease_token + 1,
  lease_version = lease_version + 1,
  lease_acquired_at = now(3),
  lease_last_heartbeat_at = now(3),
  lease_expires_at = date_add(now(3), interval ? second),
  updated_at = now(3)
where id = ?;
 
commit;

方案 B:单条条件更新抢占

不依赖 SKIP LOCKED,但实现更绕,适合兼容性要求高时使用。

思路:

  • 先查候选
  • 再带条件 update
  • 依靠 affected rows = 1 判断是否抢占成功
  • 失败继续重试

6.4 持有期:本地发号 + 周期心跳

一旦拿到 worker_id,发号逻辑就完全在本地内存执行。 DB 不进入热路径。

本地状态

  • workerId
  • leaseToken
  • lastTimestamp
  • sequence
  • maxTimestampSeen
  • state

heartbeat 更新

每 5 秒或 1/3 lease 周期执行一次:

update idgen_worker_slot
set
  lease_last_heartbeat_at = now(3),
  lease_expires_at = date_add(now(3), interval ? second),
  last_max_timestamp = ?,
  checkpoint_updated_at = now(3),
  updated_at = now(3)
where
  env = ?
  and region = ?
  and dc = ?
  and app_code = ?
  and worker_id = ?
  and lease_token = ?
  and slot_status = 'LEASED';

这里用 lease_token 做条件,非常关键。 如果更新返回 0 行,说明:

  • 这个槽位已不属于你
  • 或者被别的实例抢占了
  • 当前实例必须立刻进入 READ_ONLY / STOP 状态,停止发号

6.5 下线:优雅释放

优雅停机流程:

  1. 停止接受 nextId()
  2. flush 本地 checkpoint
  3. 更新 DB 最后一次 last_max_timestamp
  4. 释放槽位
update idgen_worker_slot
set
  slot_status = 'FREE',
  holder_instance_id = null,
  holder_boot_id = null,
  holder_host = null,
  lease_last_heartbeat_at = null,
  lease_expires_at = null,
  updated_at = now(3)
where
  env = ?
  and region = ?
  and dc = ?
  and app_code = ?
  and worker_id = ?
  and lease_token = ?;

6.6 异常退出:TTL 超时自动回收

如果实例崩溃,靠下面逻辑回收:

  • 没有 heartbeat
  • lease_expires_at < now()
  • 新实例可重新抢占

建议加冷却窗口

不要一过期就立刻复用。 建议规则:

cooldown = max(2 * heartbeat_interval, clock_skew_budget, checkpoint_flush_interval)

更稳妥一点可以直接定:

  • lease_ttl = 20s
  • heartbeat = 5s
  • cooldown = 30~45s

然后抢占条件改成:

lease_expires_at < now(3) - interval 30 second

七、控制面设计


7.1 为什么 DB 版也必须有控制面

因为只靠 worker_slot 表,只解决了谁拿到 worker id,没解决:

  • 谁能接入
  • 需要多少号段
  • 是否独享池
  • 审批与责任归属
  • 审计与可视化
  • 强制冻结和运维操作

所以 DB 替代的是 Consul 协调层,不是控制面。


7.2 控制面核心能力

A. 应用注册

  • app_code
  • owner 团队 / owner 人
  • 环境范围
  • 部署方式
  • 峰值副本数
  • 峰值 ID QPS

B. worker 池规划

  • dedicated pool:独享池
  • shared pool:共享池
  • 扩容 / 缩容
  • 冻结 / 停用

C. 接入审批

  • 是否已接入标准 SDK
  • 是否具备 NTP 校时
  • 是否有 graceful shutdown
  • 是否有本地 checkpoint 目录
  • 是否通过压测和回拨测试

D. 运维台

  • 当前活跃租约
  • 池使用率
  • 某应用占用了哪些 worker
  • 强制摘除
  • 池扩容
  • 应用冻结

八、业务方申请接入的标准与流程


8.1 标准

业务方申请的是:

接入统一唯一 ID 平台

不是申请一个固定 worker id。

业务方必须提供:

  • 应用编码
  • 应用名称
  • owner 团队 / owner 人
  • 所属域
  • 环境 / region / dc
  • 常驻副本数
  • 峰值副本数
  • 峰值 ID QPS
  • 是否独享池
  • 技术栈
  • 部署模式

8.2 流程

flowchart LR
    A[业务提交接入申请] --> B[平台评审容量与架构]
    B --> C[创建 app 与池绑定]
    C --> D[初始化 worker_slot]
    D --> E[下发 SDK 配置]
    E --> F[联调与验收]
    F --> G[生产启用]

九、推荐的数据表清单

除了 idgen_worker_slot,建议最少再有下面 4 张表。

9.1 idgen_app

create table idgen_app (
  id bigint primary key auto_increment,
  app_code varchar(64) not null unique,
  app_name varchar(128) not null,
  owner_team varchar(128) not null,
  owner_user varchar(64) not null,
  env varchar(32) not null,
  region_scope varchar(256),
  dc_scope varchar(256),
  max_replicas int not null,
  peak_id_qps bigint,
  status varchar(32) not null,
  created_at datetime(3) not null,
  updated_at datetime(3) not null
);

9.2 idgen_worker_pool

create table idgen_worker_pool (
  id bigint primary key auto_increment,
  pool_code varchar(64) not null unique,
  env varchar(32) not null,
  region varchar(32) not null,
  dc varchar(32) not null,
  worker_start int not null,
  worker_end int not null,
  pool_mode varchar(16) not null,
  status varchar(16) not null,
  created_at datetime(3) not null,
  updated_at datetime(3) not null
);

9.3 idgen_app_pool_binding

create table idgen_app_pool_binding (
  id bigint primary key auto_increment,
  app_code varchar(64) not null,
  pool_code varchar(64) not null,
  quota_worker_count int not null,
  status varchar(16) not null,
  created_at datetime(3) not null,
  updated_at datetime(3) not null,
  unique key uk_app_pool(app_code, pool_code)
);

9.4 idgen_audit_log

create table idgen_audit_log (
  id bigint primary key auto_increment,
  event_type varchar(64) not null,
  app_code varchar(64),
  operator_user varchar(64),
  detail_json json,
  created_at datetime(3) not null
);

十、为什么我不建议“业务方提供表”

这里我直接给你架构结论。

10.1 不合理点

1)平台依赖业务库

ID 平台本来应该被业务依赖。 如果反过来让平台依赖业务库,就会形成倒置:

  • 业务库升级、迁移、限流、容灾切换
  • 会直接影响平台获取 worker id

2)责任边界不清

出了问题时,很难界定:

  • 是平台问题
  • 还是业务库慢 SQL / 索引 / 锁冲突
  • 还是业务 DBA 变更导致

3)多租户治理差

统一平台要做:

  • 审计
  • 冻结
  • 配额
  • 池规划
  • 全局视图

业务自带表会把这些能力切碎。

4)安全边界差

如果多个业务共用 ID 平台,但协调表落在某个业务库里,本质上不适合作为企业级公共基础设施的信任根。


10.2 唯一合理的例外

如果团队明确是:

  • 一个业务域自建
  • 不对全公司共享
  • 只有自己的应用使用
  • 那张表和那套库完全由该业务域自己运维

那就可以做成“业务内 ID 服务”。

但这就不是“企业统一平台”,而是“业务域内平台”。


十一、推荐的最终口径

方案选择建议

推荐级别 1:最推荐

平台自有 DB 协调库 + 租约表方案

适用:

  • 企业统一平台
  • 多业务接入
  • 需要审批、审计、配额、运维台
  • MySQL / PostgreSQL 基础设施成熟

推荐级别 2:可接受

业务域自有 DB + 域内 ID 服务

适用:

  • 单一大域
  • 不共享给全公司
  • 自己承担全部责任

不推荐

统一平台,但让业务系统自己的表充当平台协调表


十二、我的建议

如果你问我“该怎么落地”,我的明确建议是:

可以用 DB 替代 Consul,但要建设“平台自有协调库”,不要让业务方提供那张 worker 租约表。

并且方案上采用:

  • worker_slot 租约表
  • 启动优先重认领
  • FOR UPDATE SKIP LOCKED 抢占空闲槽位
  • lease_token 作为 fencing token
  • heartbeat 续租
  • checkpoint 落盘
  • 超时 + cooldown 回收
  • 控制面做审批、池规划、审计、运维

这样做,既保留了 Consul 方案里的完整生命周期管理能力,又贴合很多企业内部“DB 能力比 Consul 更成熟”的现实。

我先直接回答核心点:默认不要做“一个业务一张表”。更合理的是 平台统一表模型 + 用 app_code / env / region / dc 做逻辑隔离;只有在超大规模或强隔离场景下,才考虑按业务拆表。

{“open”:[{“ref_id”:“turn335429search0”},{“ref_id”:“turn335429search13”},{“ref_id”:“turn335429search1”}]}

默认不要做“一个业务一张表”。

建议你把它设计成:

  • 管理面:统一几张主数据表
  • 运行时:统一一张 worker_slot 租约表
  • 通过 app_code + env + region + dc 做逻辑隔离
  • 只有在极少数强隔离场景下,才拆成“一个业务一张运行时表”

原因很简单: 你这里管理的不是业务数据,而是平台级协调状态。这类数据量通常不大,但需要统一审计、统一配额、统一运维。如果按业务拆成很多张表,平台治理会变复杂很多。


推荐结论

方案 1:默认推荐

所有业务共用一套表模型。

至少分两类表:

管理面表

  • idgen_app:应用注册
  • idgen_worker_pool:worker 池定义
  • idgen_app_pool_binding:应用和池绑定
  • idgen_access_request:接入申请
  • idgen_audit_log:审计日志

运行时表

  • idgen_worker_slot统一租约表

也就是说,不是一个业务一张 worker_slot,而是所有业务共用同一张 worker_slot,靠维度字段隔离。


为什么默认不建议一个业务一张表

1. 运维和治理复杂度会明显上升

你会遇到这些问题:

  • 表数量越来越多
  • 每个业务都要单独建表、建索引、巡检
  • 平台做统一查询、统一告警、统一审计很麻烦
  • DDL 变更会变成批量运维问题

2. 运行时数据本来就不大

worker_slot 不是业务订单表,不会无限增长。 它本质上是一组可分配 worker 槽位。比如:

  • 100 个应用
  • 每个应用 64 个 worker
  • 总共也就 6400 行左右

这个量级通常完全没必要按业务拆表。

3. 平台能力会被业务边界切碎

统一平台最有价值的地方是:

  • 统一审批
  • 统一配额
  • 统一池规划
  • 统一运维台
  • 统一强制摘除 / 冻结

如果一业务一张表,这些能力实现会变得又散又重。


更合理的建表方式

建议你按**“统一逻辑表 + 条件唯一键 + 必要时分区”**来做。


一、核心运行时表怎么建

1)统一表:idgen_worker_slot

create table idgen_worker_slot (
  id bigint primary key auto_increment,
 
  env varchar(32) not null,
  region varchar(32) not null,
  dc varchar(32) not null,
  app_code varchar(64) not null,
  worker_id int not null,
 
  slot_status varchar(16) not null default 'FREE',
  -- FREE / LEASED / FROZEN / DISABLED
 
  holder_instance_id varchar(128),
  holder_boot_id varchar(64),
  holder_host varchar(128),
 
  lease_token bigint not null default 0,
  lease_version bigint not null default 0,
 
  lease_acquired_at datetime(3),
  lease_last_heartbeat_at datetime(3),
  lease_expires_at datetime(3),
 
  last_max_timestamp bigint,
  checkpoint_updated_at datetime(3),
 
  created_at datetime(3) not null,
  updated_at datetime(3) not null,
 
  unique key uk_slot (env, region, dc, app_code, worker_id),
  key idx_lease_scan (env, region, dc, app_code, slot_status, lease_expires_at)
);

这个表的含义

每一行就是一个可分配的 worker_id 槽位。 比如:

  • prod / cn-east / sh-a / order-center / worker_id=37

这就是一个槽位,不需要再单独建 order_center_worker_slot 表。


二、管理面表怎么建

2)应用注册表:idgen_app

create table idgen_app (
  id bigint primary key auto_increment,
  app_code varchar(64) not null unique,
  app_name varchar(128) not null,
  owner_team varchar(128) not null,
  owner_user varchar(64) not null,
  env_scope varchar(256),
  region_scope varchar(256),
  dc_scope varchar(256),
  max_replicas int not null,
  peak_id_qps bigint,
  status varchar(16) not null,
  created_at datetime(3) not null,
  updated_at datetime(3) not null
);

3)worker 池定义表:idgen_worker_pool

create table idgen_worker_pool (
  id bigint primary key auto_increment,
  pool_code varchar(64) not null unique,
  env varchar(32) not null,
  region varchar(32) not null,
  dc varchar(32) not null,
  worker_start int not null,
  worker_end int not null,
  pool_mode varchar(16) not null,
  -- dedicated / shared
  status varchar(16) not null,
  created_at datetime(3) not null,
  updated_at datetime(3) not null
);

4)应用与池绑定表:idgen_app_pool_binding

create table idgen_app_pool_binding (
  id bigint primary key auto_increment,
  app_code varchar(64) not null,
  pool_code varchar(64) not null,
  quota_worker_count int not null,
  status varchar(16) not null,
  created_at datetime(3) not null,
  updated_at datetime(3) not null,
  unique key uk_app_pool (app_code, pool_code)
);

三、什么时候才考虑“一个业务一张表”

只有下面几类场景,我才建议拆表:

1. 强隔离要求

比如:

  • 金融核心域
  • 合规要求必须物理隔离
  • 某些业务必须独立库、独立账号、独立备份恢复

2. 超大规模热点

如果某一个超级核心系统:

  • 单独持有几千个 worker 槽位
  • 租约更新频率远高于其他应用
  • 已经成为明显热点

可以考虑给这个应用单独拆一张运行时表。

3. 生命周期完全独立

比如某个 BU 完全独立运营:

  • 自己的 DB
  • 自己的运维
  • 自己的审计
  • 不走统一平台

那它可以自建域内 ID 服务,自己维护一套表。


四、按规模给你一个直接可执行的建议

小中规模

比如:

  • 几十到几百个应用
  • 每个应用几十个 worker
  • 总槽位几千到几万

建议:一张 idgen_worker_slot 就够。

这个阶段不要先拆表,先把:

  • 唯一键
  • 索引
  • lease_token
  • heartbeat
  • checkpoint
  • 运维台

做好。


中大规模

比如:

  • 上千个应用
  • 多 region / 多 dc
  • 槽位规模几十万

这时也不一定要一个业务一张表,更推荐:

逻辑上一张表,物理上做分区

PostgreSQL 原生支持声明式分区,支持 range / list / hash;分区必须与父表保持同样的列集合。 MySQL 也支持多种分区类型,包括 RANGE / LIST / HASH / KEY,但分区有明确限制,尤其是主键/唯一键与分区键相关的约束需要提前设计好。

建议的分区策略

优先按这些维度做:

  • 一级:env
  • 二级:region + dc
  • 再大一点:对 app_code 做 hash 分区

也就是说,是“分区”而不是“一个业务一张物理表”


五、我更推荐的最终表组织方式

推荐模型

方案 A:最常用

  • 一套管理面表
  • 一张运行时表 idgen_worker_slot

适合大多数企业。

方案 B:更大规模

  • 一套管理面表
  • 一张逻辑运行时表
  • 物理分区:按 env / region / dc

方案 C:少量特例业务单独表

  • 默认统一表
  • 只有极少数超热点或强隔离业务单独拆出表

不是“所有业务都一张表”。


六、你可以直接采用的规则

建议团队平台规范直接写成这样:

默认规则

  • 统一平台默认使用一张 idgen_worker_slot 运行时表
  • env + region + dc + app_code + worker_id 唯一定位槽位
  • 不按业务默认拆表

升级规则

满足以下任一条件,才允许拆出专表:

  • 单业务槽位数 > 5000
  • 单业务 heartbeat QPS 持续成为热点
  • 合规要求物理隔离
  • 独立 BU 独立运营

分区规则

达到大规模后,优先选择:

  • 分区
  • 分库
  • 独立实例

而不是一开始就“一业务一表”。


七、最终结论

DB 建表默认不建议“一个业务一张表”。

更合理的是:

  • 统一表模型
  • 统一 worker_slot 运行时表
  • 通过 app_code / env / region / dc 做逻辑隔离
  • 规模上来后优先做分区
  • 只有强隔离或超热点业务才单独拆表

这套方式更适合你要做的企业级统一唯一 ID 平台

我先直接给判断方向:Apollo 能替代 Consul 的“配置中心”部分,但不能完整替代 Consul 在这个场景里的“运行时协调 / 租约 / 锁”能力。 我去核对一下两边官方能力边界,然后给你一个明确的替代结论和推荐架构。

结论Apollo 可以替代 Consul 的“配置中心”角色;不能直接替代 Consul 在 worker id 生命周期管理里承担的“运行时协调中心”角色。 原因很清楚:Apollo 官方定位和能力清单集中在配置管理,例如多环境/多集群/多 namespace 的集中配置、实时生效、版本管理、灰度发布、权限审批和审计;而 Consul 官方明确提供的是 session、TTL、KV acquire/release、blocking query、服务注册与发现 这些分布式协调原语。两者解决的问题层级不同。

放到你前面这个雪花算法 worker id 平台场景里,答案更直接:

1. 哪些部分 Apollo 可以替代

Apollo 很适合承载静态或半静态配置,比如:

  • Snowflake bit 位方案
  • epoch 起始时间
  • region / dc 编码
  • 应用可用的 worker 池范围
  • 时钟回拨策略阈值
  • SDK 开关、告警阈值
  • 灰度发布某个 schema 版本

因为 Apollo 的核心强项就是集中配置、热更新、版本化、灰度发布和审计。

2. 哪些部分 Apollo 不能直接替代

  • 实例启动时动态申请 worker id
  • 持有期间续约
  • 实例异常退出后自动回收
  • 防止两个实例同时持有同一个 worker id
  • 借助 lock-delay / fencing / watch 降低重复持有风险

那 Apollo 不适合直接承担这个角色。Consul 官方对这套能力是有明确原语支持的:session 可设置 TTL,KV 支持 acquire/release,session 失效会释放锁,并且有 lock-delay 和 blocking query/watch 机制;它还原生支持服务注册与发现。Apollo 的官方能力描述并没有把自己定位成分布式锁、租约或服务发现系统。

3. 所以 Apollo 能不能“替代 Consul”

要分场景回答:

  • 如果你现在把 Consul 主要当配置中心用可以,Apollo 能替代这一部分。 Apollo 本来就是为配置中心场景设计的。

  • 如果你现在把 Consul 同时用于配置 + 服务发现 + session/lock/租约协调Apollo 不能一把替掉。 你至少还需要补一个运行时协调组件,常见就是 DB / etcd / ZooKeeper / Consul 自身。Consul 官方文档里这些能力是明确存在的。

4. 对你这个 worker id 平台的推荐

对你前面讨论的方案,建议这样分层:

  • Apollo:放控制面静态配置 例如 app 接入参数、号段池定义、bit 位规则、阈值、灰度配置

  • DB 或 Consul:放运行时租约状态 例如当前哪个实例持有哪个 worker id、何时过期、心跳、回收

也就是:

Apollo 做配置中心;DB/Consul 做协调中心。

这样边界最清晰,也最符合它们各自的产品定位。Apollo 负责“告诉你应该怎么做”,协调存储负责“保证同一时刻只有一个实例真正拿到这个 id”。前者是配置问题,后者是分布式互斥与租约问题。

5. 一句话判断

Apollo 不能完整替代 Consul;但可以替代 Consul 在“配置中心”这一层的职责。 对于雪花 worker id 全生命周期管理,Apollo 最多做控制面配置下发,不能单独承担运行时发号协调

**

我先核对一下 Consul 官方对 KV、Session、Lock、服务发现这几块现在的状态,尤其是你提到的“KV 会下线”到底指的是哪一层能力。然后一个明确判断:哪些能力等价,哪些完全不是一回事。

不是一个含义。

更准确地说,“协调中心能力”是架构角色,而 Consul KV 是 Consul 里的一个具体能力组件。 在 Consul 里,你前面说的那类运行时协调——比如 leader election、worker id 抢占、租约持有、释放、监听变化——并不是“单靠 KV”完成的,而是**Session + KV 的 acquire/release + watch/blocking query 组合出来的能力**。Consul 官方在 session 文档里明确写了:session 主要与 KV 集成使用,KV API 扩展了 acquirerelease 来做分布式锁;官方的 leader election 文档也是基于 sessions + Consul KV 来做的。

所以要分三层理解:

第一层:KV 本身 这是一个 key/value datastore。Consul 官方现在对 KV 的表述是:KV 是 Consul 的核心功能,默认启用;API/CLI/UI 被认为已经 feature complete,未来不再计划新增功能。这句话的意思是“功能冻结/成熟”,不是官方宣布要下线或移除 KV

第二层:协调中心能力 这是你架构里需要的职责,包括:

  • 唯一抢占
  • 租约续约
  • 过期回收
  • 变更通知
  • 分布式互斥

在 Consul 里,这个职责是靠 Session + KV lock + watch 实现的,不等于“普通配置 KV”。

第三层:如果 KV 真不可用,会影响什么 如果你用的是 Consul 的协调方案,那它底层就依赖 KV 的 acquire/release 和对 KV 的 watch。也就是说,协调能力在 Consul 里的具体落地是建立在 KV 之上的;如果没有 KV,这套实现方式就没法照搬。因为官方文档明确说了 acquire/release 是 KV API 的一部分,watch 里的 key / keyprefix 也直接映射到 /v1/kv/

所以,针对你的问题,我给一个判断:

结论

  • “Consul KV 会下线”“协调中心能力”不是同一个概念
  • 协调中心能力是架构能力;Consul KV只是 Consul 用来实现其中一部分能力的底座之一。
  • 就 Consul 官方当前文档看,KV 并没有被官方宣告下线/移除,而是被定义为核心功能、默认启用、功能已完整、未来不再重点扩展

你可以把它理解成:

  • Apollo:偏“配置中心”
  • Consul KV:既能存配置,也能作为 session/lock/watch 的底座
  • 协调中心能力:是更上一层的抽象职责,不绑定某一个产品实现

如果你担心的是中长期技术选型风险,那真正该问的不是“KV 这个词是不是要下线”,而是:

按你前面这条链路的讨论,我的建议仍然是:

  • Apollo 负责静态配置和治理配置
  • DB / etcd / ZooKeeper / Consul session+KV 负责运行时协调
  • 不要把“配置中心”和“协调中心”混成一个概念

**

我先把边界定清楚:你问的不是“Redis 能不能存个编号”,而是能不能替代 Consul 在 worker id 生命周期里承担的协调中心能力。我会按官方机制对比 租约/续约/自动回收/watch/权限治理 这几项,再给你一个明确结论。

{“open”:[{“ref_id”:“turn773740search0”},{“ref_id”:“turn773740search1”},{“ref_id”:“turn773740search2”},{“ref_id”:“turn773740search9”},{“ref_id”:“turn773740search10”},{“ref_id”:“turn773740search16”}],“response_length”:“short”}

在你这个“雪花算法 worker id 生命周期管理”场景里,Redis 可以替代 Consul 的一部分协调能力,但不能等价替代 Consul 的整套能力模型。 Redis 官方提供了分布式锁的典型做法:单实例常见模式是 SET key value NX PX ttl,更进一步还有官方提出的 Redlock 算法;这说明 Redis 具备“抢占、TTL 过期、自动释放”这类基础协调能力。相对地,Consul 原生把这套能力做成了 session + lock + renew/release 的一等模型:session 有 TTL、可续约,锁可绑定 session,并在 session 过期时自动释放。

如果问题限定在worker id 的运行时租约,我的判断是:

Redis 能做

  • 实例启动时抢占一个 worker:{env}:{dc}:{app}:{id} 键;
  • 用 TTL 表示租约有效期;
  • 周期续租;
  • 异常退出后靠 TTL 自动回收;
  • 用 Lua 做“只允许持有者释放自己锁”的原子校验。 这些都属于 Redis 擅长的原子键值与过期控制范围。Redis 官方对分布式锁的文档就是围绕这类模式展开的。

Redis 不等价于 Consul 的地方主要有三点。 第一,Consul 的 session 是显式对象,可查询、可续约、可列举、可绑定 node/checks,并且官方 API 明确支持读 session、列 active sessions、renew session;Redis 锁更像“你自己用 key 和 TTL 搭一个租约协议”,平台要自己补很多元数据和治理逻辑。

第二,变更通知语义更弱。Redis 的 keyspace notifications 是基于 Pub/Sub 的,而 Redis 官方明确说明 Pub/Sub 是 at-most-once,客户端断开期间消息会丢;keyspace notifications 也明确写了,客户端断开再连期间的事件会丢失。Consul 这边更常见的是 blocking query / watch 风格,不是纯 fire-and-forget 的 Pub/Sub。对于运维台、租约观测、抢锁等待来说,Redis 这块需要你自己做补偿扫描,不能只靠订阅。

第三,Redis 有“被逐出”的风险模型。Redis 官方说明,如果配置了 maxmemory 和 eviction policy,内存超限时会驱逐 key。对缓存没问题,但对“worker id 租约 key”是危险的:key 被淘汰,等价于租约状态被非业务逻辑删除。所以如果用 Redis 做协调,你必须把这套 key 放在禁止驱逐的实例/库上,或者至少确保使用 noeviction 类策略并做好容量隔离。

所以,放到架构层面,一个直接结论:

Redis 可以替代 Consul 的“轻量运行时协调”部分,但不能完整替代 Consul。 尤其是当你还需要:

  • 服务注册发现;
  • session 级可视化与租约治理;
  • 更自然的 lock/renew/release 模型;
  • 更强的 watch / ACL / namespace 边界; 这时 Consul 仍然更贴近“协调中心”这个角色。Consul 官方文档就是把锁、session、续约、自动释放当成一组原生能力来设计的。

对你这个 worker id 平台,我的建议是

可以用 Redis,但前提是只把它当“运行时租约存储”,不要把它当全能协调中心。 比较稳的分层是:

  • Apollo / DB 控制面:应用接入、池配置、审批、配额、审计;
  • Redis 数据面:worker id 抢占、续租、释放、过期回收;
  • 本地 checkpoint:保存 workerId + leaseToken + maxTimestampSeen

这样 Redis 只负责“谁现在持有哪个 worker id”,控制面继续负责治理。这个边界比“全靠 Redis”更稳。相关基础能力分别由 Redis 锁/TTL/通知机制与 Consul 的 session 模型差异所决定。

我会怎么选

如果你问我在当前这个场景下怎么选底座:

  • 想要最简依赖、团队 Redis 很熟、规模中小、接受自己补治理逻辑:Redis 可用。
  • 想要原生租约模型、清晰的 session/renew/release 语义、治理更自然:Consul 更合适。
  • 想要企业内最稳妥、最好审计和报表、团队 DB 能力最成熟:我仍然更偏向前面说的 DB 租约表方案。这个判断是架构取舍,不是官方结论。

一句话总结Redis 能替代 Consul 做 worker id 租约,但属于“可以做”,不是“天然等价”;它更像轻量实现,而不是 Consul 那种原生协调模型。

你要的话,我下一条可以直接给你一版 《Redis 替代 Consul 的 worker id 协调方案》,包括:

  • key 设计
  • Lua 脚本
  • 续租与回收流程
  • 异常场景处理矩阵。

我先筛一遍“成熟开源 + 带控制面/控制台”的候选,而不是只列算法库。重点会看两点:有没有现成控制台/治理面,以及能不能真正在 worker id / 唯一 ID 平台场景里落地

{“open”:[{“ref_id”:“turn771280search2”},{“ref_id”:“turn771280search1”},{“ref_id”:“turn771280search3”}],“response_length”:“short”}

有,但要先把“带控制面”分清楚:

目前比较成熟的开源里,几乎没有一个是“开箱即用的唯一 ID 平台 + 完整企业控制面”同时都做得很好的。 更常见的是两类:

  • 有较成熟控制台/控制面的基础设施,但它不是专门的 ID 平台;
  • 有成熟的 ID 生成能力,但控制面很弱,通常只有简单监控页,企业级接入审批、号段治理、租约运维还得自己补。

直接结论

最值得考虑的 4 个方向

1)Consul:最像“运行时协调控制面底座”

Consul 原生就有 session、KV、ACL、UI 这套能力,KV 是核心功能,ACL 也是一等能力;前面我们讨论的 worker id 生命周期里最关键的 租约 / 续约 / 自动释放 / 变更监听,Consul 本身就是按这个模型设计的。

它适合做:

  • worker id 运行时协调中心
  • ACL 权限控制
  • 运维可视化底座

但它不是现成的唯一 ID 平台控制面。 也就是说,Consul 有控制面能力,但没有“业务接入审批、号段池管理、worker 运维台”这套专门面向 ID 平台的控制面。

2)Nacos:最像“配置/服务治理控制面底座”

Nacos 官方就把自己定义成 dynamic service discovery, configuration and service management platform,并且有官方 Console,用来做服务列表、健康管理、服务管理、分布式配置管理。

它适合做:

  • ID 平台控制面的静态配置中心
  • app 接入配置、号段配置、阈值配置、灰度发布

但它不擅长该场景里最核心的那部分: worker id 的运行时租约协调。

所以如果你想走 “Apollo/Nacos + DB/Consul” 分层,Nacos 是合格的控制面配置底座,但不是完整的运行时协调中心。

3)ShardingSphere:最接近“带治理能力的分布式主键方案”

ShardingSphere 内置了 SNOWFLAKE key generator;文档明确写到,在 cluster mode 下,worker-id 可以由系统自动生成,并保证同一 namespace 内不重复。它的 cluster mode 还支持多实例间的元数据共享和分布式状态协调,并依赖 ZooKeeper / etcd 作为 registry/governance center。

它的优点是:

  • 已有分布式主键能力
  • 有治理/集群模式/DistSQL 这类“控制面味道更浓”的机制

但它的问题也很明显:

  • 它的主定位是 数据库增强平台 / Proxy / JDBC 中间件
  • 不是一个面向“应用接入、号段池治理、worker 生命周期”的独立 ID 平台

所以它更适合:

  • 团队已经重度采用 ShardingSphere
  • 想把主键生成放进数据访问层统一治理

不太适合:

  • 单独建设一个企业唯一 ID 平台

4)Leaf / TinyId:最成熟的 ID 引擎,但不是成熟控制面

Leaf 提供两种 ID 模式:segment 和 snowflake;README 里能看到 HTTP 服务、segment 模式的 DB 表、snowflake 模式依赖 ZooKeeper,以及一个简单 monitor page。

TinyId 提供 REST API 和 Java client,基于 DB 表发号,README 展示的是 server、client、建表和 API 调用。

这两者的定位都很清楚:

  • Leaf / TinyId 很适合做“发号引擎”
  • 但从公开文档看,它们都不是“企业级控制面产品”

也就是说,它们缺的通常是:

  • 应用注册
  • 接入审批
  • worker 池/号段池规划
  • 配额治理
  • 强制冻结/摘除
  • 统一审计
  • 多环境运营面板

如果你问“有没有现成就很好用的开源控制面产品”

我的判断是:

没有一个特别成熟、行业通用、开箱即用的开源项目,能直接满足你要的这套完整诉求:

  • 唯一 ID 生成
  • Snowflake / Segment 双模式
  • worker 生命周期治理
  • 接入申请/审批
  • 号段池与配额管理
  • 多环境多机房治理
  • 运维台和审计

这类产品,业界大多还是“引擎开源,控制面自建”。


所以建议你怎么选

路线 A:你要的是“尽快落地 + 控制面不想从零做”

最现实的是:

  • Leaf 或 TinyId 作为发号引擎
  • Nacos / Apollo 做静态配置控制面
  • DB 做 segment/worker runtime state
  • 自己补一个轻量 Portal

这条路实现成本低,尤其对 Java 团队友好。 如果团队更偏 segment 发号,我会优先看 TinyId / Leaf-segment

路线 B:你要的是“worker id 生命周期治理最稳”

那就:

  • 自研 ID 平台控制面
  • 底座选 ConsulDB
  • 发号引擎自己写一层 Snowflake SDK/Sidecar

因为这条路最符合你前面提出的要求: 从申请接入,到租约持有,到回收,到运维审计,全部统一。

路线 C:团队已经深度使用 ShardingSphere

那可以评估把分布式主键治理并入 ShardingSphere 的 cluster mode / governance 体系,但这更像“数据库中间件方案”,不太像独立 ID 平台。


最终推荐

如果按团队这种企业级治理诉求,我会这么排:

第一推荐:自研控制面 + Leaf/TinyId 或自研 Snowflake 引擎 理由:最贴近你的治理诉求,Java 团队也最容易控住。

第二推荐:Consul 作为运行时协调底座,自建控制面 理由:worker 生命周期语义天然贴合。

第三推荐:Nacos 作为控制面配置底座,DB 做运行时协调 理由:控制台现成,接入门槛低,但运行时租约还是要自己补。

不太推荐把希望寄托在“某个现成开源项目已经把完整 ID 控制面都做好了”,因为从当前公开成熟项目看,这类“一体化成品”并不多见。

该场景下,最像“正确姿势”的其实是:

开源项目拿来做发号内核,控制面自己做。

你要是愿意,我下一条就直接给你整理一份 《开源工具选型对比表(Leaf / TinyId / Consul / Nacos / ShardingSphere)》,按“控制面能力、运行时协调、Java 友好度、落地复杂度、推荐级别”逐项打分。

我先按 DDD 方式把这份方案拆成几个上下文:接入治理、池与配额治理、运行时租约、发号执行、审计运维。接下来我会先判断哪些对象应该是聚合根,哪些只是运行态快照,再给你一版可直接落地的领域模型图。

基于你这份方案,我的判断是:整体架构已经具备明显的领域边界,只是当前文档里的“逻辑对象模型”更偏数据对象,还没有完全收敛成 DDD 语义下的聚合、值对象、领域服务和读模型。 方案里已经明确分出了控制面、数据面和 Consul 协调层,并定义了 ApplicationWorker PoolApp-Pool BindingAccess RequestLease Runtime Snapshot 等对象,同时运行时流程也已经清晰描述了 bootstrap → checkpoint → create session → reclaim/acquire → renew → release/destroy 的完整生命周期。fileciteturn0file0

建议把这个平台拆成 5 个限界上下文(Bounded Context),而不是把所有对象塞进一个“大而全”的 ID 平台域模型里。这样既符合你文档中“控制面 / 数据面 / 运行时协调”的分层,也更利于后续把控制面 API、数据库表、SDK 和运维台拆开实现。这个拆分是我基于方案结构做的领域抽象。fileciteturn0file0

一、领域分析结论

1)核心域与支撑域划分

建议这样划:

  • 核心域(Core Domain)

    • 运行时租约域 Runtime Lease
    • 本地发号域 ID Generation
  • 支撑域(Supporting Domain)

    • 接入治理域 Access Governance
    • 池与配额治理域 Pool Governance
    • 运维审计域 Operations & Audit
  • 通用域(Generic Subdomain)

    • 身份与授权域 Identity & ACL

原因很简单: 真正决定这个平台技术价值和正确性的,不是“应用申请单”本身,而是 worker id 的唯一持有、续约、回收,以及 lease 丢失时必须 fail closed 停止发号。这些在你的文档里是最关键的系统约束。fileciteturn0file0

2)最重要的领域主线

这个平台的业务主线其实只有一条:

应用被准入 → 获得池与配额 → 运行实例启动 → 成功持有 worker lease → 本地 Snowflake 发号 → 续约/释放/回收 → 运维与审计追踪

这条主线已经在你的接入流程、生命周期、控制面 API 和监控设计里完整出现了。fileciteturn0file0


二、推荐的限界上下文

1. 接入治理域 Access Governance

负责“谁可以接入平台”。

核心对象:

  • Application
  • AccessRequest
  • AdmissionPolicy

职责:

  • 应用注册
  • 接入申请
  • 审批通过 / 驳回
  • 生产准入校验

2. 池与配额治理域 Pool Governance

负责“给哪个应用分配多少 worker 资源”。

核心对象:

  • WorkerPool
  • PoolAllocation(建议替代文档里的 App-Pool Binding 作为正式聚合名)
  • QuotaPolicy

职责:

  • dedicated/shared 池管理
  • worker 范围规划
  • 应用与池绑定
  • 配额、峰值副本数、峰值 ID QPS 管理

3. 运行时租约域 Runtime Lease

负责“哪一个运行实例当前合法持有哪个 worker id”。

核心对象:

  • RuntimeLease
  • LeaseHolder
  • LeasePolicy

职责:

  • reclaim / acquire
  • renew
  • release
  • expire / cooldown
  • force release

4. 本地发号域 ID Generation

负责“本地如何安全、快速地产生唯一 ID”。

核心对象:

  • GeneratorNode
  • IdSchema
  • Checkpoint
  • GeneratorState

职责:

  • nextId()
  • sequence 递增
  • 时钟回拨防护
  • checkpoint 刷盘
  • lease 不确定时停发

5. 运维审计域 Operations & Audit

负责“看见系统状态并做治理动作”。

核心对象:

  • LeaseRuntimeSnapshot
  • PoolUsageView
  • AuditRecord
  • OperationCommand

职责:

  • 活跃租约视图
  • 池使用率
  • 强制摘除
  • 冻结应用
  • 审计追踪

这里我要特别指出一个关键建模调整:

LeaseRuntimeSnapshot 不应该作为核心领域聚合,而应该视为读模型(Read Model / Projection)。

因为你文档里已经写得很清楚,它是“控制面缓存的运行态快照”,它更像运维台视图,不是运行时租约的真相源。fileciteturn0file0


三、聚合根建议

1)Application(聚合根)

表示平台治理视角下的“接入主体”。

包含:

  • 应用编码
  • owner/team
  • env / region / dc scope
  • deploy mode
  • schema version
  • status

不变量:

  • 未注册应用不能申请池
  • 未通过接入审批不能进入生产启用

2)AccessRequest(聚合根)

表示一次正式接入或变更申请。

包含:

  • request_no
  • app_code
  • request_type
  • requested_worker_count
  • risk_level / reason
  • approver / status

不变量:

  • 一个申请只能处于一个明确审批状态
  • 驳回后不能直接启用,需要重新发起

3)WorkerPool(聚合根)

表示一个环境下某个范围的 worker 资源池。

包含:

  • pool_code
  • env / region / dc
  • worker range
  • pool_mode
  • status

不变量:

  • 同一资源范围不能重复分配
  • 冻结池不能再新分配

4)PoolAllocation(聚合根)

这个建议从 App-Pool Binding 升级为独立聚合。

包含:

  • app_code
  • pool_code
  • quota_worker_count
  • max_replicas
  • peak_id_qps
  • status

不变量:

  • 分配额度不能超过池容量
  • 未绑定分配关系的应用,运行时不能启动申请 worker id

5)RuntimeLease(聚合根)

这是平台最关键的聚合。

包含:

  • env / region / dc / app_code / worker_id
  • holder_instance_id
  • boot_id
  • session_id
  • lease_state
  • acquired_at / expires_at
  • cooldown_until

不变量:

  • 同一 (env, region, dc, app_code, worker_id) 在同一时刻最多只有一个 ACTIVE lease
  • lease 失效后必须进入 EXPIREDCOOLDOWN
  • 失去 lease 的实例必须停止发号

6)GeneratorNode(聚合根)

表示本地发号执行体。

包含:

  • workerId
  • regionId / dcId
  • lastTimestamp
  • sequence
  • maxTimestampSeen
  • state
  • checkpoint

不变量:

  • state != ACTIVE 时不能发号
  • 时钟回拨超过阈值必须拒绝发号
  • sequence 溢出必须等待下一毫秒

四、值对象建议

建议把下面这些建成值对象,而不是普通字段:

  • DeploymentScope(env, region, dc)
  • WorkerRange(start, end)
  • QuotaPolicy(quotaWorkerCount, maxReplicas, peakIdQps)
  • IdSchema(signBits, tsBits, regionBits, dcBits, workerBits, seqBits, epoch)
  • Checkpoint(workerId, bootId, lastTimestamp, maxTimestampSeen, updatedAt)
  • LeasePolicy(ttl, lockDelay, renewInterval, cooldown, renewFailThreshold)
  • GeneratorState(active/readOnly/draining)

这样能把你文档里分散的参数约束,收口成领域内可校验的对象。相关参数和规则在方案里已经明确定义,例如 bit 位分配、TTL、LockDelay、cooldown、rollback threshold 等。fileciteturn0file0


五、领域服务建议

这些更适合建成领域服务,而不是塞进某个实体:

  • AdmissionService:接入申请、审批、准入判定
  • PoolPlanningService:池规划、配额校验
  • LeaseAllocationService:reclaim / acquire worker id
  • LeaseRenewService:session renew 与租约续期
  • LeaseRecoveryService:expire / cooldown / force release
  • IdGenerationService:本地 nextId 生成
  • BootstrapService:拉取 bootstrap 配置并初始化节点
  • RuntimeObservationService:runtime snapshot 投影与运维查询

这些服务,都能在你现有文档中找到对应职责来源。fileciteturn0file0


六、领域事件建议

这套系统很适合事件化,因为很多状态变化都需要被运维、审计、监控和控制面感知。

推荐事件:

  • ApplicationRegistered
  • AccessRequested
  • AccessApproved
  • AccessRejected
  • PoolAllocated
  • PoolExpanded
  • WorkerLeaseAcquired
  • WorkerLeaseRenewed
  • WorkerLeaseReleased
  • WorkerLeaseExpired
  • WorkerLeaseForceReleased
  • GeneratorEnteredReadOnly
  • ClockRollbackDetected
  • PoolExhausted
  • CheckpointFlushed

这些事件都与文档中的 API、状态机、告警规则和异常矩阵天然对应。fileciteturn0file0


七、领域模型图(限界上下文图)

以下这张图,先画上下文与关系

flowchart LR
    subgraph AG["接入治理域 Access Governance"]
        Application["Application"]
        AccessRequest["AccessRequest"]
        AdmissionPolicy["AdmissionPolicy"]
    end

    subgraph PG["池与配额治理域 Pool Governance"]
        WorkerPool["WorkerPool"]
        PoolAllocation["PoolAllocation"]
        QuotaPolicy["QuotaPolicy"]
    end

    subgraph RL["运行时租约域 Runtime Lease"]
        RuntimeLease["RuntimeLease"]
        LeasePolicy["LeasePolicy"]
        LeaseHolder["LeaseHolder"]
    end

    subgraph IG["本地发号域 ID Generation"]
        GeneratorNode["GeneratorNode"]
        IdSchema["IdSchema"]
        Checkpoint["Checkpoint"]
        GeneratorState["GeneratorState"]
    end

    subgraph OA["运维审计域 Operations & Audit"]
        LeaseRuntimeSnapshot["LeaseRuntimeSnapshot <<ReadModel>>"]
        PoolUsageView["PoolUsageView <<ReadModel>>"]
        AuditRecord["AuditRecord"]
        OperationCommand["OperationCommand"]
    end

    subgraph IA["身份与授权域 Identity & ACL"]
        Identity["RuntimeIdentity"]
        TokenPolicy["TokenPolicy"]
        ACLPolicy["ACLPolicy"]
    end

    AccessRequest --> Application
    PoolAllocation --> Application
    PoolAllocation --> WorkerPool
    RuntimeLease --> PoolAllocation
    RuntimeLease --> LeasePolicy
    GeneratorNode --> RuntimeLease
    GeneratorNode --> IdSchema
    GeneratorNode --> Checkpoint
    OperationCommand --> RuntimeLease
    LeaseRuntimeSnapshot --> RuntimeLease
    PoolUsageView --> WorkerPool
    AuditRecord --> Application
    AuditRecord --> RuntimeLease
    RuntimeLease --> Identity
    Identity --> TokenPolicy
    TokenPolicy --> ACLPolicy

这张图表达的是: Application 被准入后,通过 PoolAllocation 获得资源资格;RuntimeLease 在运行期消费这份资格;GeneratorNode 必须基于合法 lease 才能发号;运维审计域只读观察或下发治理命令。 这个关系链是从你的控制面对象、运行时流程和运维 API 直接抽出来的。fileciteturn0file0


八、领域模型图(聚合/实体/值对象图)

以下这张图,画得更接近“聚合根 + 值对象 + 读模型”:

classDiagram
    class Application {
        <<AggregateRoot>>
        +appCode
        +appName
        +ownerTeam
        +ownerUser
        +bizDomain
        +deployMode
        +status
        +register()
        +freeze()
        +enable()
    }

    class DeploymentScope {
        <<ValueObject>>
        +env
        +regionScope
        +dcScope
    }

    class AccessRequest {
        <<AggregateRoot>>
        +requestNo
        +requestType
        +requestedBy
        +requestedWorkerCount
        +riskLevel
        +status
        +submit()
        +approve()
        +reject()
    }

    class WorkerPool {
        <<AggregateRoot>>
        +poolCode
        +poolMode
        +status
        +allocate()
        +expand()
        +freeze()
    }

    class WorkerRange {
        <<ValueObject>>
        +workerStart
        +workerEnd
        +capacity()
    }

    class PoolAllocation {
        <<AggregateRoot>>
        +appCode
        +poolCode
        +quotaWorkerCount
        +maxReplicas
        +peakIdQps
        +status
        +bind()
        +changeQuota()
        +disable()
    }

    class QuotaPolicy {
        <<ValueObject>>
        +quotaWorkerCount
        +maxReplicas
        +peakIdQps
    }

    class RuntimeLease {
        <<AggregateRoot>>
        +env
        +region
        +dc
        +appCode
        +workerId
        +sessionId
        +leaseState
        +acquire()
        +renew()
        +release()
        +expire()
        +enterCooldown()
    }

    class LeaseHolder {
        <<Entity>>
        +instanceId
        +bootId
        +node
    }

    class LeasePolicy {
        <<ValueObject>>
        +ttl
        +lockDelay
        +renewInterval
        +cooldown
        +renewFailThreshold
    }

    class GeneratorNode {
        <<AggregateRoot>>
        +workerId
        +regionId
        +dcId
        +lastTimestamp
        +sequence
        +maxTimestampSeen
        +state
        +nextId()
        +enterReadOnly()
        +flushCheckpoint()
    }

    class IdSchema {
        <<ValueObject>>
        +timestampBits
        +regionBits
        +dcBits
        +workerBits
        +sequenceBits
        +epoch
    }

    class Checkpoint {
        <<ValueObject>>
        +workerId
        +bootId
        +lastTimestamp
        +maxTimestampSeen
        +updatedAt
    }

    class LeaseRuntimeSnapshot {
        <<ReadModel>>
        +env
        +region
        +dc
        +appCode
        +workerId
        +instanceId
        +bootId
        +sessionId
        +leaseState
        +lastSeenAt
    }

    class AuditRecord {
        <<Entity>>
        +eventType
        +operatorUser
        +target
        +detailJson
        +createdAt
    }

    Application --> DeploymentScope
    AccessRequest --> Application
    WorkerPool --> WorkerRange
    PoolAllocation --> Application
    PoolAllocation --> WorkerPool
    PoolAllocation --> QuotaPolicy
    RuntimeLease --> LeaseHolder
    RuntimeLease --> LeasePolicy
    RuntimeLease --> PoolAllocation
    GeneratorNode --> RuntimeLease
    GeneratorNode --> IdSchema
    GeneratorNode --> Checkpoint
    LeaseRuntimeSnapshot --> RuntimeLease
    AuditRecord --> Application
    AuditRecord --> RuntimeLease

九、我对原方案的几个建模优化建议

1)App-Pool Binding 建议改名为 PoolAllocation

因为它不只是“绑定关系”,它实际承载了:

  • quota_worker_count
  • max_replicas
  • peak_id_qps
  • status

这已经不是单纯关联表了,而是一个有独立生命周期和业务约束的领域对象。fileciteturn0file0

2)Lease Runtime Snapshot 不要当真相模型

它应该是 projection / read model。 真正的真相模型应该是 RuntimeLease 聚合,snapshot 只是控制面为了查询和运维投影出来的视图。你文档里已经明确把它描述成“控制面缓存的运行态快照”。fileciteturn0file0

3)Checkpoint 不应进入控制面管理聚合

它更适合作为 GeneratorNode 的本地值对象。 因为 checkpoint 的职责是:

  • 重认领原 worker id
  • 提供时钟安全下界
  • 降低短时控制面不可用影响 这些都是本地发号执行面的语义,不是控制面治理语义。fileciteturn0file0

4)IdSchema 建议显式建模

当前文档里 bit 位方案、epoch、schema_version 已经出现,但还分散在字段和文字里。建议显式拉成 IdSchema 值对象,未来支持 v1 / v2 版本演进时会更干净。fileciteturn0file0

5)安全域独立出来更好

你文档里 Token / ACL / Auth Method 已经单独成块,说明它天然是一个独立上下文,而不是 Application 聚合里的普通字段。fileciteturn0file0


十、最终结论

这份方案的领域模型,最合理的落法不是“控制面一堆表 + SDK 一堆流程”的技术拆分,而是:

  • Application / AccessRequest 解决“谁能接”
  • WorkerPool / PoolAllocation 解决“能用多少资源”
  • RuntimeLease 解决“谁当前合法持有 worker id”
  • GeneratorNode / IdSchema / Checkpoint 解决“如何本地安全发号”
  • LeaseRuntimeSnapshot / AuditRecord 解决“如何观测和治理”

一句话概括:

这个平台的核心聚合不是 Application,而是 RuntimeLease;核心业务能力不是审批,而是“合法 lease 驱动的 fail-closed 发号”。

这个判断,是从你文档里对 lifecycle、session、renew、cooldown、clock rollback 和 fail closed 的强调直接推出来的。fileciteturn0file0

你要的话,我下一条可以继续把这套领域模型展开成 《聚合边界 + 仓储接口 + 领域服务接口 + 事件清单》

from pathlib import Path p = Path(‘/mnt/data/idgen.md’) text = p.read_text()

backup

backup = Path(‘/mnt/data/idgen_original.md’) backup.write_text(text)

text = text.replace(’# 《基于 Consul 和雪花算法的唯一 ID 生成平台详细设计》v1.0’, ’# 《基于 Consul 和雪花算法的唯一 ID 生成平台详细设计》v1.1’)

insert_after = ”#### Lease Runtime Snapshot\n\n控制面缓存的运行态快照。\n字段:\n\n* env/region/dc/app_code/worker_id\n* instance_id\n* boot_id\n* session_id\n* lease_state\n* last_seen_at\n” if insert_after not in text: raise SystemExit(“anchor not found”)

addition = insert_after + """

7.2 领域建模建议

当前逻辑对象模型已经覆盖了控制面与运行时所需的核心数据对象,但从领域设计视角看,仍建议进一步区分聚合根、值对象、领域服务、读模型,避免把控制面表结构直接等同于领域模型。

核心建议如下:

  • Application 仍作为接入治理的核心聚合根,负责表达“谁可以接入平台”
  • Worker Pool 建议保留,但 App-Pool Binding 更适合提升为具有独立业务语义的 Pool Allocation
  • Lease Runtime Snapshot 不应作为真相模型,而应视为控制面运维视图的 Read Model / Projection
  • 本地 Checkpoint 应视为数据面 Generator Node 的值对象,而非控制面主数据
  • IdSchemaLeasePolicyDeploymentScopeQuotaPolicy 建议显式建模为值对象,以承载规则和约束

7.3 限界上下文划分

建议将平台拆分为以下 5 个限界上下文:

  1. 接入治理域(Access Governance) 负责应用注册、接入申请、审批、生产准入校验。

  2. 池与配额治理域(Pool Governance) 负责 worker 池规划、应用与池绑定、资源配额和容量规则。

  3. 运行时租约域(Runtime Lease) 负责 worker id 的 reclaim / acquire / renew / release / expire / cooldown。

  4. 本地发号域(ID Generation) 负责本地 Snowflake 发号、sequence 管理、时钟回拨保护、checkpoint 刷盘。

  5. 运维审计域(Operations & Audit) 负责运行态快照、池使用率、告警、强制摘除、审计追踪。

补充说明:

  • Identity & ACL 可作为通用支撑子域,服务于控制面和运行时租约域
  • Consul 在领域上不作为业务域对象,而是运行时租约域的基础设施实现
flowchart LR
    subgraph AG["接入治理域 Access Governance"]
        Application["Application"]
        AccessRequest["AccessRequest"]
        AdmissionPolicy["AdmissionPolicy"]
    end

    subgraph PG["池与配额治理域 Pool Governance"]
        WorkerPool["WorkerPool"]
        PoolAllocation["PoolAllocation"]
        QuotaPolicy["QuotaPolicy"]
    end

    subgraph RL["运行时租约域 Runtime Lease"]
        RuntimeLease["RuntimeLease"]
        LeasePolicy["LeasePolicy"]
        LeaseHolder["LeaseHolder"]
    end

    subgraph IG["本地发号域 ID Generation"]
        GeneratorNode["GeneratorNode"]
        IdSchema["IdSchema"]
        CheckpointVO["Checkpoint"]
        GeneratorState["GeneratorState"]
    end

    subgraph OA["运维审计域 Operations & Audit"]
        LeaseRuntimeSnapshotRM["LeaseRuntimeSnapshot <<ReadModel>>"]
        PoolUsageView["PoolUsageView <<ReadModel>>"]
        AuditRecord["AuditRecord"]
        OperationCommand["OperationCommand"]
    end

    AccessRequest --> Application
    PoolAllocation --> Application
    PoolAllocation --> WorkerPool
    RuntimeLease --> PoolAllocation
    GeneratorNode --> RuntimeLease
    GeneratorNode --> IdSchema
    GeneratorNode --> CheckpointVO
    OperationCommand --> RuntimeLease
    LeaseRuntimeSnapshotRM --> RuntimeLease
    PoolUsageView --> WorkerPool
    AuditRecord --> Application
    AuditRecord --> RuntimeLease

7.4 聚合根、实体、值对象与读模型

聚合根建议

  • Application:接入治理聚合根,负责应用注册、启停、冻结、生产可见性
  • AccessRequest:接入申请聚合根,负责申请、审批、驳回状态流转
  • WorkerPool:资源池聚合根,负责池状态、worker 范围、冻结与扩容
  • PoolAllocation:建议替代 App-Pool Binding,负责应用与池的配额关系
  • RuntimeLease:运行时核心聚合根,负责单个 worker id 的合法持有关系
  • GeneratorNode:数据面聚合根,负责本地发号状态机与 checkpoint 管理

实体建议

  • LeaseHolder:描述当前持有租约的实例身份(instanceId / bootId / node)
  • AuditRecord:审计记录实体
  • OperationCommand:运维操作命令实体(如 force release / freeze / readonly)

值对象建议

  • DeploymentScope(env, regionScope, dcScope)
  • WorkerRange(workerStart, workerEnd)
  • QuotaPolicy(quotaWorkerCount, maxReplicas, peakIdQps)
  • IdSchema(timestampBits, regionBits, dcBits, workerBits, sequenceBits, epoch)
  • LeasePolicy(ttl, lockDelay, renewInterval, cooldown, renewFailThreshold)
  • Checkpoint(workerId, bootId, lastTimestamp, maxTimestampSeen, updatedAt)

读模型建议

  • LeaseRuntimeSnapshot:控制面运行态查询模型
  • PoolUsageView:池使用率与容量视图
  • AccessRequestView:申请单列表和审批页读模型

7.5 领域模型图

classDiagram
    class Application {
        <<AggregateRoot>>
        +appCode
        +appName
        +ownerTeam
        +ownerUser
        +bizDomain
        +deployMode
        +status
    }

    class DeploymentScope {
        <<ValueObject>>
        +env
        +regionScope
        +dcScope
    }

    class AccessRequest {
        <<AggregateRoot>>
        +requestNo
        +requestType
        +requestedBy
        +requestedWorkerCount
        +riskLevel
        +status
    }

    class WorkerPool {
        <<AggregateRoot>>
        +poolCode
        +poolMode
        +status
    }

    class WorkerRange {
        <<ValueObject>>
        +workerStart
        +workerEnd
    }

    class PoolAllocation {
        <<AggregateRoot>>
        +appCode
        +poolCode
        +quotaWorkerCount
        +maxReplicas
        +peakIdQps
        +status
    }

    class QuotaPolicy {
        <<ValueObject>>
        +quotaWorkerCount
        +maxReplicas
        +peakIdQps
    }

    class RuntimeLease {
        <<AggregateRoot>>
        +env
        +region
        +dc
        +appCode
        +workerId
        +sessionId
        +leaseState
    }

    class LeaseHolder {
        <<Entity>>
        +instanceId
        +bootId
        +node
    }

    class LeasePolicy {
        <<ValueObject>>
        +ttl
        +lockDelay
        +renewInterval
        +cooldown
    }

    class GeneratorNode {
        <<AggregateRoot>>
        +workerId
        +regionId
        +dcId
        +lastTimestamp
        +sequence
        +maxTimestampSeen
        +state
    }

    class IdSchema {
        <<ValueObject>>
        +timestampBits
        +regionBits
        +dcBits
        +workerBits
        +sequenceBits
        +epoch
    }

    class Checkpoint {
        <<ValueObject>>
        +workerId
        +bootId
        +lastTimestamp
        +maxTimestampSeen
        +updatedAt
    }

    class LeaseRuntimeSnapshot {
        <<ReadModel>>
        +env
        +region
        +dc
        +appCode
        +workerId
        +instanceId
        +bootId
        +sessionId
        +leaseState
        +lastSeenAt
    }

    Application --> DeploymentScope
    AccessRequest --> Application
    WorkerPool --> WorkerRange
    PoolAllocation --> Application
    PoolAllocation --> WorkerPool
    PoolAllocation --> QuotaPolicy
    RuntimeLease --> LeaseHolder
    RuntimeLease --> LeasePolicy
    RuntimeLease --> PoolAllocation
    GeneratorNode --> RuntimeLease
    GeneratorNode --> IdSchema
    GeneratorNode --> Checkpoint
    LeaseRuntimeSnapshot --> RuntimeLease

""" text = text.replace(insert_after, addition)

append new sections before final conclusion or after section 24? We’ll append before ”## 24. 最终结论”

anchor2 = “\n## 24. 最终结论\n” if anchor2 not in text: raise SystemExit(“anchor2 not found”) append_sections = """

24. 聚合边界

24.1 Application 聚合边界

职责:

  • 表达平台接入主体
  • 维护 owner、部署范围、schema version、状态
  • 决定应用是否可进入后续申请和生产启用流程

聚合内对象:

  • Application
  • DeploymentScope

不变量:

  • appCode 全局唯一
  • 未注册应用不得发起接入审批
  • 已冻结应用不得发起新的生产启用与 pool 分配

不属于该聚合:

  • 运行态租约
  • worker 池容量
  • checkpoint 本地状态

24.2 AccessRequest 聚合边界

职责:

  • 表达一次接入或变更申请
  • 管理提交、审批、驳回、关闭等状态流转

聚合内对象:

  • AccessRequest

不变量:

  • 一个申请单同一时刻只能处于一个明确审批状态
  • 驳回后不得直接启用,必须重新发起申请
  • 审批完成后必须关联到明确的 Application

24.3 WorkerPool 聚合边界

职责:

  • 管理 worker 资源池的基本属性和可用性
  • 管理 dedicated / shared 模式、冻结、扩容、停用

聚合内对象:

  • WorkerPool
  • WorkerRange

不变量:

  • 同一 (env, region, dc) 下,同一 worker 范围不得重叠
  • 冻结中的池不得再新分配
  • 停用中的池不得再扩容

24.4 PoolAllocation 聚合边界

职责:

  • 表达应用与 worker 池的业务约束关系
  • 管理 quota、max_replicas、peak_id_qps 等资源约束

聚合内对象:

  • PoolAllocation
  • QuotaPolicy

不变量:

  • quota 不得超过池可用容量
  • 未建立 allocation 的应用不得申请运行时 worker id
  • allocation 被禁用后,新实例不得再成功 bootstrap 获取可用池范围

24.5 RuntimeLease 聚合边界

职责:

  • 表达单个 (env, region, dc, appCode, workerId) 的运行时合法持有关系
  • 管理 acquire、renew、release、expire、cooldown、force-release

聚合内对象:

  • RuntimeLease
  • LeaseHolder
  • LeasePolicy

不变量:

  • 同一 (env, region, dc, appCode, workerId) 任一时刻最多只有一个 ACTIVE lease
  • lease 丢失、session 不确定、续约失败达到阈值时必须进入不可发号状态
  • cooldown 未结束前不得重新分配

说明:

  • LeaseRuntimeSnapshot 不属于该聚合,它是读模型
  • Consul session / KV lock 是该聚合的基础设施实现,而不是聚合内部对象

24.6 GeneratorNode 聚合边界

职责:

  • 管理本地 Snowflake 发号状态
  • 管理 sequence、lastTimestamp、maxTimestampSeen、checkpoint
  • 基于 lease 合法性决定是否允许 nextId()

聚合内对象:

  • GeneratorNode
  • IdSchema
  • Checkpoint

不变量:

  • state != ACTIVE 时不得发号
  • 时钟回拨超阈值必须拒绝发号
  • sequence 溢出必须等待下一毫秒
  • 当前 lease 失效或不确定时必须 fail closed

25. 仓储接口

说明:以下接口为领域层抽象,具体实现可以基于 MySQL/PostgreSQL、Consul KV、缓存或读模型存储。

25.1 ApplicationRepository

public interface ApplicationRepository {
    Optional<Application> findByAppCode(String appCode);
    boolean existsByAppCode(String appCode);
    void save(Application application);
    void update(Application application);
}

25.2 AccessRequestRepository

public interface AccessRequestRepository {
    Optional<AccessRequest> findByRequestNo(String requestNo);
    List<AccessRequest> findPendingByAppCode(String appCode);
    void save(AccessRequest request);
    void update(AccessRequest request);
}

25.3 WorkerPoolRepository

public interface WorkerPoolRepository {
    Optional<WorkerPool> findByPoolCode(String poolCode);
    List<WorkerPool> findByScope(String env, String region, String dc);
    void save(WorkerPool pool);
    void update(WorkerPool pool);
}

25.4 PoolAllocationRepository

public interface PoolAllocationRepository {
    Optional<PoolAllocation> findByAppCode(String appCode);
    List<PoolAllocation> findByPoolCode(String poolCode);
    void save(PoolAllocation allocation);
    void update(PoolAllocation allocation);
}

25.5 RuntimeLeaseRepository

public interface RuntimeLeaseRepository {
    Optional<RuntimeLease> findLease(String env, String region, String dc, String appCode, int workerId);
    Optional<RuntimeLease> findActiveLease(String env, String region, String dc, String appCode, int workerId);
    List<RuntimeLease> findActiveLeasesByApp(String env, String region, String dc, String appCode);
 
    boolean tryAcquire(RuntimeLease lease);
    boolean tryRenew(RuntimeLease lease);
    boolean tryRelease(RuntimeLease lease);
    boolean forceRelease(String env, String region, String dc, String appCode, int workerId);
 
    List<Integer> findAllocatableWorkerIds(String env, String region, String dc, String appCode);
}

25.6 CheckpointRepository

public interface CheckpointRepository {
    Optional<Checkpoint> load(String appCode, String env, String region, String dc, String instanceId);
    void save(Checkpoint checkpoint);
    void delete(String appCode, String env, String region, String dc, String instanceId);
}

25.7 AuditRecordRepository

public interface AuditRecordRepository {
    void append(AuditRecord record);
    List<AuditRecord> findByTarget(String target);
    List<AuditRecord> findByOperator(String operatorUser);
}

25.8 LeaseRuntimeSnapshotRepository

public interface LeaseRuntimeSnapshotRepository {
    void upsert(LeaseRuntimeSnapshot snapshot);
    List<LeaseRuntimeSnapshot> queryByApp(String env, String region, String dc, String appCode);
    List<LeaseRuntimeSnapshot> queryActive(String env, String region, String dc);
}

26. 领域服务接口

26.1 AdmissionService

public interface AdmissionService {
    Application registerApplication(RegisterApplicationCommand command);
    AccessRequest submitAccessRequest(SubmitAccessRequestCommand command);
    void approveAccessRequest(ApproveAccessRequestCommand command);
    void rejectAccessRequest(RejectAccessRequestCommand command);
}

26.2 PoolPlanningService

public interface PoolPlanningService {
    WorkerPool createPool(CreatePoolCommand command);
    void expandPool(ExpandPoolCommand command);
    PoolAllocation allocatePool(AllocatePoolCommand command);
    void changeQuota(ChangeQuotaCommand command);
    void freezePool(FreezePoolCommand command);
}

26.3 BootstrapService

public interface BootstrapService {
    BootstrapResult bootstrap(BootstrapCommand command);
}

26.4 LeaseAllocationService

public interface LeaseAllocationService {
    LeaseAcquireResult reclaimOrAcquire(ReclaimOrAcquireCommand command);
    void release(ReleaseLeaseCommand command);
    void forceRelease(ForceReleaseLeaseCommand command);
}

26.5 LeaseRenewService

public interface LeaseRenewService {
    RenewResult renew(RenewLeaseCommand command);
}

26.6 IdGenerationService

public interface IdGenerationService {
    long nextId(NextIdCommand command);
}

26.7 CheckpointService

public interface CheckpointService {
    void flush(FlushCheckpointCommand command);
    Optional<Checkpoint> load(LoadCheckpointCommand command);
}

26.8 RuntimeObservationService

public interface RuntimeObservationService {
    void reportHeartbeat(ReportRuntimeHeartbeatCommand command);
    List<LeaseRuntimeSnapshot> queryActiveLeases(QueryRuntimeLeaseCommand command);
    PoolUsageView getPoolUsage(GetPoolUsageCommand command);
}

27. 事件清单

27.1 接入治理域事件

  • ApplicationRegistered
  • ApplicationFrozen
  • ApplicationEnabled
  • AccessRequested
  • AccessApproved
  • AccessRejected

27.2 池与配额治理域事件

  • WorkerPoolCreated
  • WorkerPoolExpanded
  • WorkerPoolFrozen
  • PoolAllocated
  • PoolQuotaChanged
  • PoolAllocationDisabled

27.3 运行时租约域事件

  • WorkerLeaseReclaimed
  • WorkerLeaseAcquired
  • WorkerLeaseRenewed
  • WorkerLeaseReleased
  • WorkerLeaseExpired
  • WorkerLeaseEnteredCooldown
  • WorkerLeaseForceReleased
  • PoolExhausted

27.4 本地发号域事件

  • GeneratorActivated
  • GeneratorEnteredReadOnly
  • GeneratorDraining
  • IdGenerated
  • SequenceExhausted
  • ClockRollbackDetected
  • CheckpointFlushed

27.5 运维审计域事件

  • RuntimeHeartbeatReported
  • LeaseRuntimeSnapshotUpdated
  • PoolUsageCalculated
  • ForceReleaseRequested
  • ReadonlySwitchRequested
  • AuditRecordAppended

27.6 事件字段建议

所有领域事件建议统一携带以下元字段:

  • eventId
  • eventType
  • occurredAt
  • appCode
  • env
  • region
  • dc
  • operator
  • traceId
  • payloadVersion

27.7 事件用途说明

  • 审计用途:记录接入审批、池变更、强制释放、只读切换
  • 运维用途:驱动运维台刷新运行态、池使用率和告警
  • 异步集成用途:驱动报表、容量分析、后续自动扩容建议
  • 追踪用途:把控制面变更和数据面运行态串到统一事件流中

""" text = text.replace(anchor2, append_sections + anchor2)

updated = Path(‘/mnt/data/idgen_updated.md’) updated.write_text(text) print(“written”, updated, “size”, updated.stat().st_size) print(text[:300]) print(”…”) print(text[text.find(”## 24. 聚合边界”):text.find(”## 24. 最终结论”)][:800])

已更新文件,新增了你要求的内容:

  • 将前面的领域建模建议合入文档
  • 新增限界上下文划分
  • 新增领域模型图上下文关系图
  • 新增《聚合边界 + 仓储接口 + 领域服务接口 + 事件清单

文件如下:

这次更新里,核心建模调整有两点:

  • App-Pool Binding 提升为更有业务语义的 PoolAllocation
  • LeaseRuntimeSnapshot 明确为 读模型(Read Model),不再当作真相聚合

要的话,