当前位置: 首页 > news >正文

我是怎么设计一个订单号生成策略的(库存系统)

我是怎么设计一个订单号生成策略的(库存系统)


一、背景

最近我在做一套自研的库存管理系统,其中有一个看似简单、实则很关键的功能:订单号生成策略

订单号不仅要全局唯一,还要有一定的可读性业务含义,比如能一眼看出是入库单还是出库单、是哪个用户、什么时间生成的。这样在后续查日志、对账、排查问题的时候才更方便。

市面上虽然有很多现成的 ID 生成方案,比如 UUID、Snowflake、Leaf 等等,但它们都有一个共同的问题:没有业务语义

比如这个订单号到底是入库单还是出库单?用户是谁?哪天生成的?看不出来。

所以,我决定自己设计一个订单号生成策略,结合 Redis、时间戳、业务标识和用户信息,实现一个既唯一、又可读、还易维护的方案。


二、我遇到的问题

在设计过程中,我遇到了几个关键问题:

  1. 如何保证订单号全局唯一?
  2. 怎么让订单号有业务含义?
  3. 如何支持分布式部署?
  4. Redis 生成自增ID会不会成为瓶颈?
  5. 如何避免 Redis Key 堆积?

带着这些问题,我开始一步步设计我的订单号生成逻辑。


三、我是怎么做的?

我最终设计了一个订单号结构如下:

[业务码][机器码][用户码][日期][自增ID]

每个字段的含义如下:

字段长度示例说明
业务码2位RK、CK入库(RK)、出库(CK)
机器码2位01、02表示部署节点
用户码6位000123用户ID后6位
日期8位20250719格式为YYYYMMDD
自增ID6位000001每天从1开始递增

示例订单号:

RK0100012320250719000001
  • RK:入库订单
  • 01:机器编号
  • 000123:用户ID后6位
  • 20250719:订单生成日期
  • 000001:当天该用户该业务类型的第一个订单

四、技术实现细节

1. 使用 Redis 生成自增ID

我用 Redis 的 INCR 命令来生成每天的自增ID,Key 的格式如下:

order:${businessType}:${date}:${userCode}

例如:

INCR order:RK:20250719:000123

这样可以保证:

  • 同一用户、同一天、同一业务类型的订单号唯一
  • 每天自动重置计数,避免ID无限增长

2. Java 实现代码

public class OrderNoGenerator {private RedisTemplate<String, String> redisTemplate;public OrderNoGenerator(RedisTemplate<String, String> redisTemplate) {this.redisTemplate = redisTemplate;}public String generateOrderNo(String businessType, int machineId, long userId) {// 1. 业务类型(2位)String bizCode = businessType;// 2. 机器ID(2位)String machineCode = String.format("%02d", machineId);// 3. 用户ID(6位)String userCode = String.format("%06d", userId % 1000000); // 截取后6位// 4. 当前日期(8位)String dateCode = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);// 5. Redis 自增ID(6位)String redisKey = String.format("order:%s:%s:%s", bizCode, dateCode, userCode);Long incrId = redisTemplate.opsForValue().increment(redisKey);String incrCode = String.format("%06d", incrId);// 6. 组装订单号return bizCode + machineCode + userCode + dateCode + incrCode;}
}

3. Redis Key 管理与清理

为了避免 Redis Key 无限增长,我加了一个定时任务,每天凌晨清理前一天的 Key:

@Scheduled(cron = "0 0 0 * * ?")
public void clearYesterdayOrderKeys() {String yesterday = LocalDate.now().minusDays(1).format(DateTimeFormatter.BASIC_ISO_DATE);Set<String> keys = redisTemplate.keys("order:*:" + yesterday + ":*");if (keys != null && !keys.isEmpty()) {redisTemplate.delete(keys);}
}

五、难点与优化点

难点:

  1. 如何保证订单号唯一性?
    答案是:Redis + 业务类型 + 时间 + 用户ID组合,保障唯一。

  2. 如何避免 Redis 成为瓶颈?
    Redis 的 INCR 是原子操作,性能很好,但为了进一步优化,也可以采用“分段缓存”机制,比如一次取100个ID本地缓存使用。

  3. 如何让订单号具备可读性?
    通过字段拼接,让订单号包含业务类型、用户、时间等信息,方便日志追踪。

优化建议:

  • 分段自增机制:减少 Redis 调用频率
  • 用户ID压缩算法:如 CRC32 或 MurmurHash,生成更短的用户码
  • 日志与监控:记录生成的订单号,异常时自动报警
  • 多机部署支持:通过机器码区分不同节点,避免冲突

六、实际效果如何?

这套订单号生成策略在我们系统中上线后,运行稳定,效果不错:

  • 订单号唯一性得到保障,未出现重复
  • 日志追踪、对账、排查问题都变得容易
  • Redis 性能良好,未出现瓶颈
  • 支持多节点部署,适配分布式环境

七、总结

通过结合 Redis 自增ID、业务标识、时间戳和用户信息,我实现了一个适合库存系统的订单号生成策略,具有以下优势:

优势说明
唯一性强Redis + 时间 + 用户 + 业务组合保障
可读性高能看出订单类型、用户、时间等信息
结构清晰易于日志追踪和调试
分布式支持机器码支持多节点部署
易于维护支持定时清理、日志记录、异常监控

如果你也在开发类似的库存系统、订单系统或支付系统,不妨参考这套方案。希望这篇文章能对你有所帮助!


http://www.dtcms.com/a/288083.html

相关文章:

  • 带root权限_新魔百和cm311-5_gk6323不分代工通刷优盘强刷及线刷
  • Openlayers 面试题及答案180道(141-160)
  • JavaScript 中的继承
  • MySQL——约束类型
  • 【RK3576】【Android14】分区划分
  • Java行为型模式---中介者模式
  • HOT100——排序篇Leetcode215. 数组中的第K个最大元素
  • 深度解析 rag-vector-agent-semantic-kernel:基于 Semantic Kernel 的 Agentic RAG 实践
  • 变频器实习Day10
  • JS原型相关知识
  • EINO框架解读:字节跳动开源的大模型应用开发框架
  • 【jquery详细讲解】
  • Vue Swiper组件
  • Vue组件化开发小案例
  • 在开发板tmp目录下传输文件很快的原因和注意事项:重启开发板会清空tmp文件夹,记得复制文件到其他地方命令如下(cp 文件所在路径 文件要复制到的路径—)
  • GitLab 社区版 10.8.4 安装、汉化与使用教程
  • GPU集群如何规划
  • 子串算法题
  • Web攻防-身份验证篇JWT令牌空密钥未签名密钥爆破JWKJWUKID算法替换CVE报告复盘
  • 在Vscode中使用Kimi K2模型:实践指南,三分钟生成个小游戏
  • TypeScript 中的「类」:从语法到实战的完整指南
  • 论C/C++的条件编译#if、#ifdef、#ifndef、#undef
  • Promise入门
  • 三级知识点汇总(详解)【c++】——2
  • 我用Cursor,1周上线了一个虚拟资料流量主小程序技术选型
  • Linux“一切皆文件“设计哲学 与 Linux文件抽象层:struct file与file_operations的架构解析
  • 【ChatOpenAI】常用方法详解
  • HOT100——动态规划篇Leetcode221. 最大正方形
  • C++ std::thread线程类 相关问题、函数总结
  • 单调队列深度解析(下)