订单号应采用雪花算法生成,通过自定义NodeID(如K8s StatefulSet序号)、校验范围(0–1023)、设置合理Epoch,并字符串化输出以避免JS精度丢失和Excel误转;多服务需确保NodeID不重叠。

为什么不能直接用 time.Now().UnixNano() 生成订单号
时间戳本身不唯一,高并发下毫秒内可能产生多个订单;单纯拼接进程 ID 或随机数又难保证全局有序和可追溯。雪花算法(Snowflake)本质是把时间、机器标识、序列号打包成一个 64 位整数,兼顾唯一性、趋势递增、无中心依赖——这正是微服务里订单号最需要的底子。
Go 生态里最常用的是 sony/sonyflake 和 bwmarrin/snowflake,但前者不支持自定义节点 ID 分配逻辑,后者默认用 MAC 地址做机器标识,在容器或 Kubernetes 环境下会出问题(Pod 重启后 MAC 变、多副本冲突)。所以得自己控制 NodeID 注入方式。
- 别在 init 函数里硬编码
NodeID,否则镜像一部署就固定死了 - 不要依赖
os.Hostname(),K8s Pod hostname 是随机字符串,不可靠 - 避免用环境变量传
NodeID后直接转 int,没校验容易 panic
如何安全注入 NodeID 并初始化 Snowflake 实例
推荐通过启动参数或配置中心传入 node_id,并在初始化时做范围校验(0–1023)。bwmarrin/snowflake 允许传入自定义 Settings,其中 NodeID 必须在 10 位内。
sf, err := snowflake.NewNode(
uint64(nodeID),
snowflake.Settings{
Epoch: 1717027200000, // 自定义纪元时间,比如 2024-06-01 00:00:00 UTC
},
)
if err != nil {
log.Fatal("failed to create snowflake node:", err)
}
-
Epoch建议设为业务上线时间,避免生成负数 ID,也方便估算 ID 生命周期 - 如果用 K8s,可以把
node_id写进 StatefulSet 的ordinal,或用 Downward API 注入pod.name哈希后取模 - 多个微服务共用同一套 ID 生成逻辑时,务必确保各服务的
NodeID不重叠
订单号要不要转成字符串?什么时候必须转
数据库主键用 int64 没问题,但订单号面向前端、日志、第三方系统时,必须是字符串:一是防止 JS 精度丢失(Number.MAX_SAFE_INTEGER 只到 2^53),二是避免被 Excel 自动转成科学计数法。
- 调用
sf.Generate().String()即可,内部是 base10 转换,不是 base64 - 别用
fmt.Sprintf("%d", id),虽然结果一样,但.String()是库原生方法,更轻量 - 如果下游要求带前缀(如
ORD_1234567890123456789),直接拼接,别改 Snowflake 编码逻辑 - 注意:字符串化不会影响 ID 本身的单调递增性,排序仍可用
strconv.ParseInt还原
怎么测并发下是否重复或乱序
写个压测函数,起 100 goroutine 各生成 1000 个 ID,放进 map 计数,再检查是否严格递增:
ids := make([]int64, 0, 100000)
var mu sync.Mutex
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 1000; j++ {
id := sf.Generate().Int64()
mu.Lock()
ids = append(ids, id)
mu.Unlock()
}
}()
}
wg.Wait()
// 检查重复
seen := make(map[int64]bool)
for _, id := range ids {
if seen[id] {
t.Fatal("duplicate id found:", id)
}
seen[id] = true
}
// 检查乱序(允许相等?不允许。Snowflake 要求每毫秒内递增)
for i := 1; i < len(ids); i++ {
if ids[i] <= ids[i-1] {
t.Fatal("id not strictly increasing at index", i)
}
}
真正容易忽略的是时钟回拨:如果宿主机时间被 NTP 同步往回调了 5ms,snowflake 默认会 panic。生产环境必须捕获 snowflake.ErrTimeBackwards 并降级(比如 sleep 等待、或 fallback 到 UUID),不能让整个订单创建流程卡住。


















