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

发布于:2025-07-23 ⋅ 阅读:(14) ⋅ 点赞:(0)

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


一、背景

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

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

市面上虽然有很多现成的 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
自增ID 6位 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 + 时间 + 用户 + 业务组合保障
可读性高 能看出订单类型、用户、时间等信息
结构清晰 易于日志追踪和调试
分布式支持 机器码支持多节点部署
易于维护 支持定时清理、日志记录、异常监控

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



网站公告

今日签到

点亮在社区的每一天
去签到