Files
backend/internal/pkg/utils/snowflake.go

262 lines
6.2 KiB
Go
Raw Normal View History

package utils
import (
"errors"
"os"
"sync"
"time"
)
// 雪花算法常量定义
const (
// 64位ID结构1位符号位 + 41位时间戳 + 10位机器ID + 12位序列号
// 机器ID占用的位数
nodeIDBits uint64 = 10
// 序列号占用的位数
sequenceBits uint64 = 12
// 机器ID的最大值 (0-1023)
maxNodeID int64 = -1 ^ (-1 << nodeIDBits)
// 序列号的最大值 (0-4095)
maxSequence int64 = -1 ^ (-1 << sequenceBits)
// 机器ID左移位数
nodeIDShift uint64 = sequenceBits
// 时间戳左移位数
timestampShift uint64 = sequenceBits + nodeIDBits
// 自定义纪元时间2024-01-01 00:00:00 UTC
// 使用自定义纪元可以延长ID有效期约24年从2024年开始
customEpoch int64 = 1704067200000 // 2024-01-01 00:00:00 UTC 的毫秒时间戳
)
// 错误定义
var (
// ErrInvalidNodeID 机器ID无效
ErrInvalidNodeID = errors.New("node ID must be between 0 and 1023")
// ErrClockBackwards 时钟回拨
ErrClockBackwards = errors.New("clock moved backwards, refusing to generate ID")
)
// IDInfo 解析后的ID信息
type IDInfo struct {
Timestamp int64 // 生成ID时的时间戳毫秒
NodeID int64 // 机器ID
Sequence int64 // 序列号
}
// Snowflake 雪花算法ID生成器
type Snowflake struct {
mu sync.Mutex // 互斥锁,保证线程安全
nodeID int64 // 机器ID (0-1023)
sequence int64 // 当前序列号 (0-4095)
lastTimestamp int64 // 上次生成ID的时间戳
}
// 全局雪花算法实例
var (
globalSnowflake *Snowflake
globalSnowflakeOnce sync.Once
globalSnowflakeErr error
)
// InitSnowflake 初始化全局雪花算法实例
// nodeID: 机器ID范围0-1023可以通过环境变量 NODE_ID 配置
func InitSnowflake(nodeID int64) error {
globalSnowflake, globalSnowflakeErr = NewSnowflake(nodeID)
return globalSnowflakeErr
}
// GetSnowflake 获取全局雪花算法实例
// 如果未初始化,会自动使用默认配置初始化
func GetSnowflake() *Snowflake {
globalSnowflakeOnce.Do(func() {
if globalSnowflake == nil {
globalSnowflake, globalSnowflakeErr = NewSnowflake(-1)
}
})
return globalSnowflake
}
// NewSnowflake 创建雪花算法ID生成器实例
// nodeID: 机器ID范围0-1023可以通过环境变量 NODE_ID 配置
// 如果nodeID为-1则尝试从环境变量 NODE_ID 读取
func NewSnowflake(nodeID int64) (*Snowflake, error) {
// 如果传入-1尝试从环境变量读取
if nodeID == -1 {
nodeIDStr := os.Getenv("NODE_ID")
if nodeIDStr != "" {
// 解析环境变量
parsedID, err := parseInt(nodeIDStr)
if err != nil {
return nil, ErrInvalidNodeID
}
nodeID = parsedID
} else {
// 默认使用0
nodeID = 0
}
}
// 验证机器ID范围
if nodeID < 0 || nodeID > maxNodeID {
return nil, ErrInvalidNodeID
}
return &Snowflake{
nodeID: nodeID,
sequence: 0,
lastTimestamp: 0,
}, nil
}
// parseInt 辅助函数:解析整数
func parseInt(s string) (int64, error) {
var result int64
var negative bool
if len(s) == 0 {
return 0, errors.New("empty string")
}
i := 0
if s[0] == '-' {
negative = true
i = 1
}
for ; i < len(s); i++ {
if s[i] < '0' || s[i] > '9' {
return 0, errors.New("invalid character")
}
result = result*10 + int64(s[i]-'0')
}
if negative {
result = -result
}
return result, nil
}
// GenerateID 生成唯一的雪花算法ID
// 返回值生成的ID以及可能的错误如时钟回拨
// 线程安全:使用互斥锁保证并发安全
func (s *Snowflake) GenerateID() (int64, error) {
s.mu.Lock()
defer s.mu.Unlock()
// 获取当前时间戳(毫秒)
now := currentTimestamp()
// 处理时钟回拨
if now < s.lastTimestamp {
return 0, ErrClockBackwards
}
// 同一毫秒内
if now == s.lastTimestamp {
// 序列号递增
s.sequence = (s.sequence + 1) & maxSequence
// 序列号溢出,等待下一毫秒
if s.sequence == 0 {
now = s.waitNextMillis(now)
}
} else {
// 不同毫秒序列号重置为0
s.sequence = 0
}
// 更新上次生成时间
s.lastTimestamp = now
// 组装ID
// ID结构时间戳部分 | 机器ID部分 | 序列号部分
id := ((now - customEpoch) << timestampShift) |
(s.nodeID << nodeIDShift) |
s.sequence
return id, nil
}
// waitNextMillis 等待到下一毫秒
// 参数:当前时间戳
// 返回值:下一毫秒的时间戳
func (s *Snowflake) waitNextMillis(timestamp int64) int64 {
now := currentTimestamp()
for now <= timestamp {
now = currentTimestamp()
}
return now
}
// ParseID 解析雪花算法ID提取其中的信息
// id: 要解析的雪花算法ID
// 返回值包含时间戳、机器ID、序列号的结构体
func ParseID(id int64) *IDInfo {
// 提取序列号低12位
sequence := id & maxSequence
// 提取机器ID中间10位
nodeID := (id >> nodeIDShift) & maxNodeID
// 提取时间戳高41位
timestamp := (id >> timestampShift) + customEpoch
return &IDInfo{
Timestamp: timestamp,
NodeID: nodeID,
Sequence: sequence,
}
}
// currentTimestamp 获取当前时间戳(毫秒)
func currentTimestamp() int64 {
return time.Now().UnixNano() / 1e6
}
// GetNodeID 获取当前机器ID
func (s *Snowflake) GetNodeID() int64 {
return s.nodeID
}
// GetCustomEpoch 获取自定义纪元时间
func GetCustomEpoch() int64 {
return customEpoch
}
// IDToTime 将雪花算法ID转换为生成时间
// 这是一个便捷方法,等价于 ParseID(id).Timestamp
func IDToTime(id int64) time.Time {
info := ParseID(id)
return time.Unix(0, info.Timestamp*1e6) // 毫秒转纳秒
}
// ValidateID 验证ID是否为有效的雪花算法ID
// 检查时间戳是否在合理范围内
func ValidateID(id int64) bool {
if id <= 0 {
return false
}
info := ParseID(id)
// 检查时间戳是否在合理范围内
// 不能早于纪元时间不能晚于当前时间太多允许1分钟的时钟偏差
now := currentTimestamp()
if info.Timestamp < customEpoch || info.Timestamp > now+60000 {
return false
}
// 检查机器ID和序列号是否在有效范围内
if info.NodeID < 0 || info.NodeID > maxNodeID {
return false
}
if info.Sequence < 0 || info.Sequence > maxSequence {
return false
}
return true
}