每日一Go-55、分布式 ID 生成(雪花算法 / Segment / Redis / DB)
一、为什么分布式系统一定要“自己造ID”? 但是在微服务/多实例/分库分表的情况下,会出现: 二、分布式ID的核心指标 一个靠谱的ID方案,至少要满足: 三、主流4种方案总览 四、方案1:DB自增 这是所有分布式系统大忌,不推荐。 五、方案2:Redis INCR 优点:实现简单;严格递增。 缺点:Redis单点;网络开销;QPS上限。 适合低并发系统/管理后台 六、方案3:Segment号段(美团/京东订单号) 思路: DB中维护一个号段 一次取一段(例如:1000个) 本地内存自增 表结构: 优点:严格递增;ID短 缺点:依赖DB;实现复杂;冷启动慢。 适合订单号、流水号 七、方案4:雪花算法(Snowflake) 1. ID结构 时间戳:毫秒 机器ID:数据中心+worker 序列号:同毫秒并发 趋势递增、完全本地生成、无依赖 2. go 代码实现 3. 雪花算法工程注意点: 解决方案: 八、如何选? 友情链接:加班费计算器(vx小程序搜索“加班计”) *源码地址* 1、公众号“Codee君”回复“每日一Go”获取源码 2、https://pan.baidu.com/s/1B6pgLWfSgMngVeFfSTcPdg?pwd=jc1s 如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!单机时代,利用数据库的自增`ID`
AUTO_INCREMENTINSERT INTO t VALUES ();
SELECT LAST_INSERT_ID();// Redis 命令
INCR order:id
// Go调用
id,_:=rdb.Incr(ctx,"order:id").Result()CREATE TABLE id_segment (
biz_tag VARCHAR(64) PRIMARY KEY,
max_id BIGINT,
step INT
);type Segment struct {
cur int64
max int64
}
func (s *Segment) Next() int64 {
if s.cur >= s.max {
s.reload()
}
s.cur++
return s.cur
}0 | 41bit 时间戳 | 10bit 机器ID | 12bit 序列号package snowflake
import (
"errors"
"strconv"
"sync"
"time"
)
// 常量定义
const (
workerBits = 10 // 工作节点位数
seqBits = 12 // 序列号位数
workerMax = -1 ^ (-1 << workerBits) // 工作节点最大ID
seqMask = -1 ^ (-1 << seqBits) // 序列号掩码
timeShift = workerBits + seqBits // 时间戳左移位数
workerShift = seqBits // 工作节点左移位数
defaultEpoch = int64(1672531200000) // 默认起始时间戳 (2023-01-01)
)
// ID 自定义类型,用于区分雪花ID和普通int64
type ID int64
// Snowflake 雪花算法生成器
type Snowflake struct {
mu sync.Mutex
lastTime int64
workerID int64
sequence int64
epoch int64
}
// New 创建雪花算法生成器
// workerID: 工作节点ID,范围 0~1023
// 返回错误如果workerID超出范围
func New(workerID int64) (*Snowflake, error) {
return NewWithEpoch(workerID, defaultEpoch)
}
// NewWithEpoch 创建带自定义起始时间的雪花算法生成器
// workerID: 工作节点ID,范围 0~1023
// epoch: 自定义起始时间戳(毫秒)
// 返回错误如果workerID超出范围
func NewWithEpoch(workerID int64, epoch int64) (*Snowflake, error) {
if workerID < 0 || workerID > workerMax {
return nil, errors.New("worker ID out of range [0, 1023]")
}
return &Snowflake{
workerID: workerID,
epoch: epoch,
}, nil
}
// NextID 生成下一个雪花ID
// 返回ID类型的雪花ID和可能的错误
func (s *Snowflake) NextID() (ID, error) {
s.mu.Lock()
defer s.mu.Unlock()
now := time.Now().UnixMilli()
// 处理时间回拨
if now < s.lastTime {
return 0, errors.New("time is back, ID generation failed")
}
if now == s.lastTime {
// 同一毫秒内,递增序列号
s.sequence = (s.sequence + 1) & seqMask
// 序列号耗尽,等待下一个毫秒
if s.sequence == 0 {
// 使用短暂休眠代替自旋等待,减少CPU占用
time.Sleep(time.Millisecond)
now = time.Now().UnixMilli()
// 处理时间回拨(再次检查)
if now < s.lastTime {
return 0, errors.New("time is back, ID generation failed")
}
s.lastTime = now
s.sequence = 0
}
} else {
// 新的毫秒,重置序列号
s.lastTime = now
s.sequence = 0
}
// 生成ID
id := ((now - s.epoch) << timeShift) |
(s.workerID << workerShift) |
s.sequence
return ID(id), nil
}
// ParseID 解析雪花ID
// 返回ID的各组成部分:时间戳、工作节点ID、序列号
func ParseID(id ID, epoch int64) (time.Time, int64, int64) {
idInt := int64(id)
timestamp := (idInt >> timeShift) + epoch
workerID := (idInt >> workerShift) & ((1 << workerBits) - 1)
sequence := idInt & seqMask
return time.UnixMilli(timestamp), workerID, sequence
}
// String 将ID转换为字符串
func (id ID) String() string {
return strconv.FormatInt(int64(id), 10)
}
// Int64 将ID转换为int64
func (id ID) Int64() int64 {
return int64(id)
}//如何使用
sf := snowflake.New(1)
id := sf.NextID()