我是怎么設計一個訂單號生成策略的(庫存系統)
一、背景
最近我在做一套自研的庫存管理系統,其中有一個看似簡單、實則很關鍵的功能:訂單號生成策略。
訂單號不僅要全局唯一,還要有一定的可讀性和業務含義,比如能一眼看出是入庫單還是出庫單、是哪個用戶、什么時間生成的。這樣在后續查日志、對賬、排查問題的時候才更方便。
市面上雖然有很多現成的 ID 生成方案,比如 UUID、Snowflake、Leaf 等等,但它們都有一個共同的問題:沒有業務語義。
比如這個訂單號到底是入庫單還是出庫單?用戶是誰?哪天生成的?看不出來。
所以,我決定自己設計一個訂單號生成策略,結合 Redis、時間戳、業務標識和用戶信息,實現一個既唯一、又可讀、還易維護的方案。
二、我遇到的問題
在設計過程中,我遇到了幾個關鍵問題:
- 如何保證訂單號全局唯一?
- 怎么讓訂單號有業務含義?
- 如何支持分布式部署?
- Redis 生成自增ID會不會成為瓶頸?
- 如何避免 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);}
}
五、難點與優化點
難點:
-
如何保證訂單號唯一性?
答案是:Redis + 業務類型 + 時間 + 用戶ID組合,保障唯一。 -
如何避免 Redis 成為瓶頸?
Redis 的INCR
是原子操作,性能很好,但為了進一步優化,也可以采用“分段緩存”機制,比如一次取100個ID本地緩存使用。 -
如何讓訂單號具備可讀性?
通過字段拼接,讓訂單號包含業務類型、用戶、時間等信息,方便日志追蹤。
優化建議:
- 分段自增機制:減少 Redis 調用頻率
- 用戶ID壓縮算法:如 CRC32 或 MurmurHash,生成更短的用戶碼
- 日志與監控:記錄生成的訂單號,異常時自動報警
- 多機部署支持:通過機器碼區分不同節點,避免沖突
六、實際效果如何?
這套訂單號生成策略在我們系統中上線后,運行穩定,效果不錯:
- 訂單號唯一性得到保障,未出現重復
- 日志追蹤、對賬、排查問題都變得容易
- Redis 性能良好,未出現瓶頸
- 支持多節點部署,適配分布式環境
七、總結
通過結合 Redis 自增ID、業務標識、時間戳和用戶信息,我實現了一個適合庫存系統的訂單號生成策略,具有以下優勢:
優勢 | 說明 |
---|---|
唯一性強 | Redis + 時間 + 用戶 + 業務組合保障 |
可讀性高 | 能看出訂單類型、用戶、時間等信息 |
結構清晰 | 易于日志追蹤和調試 |
分布式支持 | 機器碼支持多節點部署 |
易于維護 | 支持定時清理、日志記錄、異常監控 |
如果你也在開發類似的庫存系統、訂單系統或支付系統,不妨參考這套方案。希望這篇文章能對你有所幫助!