雪花算法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 个阶段:
- 分配:实例启动时,去协调中心申请一个空闲
worker id。 - 绑定:把
worker id绑定到当前实例指纹,例如cluster/zone/app/instanceId/bootUuid。 - 续约:实例存活期间持续 heartbeat 或 renew lease。
- 重启处理:优先尝试“认领自己上次的 id”,认领失败再领新号。
- 释放/回收:正常停机主动释放;异常退出由 TTL/会话失效自动回收。
- 防重号护栏:回收后不要立刻无脑复用,至少要校验
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做分布式锁或租约绑定。
- ZooKeeper 可以用
第二层:本地缓存(fallback cache)
本地磁盘可以缓存上次成功分配的 worker id,用于协调中心短时不可用时兜底启动,但本地缓存不能作为唯一真相。美团 Leaf 就是这种思路:首次从 ZooKeeper 取到 workerID 后,会在本机文件系统缓存一个 workerID 文件,以降低对 ZooKeeper 的强依赖。
本地缓存里到底该存什么
不要只存 worker_id。更合理的是至少存:
worker_idinstance_fingerprintboot_uuidlast_timestampversionupdated_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_idbizclusterzoneinstance_idboot_uuidlease_id/session_idlast_timestampstatusupdated_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 + KV 或 ZooKeeper/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”}
- 总体架构:控制面 / 数据面 / Consul 职责边界
- 实例从启动、申请、持有、使用、续约、释放、回收的全生命周期
- 控制面如何做:模型、API、审批、配额、审计、运维
- 业务方接入标准与流程:申请材料、准入门槛、发布流程、验收标准
一、先定几个关键原则
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 = releaseTTL = 15s ~ 30sLockDelay = 5s ~ 15s- 关联
NodeChecks,至少保留默认serfHealth - 不允许创建“无 TTL 永不失效”的 session
原因:
Behavior=release:session 失效时自动释放锁。- TTL 官方要求在
10s ~ 86400s之间;且官方建议使用“尽量低但实用”的 TTL,因为强制过期后锁的清理可能延迟到最多约 2 倍 TTL,因此不要用很长 TTL。 LockDelay默认是 15s,并且必须大于 0。- 如果 session 没有 TTL,就不会自动过期,需要手动删除;这不适合 worker id。
推荐默认值
TTL = 20srenew interval = 6sLockDelay = 5sgraceful 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 配置
控制面返回:
appCodeenvdcworkerPoolStart / workerPoolEndepochclockRollbackStrategycheckpointPathconsulNamespace(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 示例就是:
- create session
- 对指定 key
?acquire=<session> - 其他实例 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:
- 停止接收业务流量
- 停止发号
- flush
maxTimestampSeen到本地文件 - 主动 release key
- destroy session
- 退出
可用接口:
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:应用注册中心
管理对象:
appCodeappNameownerTeamownerUserbizDomainenvdcScopedeployMode(K8s / VM)idSchemaexpectedReplicaspeakQPSpoolMode(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:平台开通
控制面执行:
- 创建 app 元数据
- 分配池范围
- 下发接入配置
- 配置 ACL policy / binding rule
- 打开监控模板
- 给出 SDK 接入文档和验收清单
阶段 4:业务集成
业务方只做四件事:
- 引入 SDK / Sidecar
- 配置 appCode / env / dc
- 使用统一
nextId()接口 - 接入 shutdown hook 和健康检查
业务代码不应该:
- 直接调用 Consul API
- 自己维护 worker id
- 自己设计回收逻辑
- 自己保存长期 Consul token
阶段 5:测试验收
上线前至少要过 6 项测试:
- 并发唯一性测试
- 实例重启重认领测试
- 异常 kill -9 自动回收测试
- 时钟回拨测试
- Consul 短时不可用测试
- 池耗尽测试
未通过,不允许进生产。
阶段 6:生产启用
启用后控制面持续观测:
lease_acquire_latencysession_renew_failuresworker_pool_utilizationclock_rollback_eventsidgen_checkpoint_flush_failures
七、推荐的运行标准
这是建议团队平台直接固化成规范的部分。
1. 统一约束
- 一个 app 在一个
env + dc下有且仅有一个 worker 池配置 - 不允许跨 DC 共用同一 Consul 锁池
- 所有实例必须通过标准 SDK/Sidecar 获取 worker id
- 不允许直接操作
/idgen/leases/*以外的 KV
2. 推荐参数
TTL = 20srenew = 6sLockDelay = 5scheckpoint flush = 1scooldown >= 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”:“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 下限/上限、LockDelay、KV acquire/release、blocking 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_codeapp_nameowner_teamowner_userbiz_domainenvregion_scopedc_scopedeploy_modeid_schema_versionstatus
Worker Pool
worker 号段池。 字段:
pool_codeenvregiondcworker_startworker_endpool_mode(dedicated/shared)status
App-Pool Binding
应用与池的绑定关系。 字段:
app_codepool_codemax_replicaspeak_id_qpsquota_worker_count
Access Request
接入申请单。 字段:
request_noapp_coderequested_byrequested_worker_countdeploy_descrisk_levelstatus
Lease Runtime Snapshot
控制面缓存的运行态快照。 字段:
env/region/dc/app_code/worker_idinstance_idboot_idsession_idlease_statelast_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 = releaseTTL = 20sLockDelay = 5sNodeChecks = ["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 启动
- 进程启动
- SDK/Sidecar 完成身份认证
- 拉取 bootstrap 配置
- 读取本地 checkpoint
- 创建 Consul session
- 优先重认领历史 worker id
- 失败则扫描可用池
- 成功后进入 ACTIVE,开放
nextId()
11.3 使用期
- 所有 ID 纯本地生成
- 后台定时 renew session
- 后台定时 flush
maxTimestampSeen - 定时向控制面上报心跳与 runtime snapshot
11.4 优雅下线
- 进入 DRAINING
- 停止对外提供
nextId() - flush checkpoint
releaseKV lockdestroysession- 进程退出
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 核心内存状态
workerIdregionIddcIdlastTimestampsequencemaxTimestampSeenstate(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 和 maxTimestampSeen12.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/appsGET /api/idgen/apps/{appCode}POST /api/idgen/access-requestsPOST /api/idgen/access-requests/{requestNo}/approvePOST /api/idgen/access-requests/{requestNo}/reject
池管理
POST /api/idgen/poolsPOST /api/idgen/pools/{poolCode}/bind-appPOST /api/idgen/pools/{poolCode}/expandPOST /api/idgen/pools/{poolCode}/freeze
运维
GET /api/idgen/runtime/leasesPOST /api/idgen/runtime/leases/{leaseId}/force-releasePOST /api/idgen/apps/{appCode}/set-readonlyGET /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_qpsidgen_next_id_latency_msidgen_clock_rollback_countidgen_sequence_exhaust_countidgen_session_renew_fail_countidgen_stateidgen_checkpoint_flush_fail_count
19.2 控制面指标
idgen_pool_usage_ratioidgen_active_leasesidgen_bootstrap_fail_countidgen_force_release_countidgen_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. 最终结论
本方案的核心结论是:
- Snowflake 负责本地高性能发号
- Consul 负责 worker id 的运行时租约
- 控制面负责申请、审批、配额、审计、运维
- 业务只接 SDK / Sidecar,不直接碰 Consul 细节
- 出现 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 不进入热路径。
本地状态
workerIdleaseTokenlastTimestampsequencemaxTimestampSeenstate
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 下线:优雅释放
优雅停机流程:
- 停止接受
nextId() - flush 本地 checkpoint
- 更新 DB 最后一次
last_max_timestamp - 释放槽位
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 = 20sheartbeat = 5scooldown = 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 扩展了 acquire 和 release 来做分布式锁;官方的 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 平台控制面
- 底座选 Consul 或 DB
- 发号引擎自己写一层 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 协调层,并定义了 Application、Worker Pool、App-Pool Binding、Access Request、Lease Runtime Snapshot 等对象,同时运行时流程也已经清晰描述了 bootstrap → checkpoint → create session → reclaim/acquire → renew → release/destroy 的完整生命周期。fileciteturn0file0
建议把这个平台拆成 5 个限界上下文(Bounded Context),而不是把所有对象塞进一个“大而全”的 ID 平台域模型里。这样既符合你文档中“控制面 / 数据面 / 运行时协调”的分层,也更利于后续把控制面 API、数据库表、SDK 和运维台拆开实现。这个拆分是我基于方案结构做的领域抽象。fileciteturn0file0
一、领域分析结论
1)核心域与支撑域划分
建议这样划:
-
核心域(Core Domain)
- 运行时租约域 Runtime Lease
- 本地发号域 ID Generation
-
支撑域(Supporting Domain)
- 接入治理域 Access Governance
- 池与配额治理域 Pool Governance
- 运维审计域 Operations & Audit
-
通用域(Generic Subdomain)
- 身份与授权域 Identity & ACL
原因很简单: 真正决定这个平台技术价值和正确性的,不是“应用申请单”本身,而是 worker id 的唯一持有、续约、回收,以及 lease 丢失时必须 fail closed 停止发号。这些在你的文档里是最关键的系统约束。fileciteturn0file0
2)最重要的领域主线
这个平台的业务主线其实只有一条:
应用被准入 → 获得池与配额 → 运行实例启动 → 成功持有 worker lease → 本地 Snowflake 发号 → 续约/释放/回收 → 运维与审计追踪
这条主线已经在你的接入流程、生命周期、控制面 API 和监控设计里完整出现了。fileciteturn0file0
二、推荐的限界上下文
1. 接入治理域 Access Governance
负责“谁可以接入平台”。
核心对象:
ApplicationAccessRequestAdmissionPolicy
职责:
- 应用注册
- 接入申请
- 审批通过 / 驳回
- 生产准入校验
2. 池与配额治理域 Pool Governance
负责“给哪个应用分配多少 worker 资源”。
核心对象:
WorkerPoolPoolAllocation(建议替代文档里的App-Pool Binding作为正式聚合名)QuotaPolicy
职责:
- dedicated/shared 池管理
- worker 范围规划
- 应用与池绑定
- 配额、峰值副本数、峰值 ID QPS 管理
3. 运行时租约域 Runtime Lease
负责“哪一个运行实例当前合法持有哪个 worker id”。
核心对象:
RuntimeLeaseLeaseHolderLeasePolicy
职责:
- reclaim / acquire
- renew
- release
- expire / cooldown
- force release
4. 本地发号域 ID Generation
负责“本地如何安全、快速地产生唯一 ID”。
核心对象:
GeneratorNodeIdSchemaCheckpointGeneratorState
职责:
nextId()- sequence 递增
- 时钟回拨防护
- checkpoint 刷盘
- lease 不确定时停发
5. 运维审计域 Operations & Audit
负责“看见系统状态并做治理动作”。
核心对象:
LeaseRuntimeSnapshotPoolUsageViewAuditRecordOperationCommand
职责:
- 活跃租约视图
- 池使用率
- 强制摘除
- 冻结应用
- 审计追踪
这里我要特别指出一个关键建模调整:
LeaseRuntimeSnapshot不应该作为核心领域聚合,而应该视为读模型(Read Model / Projection)。
因为你文档里已经写得很清楚,它是“控制面缓存的运行态快照”,它更像运维台视图,不是运行时租约的真相源。fileciteturn0file0
三、聚合根建议
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)在同一时刻最多只有一个ACTIVElease - lease 失效后必须进入
EXPIRED或COOLDOWN - 失去 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 等。fileciteturn0file0
五、领域服务建议
这些更适合建成领域服务,而不是塞进某个实体:
AdmissionService:接入申请、审批、准入判定PoolPlanningService:池规划、配额校验LeaseAllocationService:reclaim / acquire worker idLeaseRenewService:session renew 与租约续期LeaseRecoveryService:expire / cooldown / force releaseIdGenerationService:本地 nextId 生成BootstrapService:拉取 bootstrap 配置并初始化节点RuntimeObservationService:runtime snapshot 投影与运维查询
这些服务,都能在你现有文档中找到对应职责来源。fileciteturn0file0
六、领域事件建议
这套系统很适合事件化,因为很多状态变化都需要被运维、审计、监控和控制面感知。
推荐事件:
ApplicationRegisteredAccessRequestedAccessApprovedAccessRejectedPoolAllocatedPoolExpandedWorkerLeaseAcquiredWorkerLeaseRenewedWorkerLeaseReleasedWorkerLeaseExpiredWorkerLeaseForceReleasedGeneratorEnteredReadOnlyClockRollbackDetectedPoolExhaustedCheckpointFlushed
这些事件都与文档中的 API、状态机、告警规则和异常矩阵天然对应。fileciteturn0file0
七、领域模型图(限界上下文图)
以下这张图,先画上下文与关系:
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 直接抽出来的。fileciteturn0file0
八、领域模型图(聚合/实体/值对象图)
以下这张图,画得更接近“聚合根 + 值对象 + 读模型”:
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
这已经不是单纯关联表了,而是一个有独立生命周期和业务约束的领域对象。fileciteturn0file0
2)Lease Runtime Snapshot 不要当真相模型
它应该是 projection / read model。
真正的真相模型应该是 RuntimeLease 聚合,snapshot 只是控制面为了查询和运维投影出来的视图。你文档里已经明确把它描述成“控制面缓存的运行态快照”。fileciteturn0file0
3)Checkpoint 不应进入控制面管理聚合
它更适合作为 GeneratorNode 的本地值对象。
因为 checkpoint 的职责是:
- 重认领原 worker id
- 提供时钟安全下界
- 降低短时控制面不可用影响 这些都是本地发号执行面的语义,不是控制面治理语义。fileciteturn0file0
4)IdSchema 建议显式建模
当前文档里 bit 位方案、epoch、schema_version 已经出现,但还分散在字段和文字里。建议显式拉成 IdSchema 值对象,未来支持 v1 / v2 版本演进时会更干净。fileciteturn0file0
5)安全域独立出来更好
你文档里 Token / ACL / Auth Method 已经单独成块,说明它天然是一个独立上下文,而不是 Application 聚合里的普通字段。fileciteturn0file0
十、最终结论
这份方案的领域模型,最合理的落法不是“控制面一堆表 + 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 的强调直接推出来的。fileciteturn0file0
你要的话,我下一条可以继续把这套领域模型展开成 《聚合边界 + 仓储接口 + 领域服务接口 + 事件清单》。
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 AllocationLease Runtime Snapshot不应作为真相模型,而应视为控制面运维视图的 Read Model / Projection- 本地
Checkpoint应视为数据面Generator Node的值对象,而非控制面主数据 IdSchema、LeasePolicy、DeploymentScope、QuotaPolicy建议显式建模为值对象,以承载规则和约束
7.3 限界上下文划分
建议将平台拆分为以下 5 个限界上下文:
-
接入治理域(Access Governance) 负责应用注册、接入申请、审批、生产准入校验。
-
池与配额治理域(Pool Governance) 负责 worker 池规划、应用与池绑定、资源配额和容量规则。
-
运行时租约域(Runtime Lease) 负责 worker id 的 reclaim / acquire / renew / release / expire / cooldown。
-
本地发号域(ID Generation) 负责本地 Snowflake 发号、sequence 管理、时钟回拨保护、checkpoint 刷盘。
-
运维审计域(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、状态
- 决定应用是否可进入后续申请和生产启用流程
聚合内对象:
ApplicationDeploymentScope
不变量:
appCode全局唯一- 未注册应用不得发起接入审批
- 已冻结应用不得发起新的生产启用与 pool 分配
不属于该聚合:
- 运行态租约
- worker 池容量
- checkpoint 本地状态
24.2 AccessRequest 聚合边界
职责:
- 表达一次接入或变更申请
- 管理提交、审批、驳回、关闭等状态流转
聚合内对象:
AccessRequest
不变量:
- 一个申请单同一时刻只能处于一个明确审批状态
- 驳回后不得直接启用,必须重新发起申请
- 审批完成后必须关联到明确的
Application
24.3 WorkerPool 聚合边界
职责:
- 管理 worker 资源池的基本属性和可用性
- 管理 dedicated / shared 模式、冻结、扩容、停用
聚合内对象:
WorkerPoolWorkerRange
不变量:
- 同一
(env, region, dc)下,同一 worker 范围不得重叠 - 冻结中的池不得再新分配
- 停用中的池不得再扩容
24.4 PoolAllocation 聚合边界
职责:
- 表达应用与 worker 池的业务约束关系
- 管理 quota、max_replicas、peak_id_qps 等资源约束
聚合内对象:
PoolAllocationQuotaPolicy
不变量:
- quota 不得超过池可用容量
- 未建立 allocation 的应用不得申请运行时 worker id
- allocation 被禁用后,新实例不得再成功 bootstrap 获取可用池范围
24.5 RuntimeLease 聚合边界
职责:
- 表达单个
(env, region, dc, appCode, workerId)的运行时合法持有关系 - 管理 acquire、renew、release、expire、cooldown、force-release
聚合内对象:
RuntimeLeaseLeaseHolderLeasePolicy
不变量:
- 同一
(env, region, dc, appCode, workerId)任一时刻最多只有一个ACTIVElease - lease 丢失、session 不确定、续约失败达到阈值时必须进入不可发号状态
- cooldown 未结束前不得重新分配
说明:
LeaseRuntimeSnapshot不属于该聚合,它是读模型- Consul session / KV lock 是该聚合的基础设施实现,而不是聚合内部对象
24.6 GeneratorNode 聚合边界
职责:
- 管理本地 Snowflake 发号状态
- 管理 sequence、lastTimestamp、maxTimestampSeen、checkpoint
- 基于 lease 合法性决定是否允许
nextId()
聚合内对象:
GeneratorNodeIdSchemaCheckpoint
不变量:
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 接入治理域事件
ApplicationRegisteredApplicationFrozenApplicationEnabledAccessRequestedAccessApprovedAccessRejected
27.2 池与配额治理域事件
WorkerPoolCreatedWorkerPoolExpandedWorkerPoolFrozenPoolAllocatedPoolQuotaChangedPoolAllocationDisabled
27.3 运行时租约域事件
WorkerLeaseReclaimedWorkerLeaseAcquiredWorkerLeaseRenewedWorkerLeaseReleasedWorkerLeaseExpiredWorkerLeaseEnteredCooldownWorkerLeaseForceReleasedPoolExhausted
27.4 本地发号域事件
GeneratorActivatedGeneratorEnteredReadOnlyGeneratorDrainingIdGeneratedSequenceExhaustedClockRollbackDetectedCheckpointFlushed
27.5 运维审计域事件
RuntimeHeartbeatReportedLeaseRuntimeSnapshotUpdatedPoolUsageCalculatedForceReleaseRequestedReadonlySwitchRequestedAuditRecordAppended
27.6 事件字段建议
所有领域事件建议统一携带以下元字段:
eventIdeventTypeoccurredAtappCodeenvregiondcoperatortraceIdpayloadVersion
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),不再当作真相聚合
要的话,