直接用 time.Now().UnixNano() 做ID会导致并发重复、时钟回拨、毫秒级精度不够三大问题;UUID v4 不适合作为业务ID,因其无序、低索引效率且不可排序;手写Snowflake变体需拆分64位为时间戳(41位)、机器ID(10位)、序列号(12位),并安全初始化、防回拨、加锁保序。

为什么不能直接用 time.Now().UnixNano() 做ID?
看似简单,但会立刻撞上三个硬伤:并发重复、时钟回拨、毫秒级精度不够。比如在高并发下同一纳秒内多个 goroutine 调用,UnixNano() 返回值完全一样;NTP 校时或虚拟机休眠后恢复,系统时间可能倒退,导致 ID 降序甚至重复;另外纯时间戳不带机器/进程信息,分布式环境下无法保证全局唯一。
github.com/google/uuid 是否适合做业务ID?
UUID v4 是随机生成的 128 位字符串,虽然全局唯一性极强,但对数据库索引不友好(无序、长度大、不可排序),且无法体现时间顺序,排查问题时很难按时间范围快速筛选。它更适合做文件名、token 或内部标识,而不是订单号、消息ID这类需兼顾可读性、索引效率和趋势递增的业务主键。
如何手写一个轻量、可嵌入的 Snowflake 变体?
核心是把 64 位整数拆成三段:时间戳(毫秒,41 位)、机器ID(10 位)、序列号(12 位)。关键点不在算法本身,而在如何安全初始化和应对边界:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
machineID不能硬编码,建议从环境变量或配置文件读取,启动时校验是否在[0, 1023]范围内 - 时间戳部分用
time.Now().UnixMilli()(Go 1.17+),避免自己算偏移;若需兼容旧版,用(time.Now().UnixNano() / 1e6) - 序列号要加锁(
sync.Mutex)或用sync/atomic,否则并发下会溢出重置导致 ID 重复 - 每次获取 ID 前必须检查当前时间是否小于上次时间戳——若发生回拨,应阻塞等待(最多 5ms)或 panic,**绝不能直接忽略**
示例片段(省略错误处理):
sony/fluency
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










