如何利用 System.currentTimeMillis() 和 UUID 构建一个独一无二的业务流水号?
- 内容介绍
- 相关推荐
本文共计865个文字,预计阅读时间需要4分钟。
使用`System.currentTimeMillis()`或UUID生成业务流水号存在缺陷:
核心思路:时间戳做前缀 + 有序/可控的后缀
UUID 全局唯一但随机性强、长度固定(32位十六进制+4横线),不适合直接作流水号;currentTimeMillis() 精确到毫秒,但在同一毫秒内多线程调用会重复。因此推荐以时间戳为基底,用轻量方式补充“毫秒内序号”或“机器标识”,再选择性融合 UUID 的部分信息(如 node ID 或随机段),而非整个 UUID。
- ✅ 推荐结构:时间戳(13位) + 毫秒内序列号(3-5位) + 机器/进程标识(2-4位)
- ⚠️ 不推荐:
System.currentTimeMillis() + "-" + UUID.randomUUID().toString()—— 过长(约36字符)、含横线和字母,不便于日志检索、数据库索引、前端展示 - ? 替代方案:取 UUID 的 leastSignificantBits(8字节)转为紧凑的 12~14 位 Base32 或数字字符串,仅作“随机扰动因子”,不暴露完整 UUID
轻量安全的实现示例(无外部依赖)
以下代码在单 JVM 进程内高效生成近似全局唯一的流水号(跨 JVM 需额外加机器号或服务实例 ID):
public class BusinessSeqGenerator { private static final long START_EPOCH = 1704067200000L; // 自定义起始时间(2024-01-01) private static final AtomicInteger seqInMs = new AtomicInteger(0); private static final int MAX_SEQ = 999; // 每毫秒最多 999 个 private static final String MACHINE_ID = "01"; // 可从配置或 IP 哈希生成,2位足够 <pre class="brush:php;toolbar:false;">public static String generate() { long currentMs = System.currentTimeMillis() - START_EPOCH; int seq = seqInMs.incrementAndGet() % (MAX_SEQ + 1); if (seq == 0) { // 溢出时等待下一毫秒(或丢弃重试,视业务容忍度而定) try { Thread.sleep(1); } catch (InterruptedException e) { } return generate(); } return String.format("%013d%03d%s", currentMs, seq, MACHINE_ID); }
}
生成结果形如:123456789012300101(13位时间偏移 + 3位序列 + 2位机器号),共18位纯数字,支持数据库 B-tree 索引、按时间范围查询、肉眼可大致判断生成时段。
如需更强跨进程唯一性,可引入 UUID 片段
若系统部署在多台机器且无法统一分配 machineId,可用 UUID 的低 64 位哈希压缩为 4~5 位数字,作为“轻量节点指纹”:
- 取
UUID.randomUUID().getLeastSignificantBits() - 对其做
Math.abs(hash) % 10000得到 0–9999 的 4 位数(补零) - 拼入末尾替代 MACHINE_ID,例如:
timeSeq + String.format("%04d", nodeId) - ⚠️ 注意:不能用
UUID.toString()全量,避免横线、字母、长度失控
实际使用提醒
业务流水号本质是“在特定上下文里不重复 + 易维护”,不必强求密码学级唯一:
- 数据库务必对流水号字段加 唯一索引,作为最终兜底
- 避免在分布式事务中依赖生成逻辑的“绝对唯一”,应配合幂等键(如订单号+操作类型)
- 若审计要求严格,可在生成后记录到独立日志表,关联时间、服务名、traceId
- 不建议把 UUID 全量混入流水号——它不携带时间信息,破坏排序性,也增加存储和传输开销
本文共计865个文字,预计阅读时间需要4分钟。
使用`System.currentTimeMillis()`或UUID生成业务流水号存在缺陷:
核心思路:时间戳做前缀 + 有序/可控的后缀
UUID 全局唯一但随机性强、长度固定(32位十六进制+4横线),不适合直接作流水号;currentTimeMillis() 精确到毫秒,但在同一毫秒内多线程调用会重复。因此推荐以时间戳为基底,用轻量方式补充“毫秒内序号”或“机器标识”,再选择性融合 UUID 的部分信息(如 node ID 或随机段),而非整个 UUID。
- ✅ 推荐结构:时间戳(13位) + 毫秒内序列号(3-5位) + 机器/进程标识(2-4位)
- ⚠️ 不推荐:
System.currentTimeMillis() + "-" + UUID.randomUUID().toString()—— 过长(约36字符)、含横线和字母,不便于日志检索、数据库索引、前端展示 - ? 替代方案:取 UUID 的 leastSignificantBits(8字节)转为紧凑的 12~14 位 Base32 或数字字符串,仅作“随机扰动因子”,不暴露完整 UUID
轻量安全的实现示例(无外部依赖)
以下代码在单 JVM 进程内高效生成近似全局唯一的流水号(跨 JVM 需额外加机器号或服务实例 ID):
public class BusinessSeqGenerator { private static final long START_EPOCH = 1704067200000L; // 自定义起始时间(2024-01-01) private static final AtomicInteger seqInMs = new AtomicInteger(0); private static final int MAX_SEQ = 999; // 每毫秒最多 999 个 private static final String MACHINE_ID = "01"; // 可从配置或 IP 哈希生成,2位足够 <pre class="brush:php;toolbar:false;">public static String generate() { long currentMs = System.currentTimeMillis() - START_EPOCH; int seq = seqInMs.incrementAndGet() % (MAX_SEQ + 1); if (seq == 0) { // 溢出时等待下一毫秒(或丢弃重试,视业务容忍度而定) try { Thread.sleep(1); } catch (InterruptedException e) { } return generate(); } return String.format("%013d%03d%s", currentMs, seq, MACHINE_ID); }
}
生成结果形如:123456789012300101(13位时间偏移 + 3位序列 + 2位机器号),共18位纯数字,支持数据库 B-tree 索引、按时间范围查询、肉眼可大致判断生成时段。
如需更强跨进程唯一性,可引入 UUID 片段
若系统部署在多台机器且无法统一分配 machineId,可用 UUID 的低 64 位哈希压缩为 4~5 位数字,作为“轻量节点指纹”:
- 取
UUID.randomUUID().getLeastSignificantBits() - 对其做
Math.abs(hash) % 10000得到 0–9999 的 4 位数(补零) - 拼入末尾替代 MACHINE_ID,例如:
timeSeq + String.format("%04d", nodeId) - ⚠️ 注意:不能用
UUID.toString()全量,避免横线、字母、长度失控
实际使用提醒
业务流水号本质是“在特定上下文里不重复 + 易维护”,不必强求密码学级唯一:
- 数据库务必对流水号字段加 唯一索引,作为最终兜底
- 避免在分布式事务中依赖生成逻辑的“绝对唯一”,应配合幂等键(如订单号+操作类型)
- 若审计要求严格,可在生成后记录到独立日志表,关联时间、服务名、traceId
- 不建议把 UUID 全量混入流水号——它不携带时间信息,破坏排序性,也增加存储和传输开销

