引言
在供应链与仓储管理系统开发中,编码设计与唯一标识生成是基础但至关重要的环节。一套科学、可扩展的编码体系不仅能提升人工操作效率,更是系统间数据交互、财务对账、库存追溯的基石。本文结合领域驱动设计(DDD)思想与分布式系统实践经验,系统梳理了SKU编码、业务单据号、库存业务编码的设计原则,并对比了数据库自增、号段模式、雪花算法等主流ID生成策略的适用场景,希望能为同类系统的开发者提供参考。
一、SKU编码:稳定标识与灵活属性的平衡
SKU(Stock Keeping Unit,库存量单位)是商品主数据的核心标识。在设计SKU编码时,我们面临两个互斥的目标:编码本身应具有业务可读性,同时又必须保持长期稳定(一旦生成,绝不修改)。经过多轮讨论,我们确立了以下原则:
- 唯一性与稳定性优先 :SKU编码一旦分配给某个商品,其生命周期内不可变更。因此,编码中不宜包含易变的业务属性(如供应商、商品分类、销售渠道等),否则当这些属性调整时,编码将被迫修改,导致历史订单、库存标签失效。
- 适度可读,保持简短 :可通过一级大类代码 + 年份/月份 + 流水号 的结构实现基本可读性。例如,
FO2401001(食品类,2024年1月第001号)。大类代码(2位字母)极少变动,年份月份仅为时间参考,不参与业务逻辑。 - 扩展属性通过关联表管理:商品分类、品牌、供应商、规格参数等属性应存放在独立的表中,SKU通过外键或值对象引用。这样既能支持复杂的多维查询,又不会破坏编码的稳定性。
在DDD架构中,我们明确将Category(分类)与Sku设计为两个独立的聚合根。Sku聚合中仅保存CategoryId,不保存分类名称;修改分类名称或调整分类树时,无需触碰任何SKU记录。查询时通过CQRS读模型或应用层关联获取分类信息。
二、业务单据号:可读性、唯一性与高可用
入库单号、出库单号等业务单据需要满足以下特性:
- 业务可读 :通常包含日期(如
IN202412200001),便于线下沟通与快速识别。 - 全局唯一:同一租户下不能重复。
- 趋势递增:有利于数据库索引性能。
- 高可用:生成服务不能成为单点瓶颈。
我们选择数据库号段模式(Segment) 作为单据号生成的核心技术。该模式在保证唯一性的同时,通过批量获取号段大幅降低数据库压力,且天然支持按日期重置序号。
实现要点
- 号段表结构:
id_allocator(biz_tag, max_id, step) - 将日期编码到
biz_tag中,例如inbound_no_20241220。这样每天的序号独立,自动从1开始。 - 应用层缓存号段,使用双Buffer预加载避免号段耗尽时的阻塞等待。
- 使用
SELECT FOR UPDATE保证并发安全,配合唯一索引实现不存在则插入的原子语义。
该方案已在生产环境中支撑日均十万级单据量,性能与可靠性均满足要求。
三、库存业务编码(stock_no):仓库+SKU+批次+序号
库存记录除了技术主键stock_id外,通常还需要一个业务可见的编码,用于打印货位标签、人工盘点、与外协系统交换数据。我们设计的格式为:
- 启用批次管理 :
[仓库码(4)] + [SkuCode] + [批次日期(6)] + [序号(6)]
示例:WH01SK2401241220000001 - 不启用批次管理 :
[仓库码(4)] + [SkuCode] + [序号(6)]
示例:WH01SK2401000001
序号生成同样采用号段模式,但分组键根据是否启用批次而不同:
- 有批次时,分组键 =
仓库码 + SkuCode + 批次日期(批次日期为yyMMdd),每个批次独立计数。 - 无批次时,分组键 =
仓库码 + SkuCode(可选追加年份实现年度重置)。
注意:stock_no应包含货位信息 吗?不包含。货位是可变的,移动货位时只更新数据库中的location_no字段,stock_no保持不变。这确保了已打印的标签长期有效。
四、分布式ID生成策略对比
在系统设计中,我们同时面临多种ID生成需求:技术主键(如stock_id)、业务单据号、库存编码等。下表总结了常见方案的适用场景:
| 方案 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
| 数据库自增 | 简单、有序、事务内方便 | 分库分表困难、性能瓶颈 | 单库、低并发、非核心表 |
| 号段模式(Leaf Segment) | 高性能、可重置、强一致 | 需维护数据库表、应用缓存 | 业务单据号、每日重置的编码 |
| 雪花算法(Snowflake) | 无中心、高性能、全局唯一 | 依赖时钟、需管理workerId | 技术主键、超高并发、分布式环境 |
| Redis原子自增 | 极高性能、简单 | 持久化风险、可能丢失序列 | 容忍丢失的非关键流水号 |
对于技术主键(无业务含义),我们采用雪花算法 ,并通过ZooKeeper动态分配workerId,避免了人工配置的繁琐和时钟回拨问题。对于业务单据号和库存编码,我们统一使用数据库号段模式,因为它天然支持按日期/批次重置序号,且实现简单可靠。
五、号段模式的高阶实践
为了使号段模式在仓储系统中发挥最大效用,我们总结了以下实践要点:
- 合理设置步长(step):根据业务量调整,如日均单据量1万,步长可设为1000~5000。过小会增加数据库请求,过大会浪费ID。
- 双Buffer预加载:当当前号段使用超过80%时,异步加载下一个号段,彻底消除号段切换时的等待。
- 分组键设计:对于需要按天重置的编码,将日期作为分组键的一部分;对于不需要重置的,分组键固定。不同分组的数据在数据库中独立,互不影响。
- 缓存清理策略:对于带日期的分组键,当日期变为旧日期后,缓存中的号段不会再被使用,可设置定期清理或采用软引用自动回收。
- 监控与告警:监控号段消耗速率、数据库锁等待时间、号段分配失败次数,以便及时发现性能瓶颈或配置不当。
六、总结
仓储系统中的编码设计是一项需要兼顾业务语义、技术实现与长期演进的系统工程。通过将稳定属性固化在编码中 、易变属性剥离到关联表 ,我们可以获得既清晰又灵活的编码体系。在ID生成技术上,数据库号段模式 以其平衡的性能、可靠性和易于实现的特点,成为业务单据号和库存编码的首选;而雪花算法则更适合无业务含义的技术主键,尤其是在分库分表或微服务架构下。
最后,无论选择哪种方案,都应当将编码规则文档化,并在代码中通过统一的生成服务(领域服务或应用服务)来强制执行,避免各模块随意创造编码逻辑。希望本文的实践总结能帮助读者少走弯路,构建出健壮、可扩展的仓储管理系统。