每日一Go-55、分布式 ID 生成(雪花算法 / Segment / Redis / DB)

一、为什么分布式系统一定要"自己造ID"?

javascript 复制代码
单机时代,利用数据库的自增`ID`
AUTO_INCREMENT

但是在微服务/多实例/分库分表的情况下,会出现:

  • ID冲突

  • 数据迁移困难

  • 顺序失控

  • 跨库无法唯一定位

二、分布式ID的核心指标

一个靠谱的ID方案,至少要满足:

|------|---------|
| 指标 | 说明 |
| 全局唯一 | 不能重复 |
| 高性能 | AQS≥10w |
| 有序性 | 越新越大 |
| 高可用 | 不能成为单点 |
| 易扩展 | 节点随时加 |

三、主流4种方案总览

|------------|----|----|-------|-----|---------|
| 方案 | 性能 | 有序 | 依赖 | 复杂度 | 典型 |
| 雪花算法 | 5星 | 4星 | 本地 | 两星 | Twitter |
| Segment号段 | 4星 | 5星 | DB | 3星 | 美团/京东 |
| Redis INCR | 3星 | 5星 | Redis | 1星 | 小系统 |
| DB自增 | 1星 | 5星 | DB | 1星 | 单体 |

四、方案1:DB自增

cs 复制代码
INSERT INTO t VALUES ();
SELECT LAST_INSERT_ID();

这是所有分布式系统大忌,不推荐。

五、方案2:Redis INCR

css 复制代码
// Redis 命令
INCR order:id
// Go调用
id,_:=rdb.Incr(ctx,"order:id").Result()

优点:实现简单;严格递增。

缺点:Redis单点;网络开销;QPS上限。

适合低并发系统/管理后台

六、方案3:Segment号段(美团/京东订单号)

思路:

DB中维护一个号段

一次取一段(例如:1000个)

本地内存自增

表结构:

sql 复制代码
CREATE TABLE id_segment (
  biz_tag VARCHAR(64) PRIMARY KEY,
  max_id BIGINT,
  step INT
);
go 复制代码
type Segment struct {
	cur  int64
	max  int64
}

func (s *Segment) Next() int64 {
if s.cur >= s.max {
		s.reload()
	}
	s.cur++
return s.cur
}

优点:严格递增;ID短

缺点:依赖DB;实现复杂;冷启动慢。

适合订单号、流水号

七、方案4:雪花算法(Snowflake)

  1. ID结构
apache 复制代码
0 | 41bit 时间戳 | 10bit 机器ID | 12bit 序列号

时间戳:毫秒

机器ID:数据中心+worker

序列号:同毫秒并发

趋势递增、完全本地生成、无依赖

  1. go 代码实现
go 复制代码
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)
}
go 复制代码
//如何使用
sf := snowflake.New(1)
id := sf.NextID()
  1. 雪花算法工程注意点:
  • 时间回拨问题

  • NTP同步导致时间倒退

    解决方案:

  • 禁止自动回拨

  • 检测回拨直接panic/等待

  • 使用逻辑时间

八、如何选?

|----------|---------|
| 场景 | 推荐 |
| 单体 | DB |
| 小系统/快速上线 | Redis |
| 订单/财务流水 | Segment |
| 微服务/高并发 | 雪花算法 |

友情链接:加班费计算器(vx小程序搜索"加班计")

*源码地址*

私给


如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!

相关推荐
喵了几个咪1 分钟前
AI重构软件开发范式:框架与脚手架为何仍是生产级开发的刚需?
vue.js·人工智能·react.js·重构·golang·ai编程
wangbing112537 分钟前
SQL Server2008 R2版自动备份问题
数据库
Trouvaille ~44 分钟前
【Redis篇】Redis 渐进式遍历与数据库管理
数据库·redis·缓存·中间件·数据库管理·后端开发·scan
xcLeigh1 小时前
KES数据库运维监控与故障排查实战
运维·数据库·sql·故障排查·运维监控·kes
GlobalSign数字证书1 小时前
中小企业的 SSL/TLS 证书管理,有更轻量的方案
数据库·网络协议·ssl
梓䈑1 小时前
【MySQL】库的操作(数据库的创建、查看、修改 和 备份)
数据库·mysql
yuzhiboyouye1 小时前
原生 SQL 常用核心语句基础语法
数据库·sql·oracle
我是一颗柠檬1 小时前
【Redis】事务与Lua脚本Day7(2026年)
数据库·redis·后端·lua·database
流星白龙1 小时前
【MySQL高阶】14.MySQL存储结构
android·数据库·mysql
一只fish1 小时前
Oracle官方文档翻译《Database Concepts 26ai》第18章-进程架构
数据库·oracle