文章目录
- 前言
- 一、核心思路
- [二、Snowflake 算法实现(Java版)](#二、Snowflake 算法实现(Java版))
-
-
- [1️⃣ Snowflake 工具类](#1️⃣ Snowflake 工具类)
-
- [三、订单号生成 Service](#三、订单号生成 Service)
-
-
- [2️⃣ 订单号生成器](#2️⃣ 订单号生成器)
-
- [四、Controller 测试](#四、Controller 测试)
- 五、生产环境必须注意(重点)
-
-
- [✅ 1. machineId / datacenterId 不能乱写](#✅ 1. machineId / datacenterId 不能乱写)
- [✅ 2. 时钟回拨问题](#✅ 2. 时钟回拨问题)
- [✅ 3. 高并发优化](#✅ 3. 高并发优化)
- [✅ 4. 是否需要"用户可读订单号"](#✅ 4. 是否需要“用户可读订单号”)
-
- 六、总结(给你决策建议)
前言
在 Spring Boot 里实现「业务前缀 + Snowflake ID」
一、核心思路
订单号结构:
js
订单号 = 业务前缀 + Snowflake生成的ID
比如:
js
OD + 1789237498127349823
或者更好一点(带时间可读):
js
OD + yyyyMMdd + SnowflakeID
二、Snowflake 算法实现(Java版)
1️⃣ Snowflake 工具类
java
@Component
public class SnowflakeIdGenerator {
// 起始时间戳(自己定义)
private final long START_TIMESTAMP = 1700000000000L;
// 每部分占用位数
private final long SEQUENCE_BIT = 12; // 序列号
private final long MACHINE_BIT = 5; // 机器ID
private final long DATACENTER_BIT = 5; // 机房ID
// 最大值
private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
private final long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
private final long MAX_DATACENTER_NUM = ~(-1L << DATACENTER_BIT);
// 位移
private final long MACHINE_LEFT = SEQUENCE_BIT;
private final long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final long TIMESTAMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
private long datacenterId;
private long machineId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator() {
this(1, 1); // 默认机房ID=1,机器ID=1
}
public SnowflakeIdGenerator(long datacenterId, long machineId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId非法");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("machineId非法");
}
this.datacenterId = datacenterId;
this.machineId = machineId;
}
public synchronized long nextId() {
long currTimestamp = System.currentTimeMillis();
if (currTimestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨,拒绝生成ID");
}
if (currTimestamp == lastTimestamp) {
sequence = (sequence + 1) & MAX_SEQUENCE;
if (sequence == 0L) {
currTimestamp = waitNextMillis();
}
} else {
sequence = 0L;
}
lastTimestamp = currTimestamp;
return (currTimestamp - START_TIMESTAMP) << TIMESTAMP_LEFT
| datacenterId << DATACENTER_LEFT
| machineId << MACHINE_LEFT
| sequence;
}
private long waitNextMillis() {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
三、订单号生成 Service
2️⃣ 订单号生成器
java
@Service
public class OrderNoGenerator {
@Autowired
private SnowflakeIdGenerator snowflakeIdGenerator;
public String generateOrderNo() {
long id = snowflakeIdGenerator.nextId();
String date = LocalDate.now()
.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
return "OD" + date + id;
}
}
四、Controller 测试
java
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderNoGenerator orderNoGenerator;
@GetMapping("/gen")
public String genOrderNo() {
return orderNoGenerator.generateOrderNo();
}
}
访问:
js
GET /order/gen
返回示例:
js
OD202604271789237498127349823
五、生产环境必须注意(重点)
✅ 1. machineId / datacenterId 不能乱写
否则会重复
推荐:
- 从配置中心获取
- 或用 IP hash
- 或用容器编号
✅ 2. 时钟回拨问题
解决方案:
- NTP 时间同步
- 或检测后等待(上面已处理)
✅ 3. 高并发优化
当前是 synchronized,可以:
-
换成:
AtomicLong + CAS- 或直接用开源方案:
👉 更推荐:
- 美团 Leaf
- 百度 UidGenerator
✅ 4. 是否需要"用户可读订单号"
如果你要给用户看,建议:
js
OD + 日期 + 随机码/短ID
否则 Snowflake 太长:
js
OD202604271789237498127349823
可以优化成:
js
OD2404278F3K92
六、总结(给你决策建议)
| 场景 | 推荐 |
|---|---|
| 小项目 | 时间戳 + Redis INCR |
| 中大型电商 | Snowflake |
| 超高并发(亿级) | Leaf / UidGenerator |
后续可以升级成:
✅ 支持分库分表
✅ 支持多机房自动分配ID
✅ 可读订单号(防爬虫、防推测)
✅ 短ID版本(类似抖音订单号)