不能直接用数据库自增id或time.now().unixnano(),因多库自增id重复、纳秒级并发撞车及时钟回拨导致倒序,不满足全局唯一和趋势递增;sonyflake需显式配置starttime和machineid;snowflake的nodeid必须全局唯一且启动时单例复用;mysql存储和json传输需处理uint64溢出与精度丢失。

为什么不能直接用数据库自增ID或time.Now().UnixNano()
微服务拆分后,多个服务写入不同数据库实例,自增ID必然重复;time.Now().UnixNano()没有机器标识和序列号,同一纳秒内多协程调用就撞车,时钟回拨还会导致ID倒序。这两者都不满足「全局唯一 + 趋势递增」的基本要求,上线后大概率触发主键冲突、分片路由失效或数据倾斜。
sony/sonyflake初始化必须设StartTime和MachineID
sony/sonyflake默认用当前时间做纪元起点,容器重启后可能生成更小ID;默认MachineID基于主机名哈希,K8s中Pod hostname常为随机字符串,哈希易冲突。必须显式配置:
-
StartTime设为固定时间点,例如time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli() -
MachineID函数别用os.Hostname(),改用Downward API注入的POD_IP或metadata.uid,再取sha256.Sum32([]byte(ip))[:2]转uint16 - 若集群节点超32个(默认5位MachineID),需调整位宽——但总位数不能超64,否则panic
bwmarrin/snowflake的NodeID必须全局唯一且复用
这个库不校验NodeID是否重复,snowflake.NewNode(1)在多个Pod里硬编码就会批量撞车。常见错误包括:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 本地测试用
NodeID=1,上线后所有Pod都用1 - 用环境变量读
NODE_ID但忘了在K8s yaml里配置,fallback到默认值 - Node实例被跨goroutine复用,而它本身不是goroutine-safe的
正确做法:把NodeID当核心配置项管理,从Consul/Etcd或K8s Downward API获取,启动时只调用一次NewNode并全局复用该实例。
ID存MySQL和JSON传输要注意溢出与精度丢失
uint64型Snowflake ID直接存BIGINT(有符号)会高位溢出变负数;Go的database/sql默认不支持uint64扫描;JSON序列化时int64在JavaScript里会精度丢失(超过2^53)。
- 写入MySQL:用
driver.Valuer包装uint64,Value()里返回int64按位解释 - 读取MySQL:实现
sql.Scanner,Scan()里把int64按位转回uint64 - HTTP API返回:ID字段必须定义为JSON字符串(
"id": "1234567890123456789"),不能裸数字
真正麻烦的不是生成ID,而是确保上下游系统对64位无符号整数的处理方式完全一致——哪怕一个环节漏掉类型转换,就会在某个凌晨三点爆出来。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










