分布式 ID 设计实战:订单号、优惠券、一码付与业务 ID 生成策略
讨论 ID 生成的文章很多,但多数停在算法层;真实项目里决定设计的从来不是算法,而是业务约束:订单号要防爬、日志 ID 要零成本、券码要防爆破。本文演示一条完整路径:如何从业务约束反推技术指标,如何确定时间、机器、序列各分多少位,以及什么时候该自研、什么时候直接用现成方案。
它解决什么问题
技术选型文章会告诉你 Snowflake 有 41 位时间戳,却不会告诉你为什么订单号不能直接用数据库自增 ID。把自增 ID 暴露给外部的风险很具体:竞对连续两天下单,用订单号差值就能算出你的日单量;攻击者从 1 开始递增遍历,就能拖走全部订单数据。业务 ID 设计的第一课是:ID 是系统对外的信息出口,每一位都在泄露信息,要么泄露得有控制,要么泄露得毫无察觉。
不同业务对 ID 的诉求差异极大,甚至互相矛盾。订单号要能在客服电话里口述,所以要短、要局部可读、最好带日期;但又要防遍历,所以不能纯连号。优惠券码要印在瓶盖和海报上,必须预先生成几十万张、离线可校验、无法被批量伪造。链路追踪的 TraceId 则完全相反:它不上前台、不给人看,唯一要求是生成成本趋近于零——每个请求都要产生一个,绝不能为此多一次网络调用。用同一把尺子量所有 ID,是 ID 设计最常见的错误。
核心原理
业务 ID 设计的方法论可以归纳成四步:
第一步,列约束清单。 把业务方、客服、风控、安全、运维各方的要求全部摆出来,通常落在六个维度:唯一性、有序性、长度、可读性、安全性、生成成本。
第二步,约束翻译成技术指标。 “防爬”翻译成”相邻订单号不可推导”,即要引入随机段或哈希段;”客服可查”翻译成”前缀含日期与业务线”;”印在瓶盖上”翻译成”Base32/Base62 字符集、剔除易混淆字符”;”成本趋零”翻译成”本地生成、无网络依赖”。翻译完成后,方案空间会急剧收窄。
第三步,定位生成位置。 中心发号(DB 号段、发号服务)管控强、易加审计,但每次取号有网络成本,且引入外部依赖;本地生成(Snowflake、UUID、规则拼接)零延迟零依赖,代价是机器位治理和时钟敏感。这一步本质是分布式系统的经典取舍:一致性与管控换可用性与延迟。
第四步,位分配。 总位数确定后,把时间、机器、序列、随机、校验各段的位数一一敲定,这是下一节的主题。
一个贯穿全程的原则是内外 ID 分离:对内主键用 bigint 的 Snowflake 或号段,保证聚簇索引友好;对外的订单号、券码单独编码,可以带日期、随机段、校验位。两套 ID 解耦后,对内可以换发号方案,对外可以加风控规则,互不牵制。
深入机制
位分配怎么定
位分配不是玄学,每一步都有计算公式。以类 Snowflake 结构为例:
时间位定寿命。 毫秒级时间戳每多一位,可用年限翻倍。41 bit 是 2^41 毫秒 ≈ 69.7 年,从自定义上线起点算起够用;若业务只需要秒级精度且希望 ID 更短,33 bit 的秒级时间戳约 272 年,能把省下的 8 位让给序列号或随机段。反过来,时间位砍太多会让”寿命”先于业务量耗尽,扩容时被迫升位,成本极高。
序列位定峰值。 单节点每毫秒的序列容量是 2^n。12 bit 给 4096 个,意味着单节点峰值 409.6 万 QPS——单节点永远到不了这个量,真正的瓶颈是机器位。定序列位时用”单节点峰值 QPS ÷ 1000”反推,再留 4 到 8 倍余量应对热点。
机器位定规模。 按”机房数 × 每机房最大节点数”计算,1024 节点内用 10 bit,超过就要么加位、要么改注册复用机制。容器环境下节点漂移频繁,机器位必须启动时向注册中心申领并落盘缓存。
校验位与随机盐定安全。 券码类 ID 需要校验位:一段 CRC 或哈希截断,让离线核销端不查库就能筛掉伪造码,同时校验位的散列性还能打平序列段的规律。订单号的防遍历则靠随机段:在序列段里混入随机位,使相邻两单号码无规律可循。
把这套方法套到券码上就是一道完整算术题:编码空间用 60 个字符(剔除易混淆的 O 和 I),12 位券码对应 60^12 的组合空间;二进制展开约 61 bit,按”方案 ID 15 bit(3.2 万场活动)+ 序列号 30 bit(单场 10 亿张)+ 校验位 13 bit(前后两段散列)”切分,容量、隔离性、防伪性一次配齐。
TraceId:低成本约束下的极简设计
日志 TraceId 是位分配方法的反面教材式应用——所有奢侈设计都被砍掉。它的约束只有两条:全局唯一、生成成本趋近于零。常见结构是”IP + 时间戳 + 自增序列 + 进程号”:8 位十六进制 IP 定位首台机器,13 位毫秒时间戳,4 位自增序列滚动复用,5 位进程号防止单机多进程撞号。没有任何注册中心、没有持久化、没有校验位,因为漏一条日志 ID 冲突的代价是排查变难,而不是资损——约束的宽松直接映射为设计的极简。这恰好说明了方法论的有效性:不是把所有 ID 都设计成 Snowflake,而是让每个 ID 的复杂度匹配它的约束清单。
工程实践
- 订单号:对内主键用 Snowflake;对外订单号按”日期 8 位 + 业务线 2 位 + 随机 4 位 + 序列”拼接,客服报日期就能定位分表,随机段阻断遍历。预留版本前缀,规则升级时老号仍可解析。
- 券码:预生成批量入库或纯规则离线校验;字符集剔除
0/O、1/I这类易混淆字符;生成侧加限流和批量作废能力,核销侧记录审计日志。 - 支付码/一码付:码值本质是带唯一参数的短链,绑定用户、商品与会话,设 TTL 且消费一次即失效;动态码把”选商品”环节前置,扫码后服务端才组装真实订单。
- 何时用现成方案:内部主键、普通业务 ID,直接用 Leaf、Tinyid 或成熟 Snowflake 库,自研发号器的收益远不抵 workerId 治理、时钟回拨这些隐性成本。
- 何时自研:自研的从来不是发号算法,而是编码规则层——业务前缀、随机段、校验位、分片基因的拼装与解析。这层与业务强耦合,没有现成轮子,也必须自己掌控。
- 上线前必做:压测峰值下短码空间消耗速率并设使用率告警;演练时钟回拨与发号存储故障切换;落库侧保留唯一索引兜底。
对比与权衡
| 场景 | 核心约束 | 关键指标 | 生成位置 | 推荐策略 |
|---|---|---|---|---|
| 订单号(对外) | 防爬、可口述、可查 | 相邻不可推导、含日期 | 本地拼接 | 日期+业务线+随机段+序列,与内部主键分离 |
| 订单主键(对内) | 索引友好 | 趋势递增、64 bit | 本地/号段 | Snowflake 或 Leaf-segment |
| 优惠券码 | 防爆破、离线核销 | 大编码空间、带校验位 | 预生成 | 方案ID+序列+校验位,Base60/62 编码 |
| 支付码 | 防重放 | 一次性、短 TTL | 中心签发 | 短链参数绑定会话,消费即失效 |
| TraceId | 成本趋零 | 本地无依赖、单机唯一 | 本地 | IP+时间戳+滚动序列+进程号 |
| 短网址码 | 长度极致压缩 | 高进制、短字符集 | 号段+转码 | 数字 ID 转 Base62 |
权衡的主轴只有两条:可读性与安全性相互拉扯,用”内外 ID 分离”解耦;中心化管控与本地化成本相互拉扯,用”核心链路中心签发、高频内部 ID 本地生成”划界。
面试视角
为什么不能把数据库自增 ID 直接暴露给外部? 三类风险:商业信息泄露,竞对用相邻两天的 ID 差值推算日单量;数据遍历,攻击者递增枚举拖库;规律暴露,纯连号让伪造单号零成本。标准做法是对内 bigint 主键与对外编码单号分离,对外侧加日期、随机段与校验位。
设计订单号要考虑哪些点? 按约束清单回答:唯一性是底线;安全性要求编号不连号、不透露销量,引入随机段;可读性要求长度适中、局部有规律,前缀带日期与业务线方便客服定位;查询效率上纯数字优于混合字符;最后预留版本位应对规则演进。
位分配时时间位和序列位怎么定? 时间位定寿命:41 bit 毫秒约 69 年,按业务预期年限和起始点反推,砍位换短 ID 时先算清寿命账。序列位定峰值:2^n 是单节点每毫秒容量,用单节点峰值 QPS 除以一千再留数倍余量。机器位按机房乘节点规模定,容器环境必须配注册申领机制。
券码怎么防爆破和伪造? 三层:编码空间足够大使暴力枚举不可行,12 位 Base60 是 60 的 12 次方种组合;校验位让核销端离线筛掉伪造码,不查库即可拒绝;风控兜底,单用户短时大量尝试触发限流与黑名单。另外活动间用方案 ID 隔离,防跨场复用。
什么时候该自研发号服务? 几乎不该自研发号算法本身——Leaf、Tinyid 已经解决了双缓冲预加载、workerId 注册、时钟回拨校验这些硬骨头。值得自研的是业务编码层:订单号规则、券码编解码、分片基因嵌入,这层与业务强耦合且无现成方案。判断标准是:自研部分是否承载独有业务规则,是才值得投入。
小结
- 业务 ID 设计的起点是约束清单而非算法:先把防爬、可读、成本等业务诉求翻译成唯一性、随机段、本地生成等技术指标,方案空间自然收窄。
- 位分配有公式可循:时间位定寿命、序列位定峰值、机器位定规模、校验位与随机盐定安全,每一段位数都能算出来。
- TraceId 的极简设计证明方法论的普适性:约束宽松处设计从简,约束严苛处设计从重。
- 内外 ID 分离是最重要的工程纪律:对内主键保索引友好,对外单号保安全可读,两者解耦互不牵制。
- 复用与自研的边界清晰:发号算法用 Leaf/Tinyid 等现成方案,业务编码规则层才是值得自研的部分。

