2026-04-25 13:21:39 +08:00

80 lines
1.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package idgen
import (
"sync"
"time"
)
const (
// int64EpochMs 使用固定业务纪元,缩短时间戳占用位数。
int64EpochMs int64 = 1704067200000
// maxInt64NodeID 预留 10 bit 给节点,支持 1024 个发号节点。
maxInt64NodeID int64 = 1023
// int64SequenceMask 预留 12 bit 给同毫秒序列。
int64SequenceMask int64 = 4095
)
// Int64Generator 生成 Snowflake-like int64 ID。
// 格式为 timestamp_ms | node_id | sequence适合用户主键这种不可变长 ID。
type Int64Generator struct {
mu sync.Mutex
nodeID int64
lastMs int64
sequence int64
now func() time.Time
sleepUntil func(time.Time)
}
// NewInt64Generator 创建用户主键发号器。
// nodeID 必须由部署配置保证不同发号节点不冲突。
func NewInt64Generator(nodeID int64) *Int64Generator {
if nodeID < 0 {
nodeID = 0
}
if nodeID > maxInt64NodeID {
nodeID = nodeID & maxInt64NodeID
}
return &Int64Generator{
nodeID: nodeID,
now: time.Now,
sleepUntil: defaultSleepUntil,
}
}
// NewInt64 返回一个全局唯一倾向的 int64 ID。
func (g *Int64Generator) NewInt64() int64 {
g.mu.Lock()
defer g.mu.Unlock()
nowMs := g.now().UnixMilli()
if nowMs < g.lastMs {
// 时钟回拨时等待到上次发号时间,避免生成倒退 ID。
g.sleepUntil(time.UnixMilli(g.lastMs))
nowMs = g.now().UnixMilli()
}
if nowMs == g.lastMs {
g.sequence = (g.sequence + 1) & int64SequenceMask
if g.sequence == 0 {
// 同毫秒序列耗尽时等到下一毫秒再继续。
next := time.UnixMilli(g.lastMs + 1)
g.sleepUntil(next)
nowMs = g.now().UnixMilli()
}
} else {
g.sequence = 0
}
g.lastMs = nowMs
return ((nowMs - int64EpochMs) << 22) | (g.nodeID << 12) | g.sequence
}
// defaultSleepUntil 封装真实等待,测试可通过结构体替换。
func defaultSleepUntil(target time.Time) {
if delay := time.Until(target); delay > 0 {
time.Sleep(delay)
}
}