分布式id不能直接用time.now().unixnano(),因高并发下纳秒级重复、时钟回拨、容器漂移、随机数种子相同等导致id冲突与乱序;需用sonyflake或自研snowflake并妥善处理workerid分配与时钟治理。

为什么不能直接用 time.Now().UnixNano() 拼接做分布式ID
时间戳本身不唯一,高并发下同一纳秒内多个节点或协程会生成重复ID;单机多进程、容器重启、系统时钟回拨都会让顺序性崩坏。哪怕加随机数或机器号,也解决不了时钟问题带来的 ID 冲突和乱序。
- 回拨 1ms 就可能触发大量重复
workerId+sequence组合 - 容器漂移后
hostname或MAC地址可能复用,导致 workerId 冲突 -
math/rand默认种子相同,多实例并行时初始 sequence 易撞车
用 github.com/sony/sonyflake 快速落地但要注意三点
它比雪花算法(Snowflake)更轻,用毫秒时间戳 + 机器 ID + 序列号,且内置了基于 /dev/urandom 的自增 fallback 机制,适合中小规模场景。但它默认不持久化 last time,重启后若时钟未前进,会拒绝发号。
- 必须传入自定义
StartTime,建议设为服务上线时间(如time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)),避免首次启动时因系统时间早于默认值而 panic -
MachineID函数不能返回固定值;推荐用hostname+pid哈希,或从环境变量读WORKER_ID,否则 K8s 里 Pod 重建就撞 ID - 它不处理时钟回拨,需配合
sync.Once+ 状态检查,在NextID()失败时主动 sleep 等待时钟追上
自己实现 Snowflake 时 sequence 怎么不丢不重
核心是用原子操作保序,而不是锁或 channel——后者在每秒百万级调用下会成为瓶颈。Go 的 atomic.Uint64 足够支撑单机每毫秒上万 ID。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- 每次获取 ID 前先
atomic.AddUint64(&s.seq, 1),再与阈值(如 4095)取模,溢出则阻塞等待下一毫秒 - 不要把
seq存 struct 字段里,得用指针传参或闭包捕获,否则 goroutine 间看到的是副本 - 测试时用
runtime.Gosched()模拟调度切换,验证多 goroutine 并发调用是否仍严格递增
要不要存数据库做 ID 号段?看吞吐和一致性要求
号段模式(如 Leaf-segment)本质是批量预占,降低 DB 压力,但引入双写风险和号段浪费。它适合 ID 需要全局强单调、且能容忍少量跳号的业务,比如订单号展示。
- 别用
AUTO_INCREMENT直接对外暴露,主从延迟会导致从库查不到刚生成的 ID - 号段表必须带
version字段,更新时SET step = step + 1000 WHERE version = ?,失败则重试,防止并发覆盖 - 服务启动时预加载 1~3 个号段到内存,用
sync.Pool缓存已分配的号段对象,避免频繁 new
真正难的不是生成逻辑,是 workerId 分配的自动化和时钟治理——K8s 里没 /proc/sys/kernel/random/uuid 这种稳定熵源,得靠 Operator 注入或 ConfigMap 管理 ID 池。时钟监控也得单独跑一个 goroutine 定期比对 NTP,发现偏移 >50ms 就打 warning 日志。这些不在算法里,但线上一出事,最先爆的就是它们。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










