引言
在分布式系统中,生成全局唯一ID是一个基础但关键的问题。传统的数据库自增ID在多节点环境下无法满足需求,需要专门的分布式ID生成方案。
本文将系统介绍主流的分布式ID生成方案及其实现细节。
ID生成方案对比
| 方案 | 有序性 | 性能 | 长度 | 依赖 | 适用场景 |
|---|---|---|---|---|---|
| UUID | 无序 | 高 | 36字符 | 无 | 无需排序的场景 |
| 数据库自增 | 有序 | 中 | 8字节 | 数据库 | 单库应用 |
| Snowflake | 趋势有序 | 极高 | 8字节 | 时钟 | 高并发分布式系统 |
| 号段模式 | 有序 | 高 | 8字节 | 数据库 | 中等并发 |
| Redis INCR | 有序 | 高 | 8字节 | Redis | 简单场景 |
UUID方案
UUID v4实现
import "github.com/google/uuid"
func generateUUID() string {
return uuid.New().String() // 例如: 550e8400-e29b-41d4-a716-446655440000
}
// 性能测试
func BenchmarkUUID(b *testing.B) {
for i := 0; i < b.N; i++ {
uuid.New().String()
}
}
// 结果: ~200ns/op, 无内存分配
import java.util.UUID;
public class UUIDGenerator {
public static String generate() {
return UUID.randomUUID().toString();
}
// 优化:使用SecureRandom提升性能
private static final SecureRandom secureRandom = new SecureRandom();
public static String generateOptimized() {
byte[] randomBytes = new byte[16];
secureRandom.nextBytes(randomBytes);
randomBytes[6] &= 0x0f;
randomBytes[6] |= 0x40;
randomBytes[8] &= 0x3f;
randomBytes[8] |= 0x80;
return bytesToUUID(randomBytes);
}
}
优点:
- 无需外部依赖,本地生成
- 全球唯一性保证
- 生成速度快
缺点:
- 无序,导致数据库索引性能差
- 字符串存储占用空间大(36字节 vs 8字节)
- 不可读,调试困难
数据库索引性能对比
-- UUID作为主键(InnoDB)
CREATE TABLE orders_uuid (
id CHAR(36) PRIMARY KEY,
user_id BIGINT,
created_at TIMESTAMP
);
-- 插入100万条数据后,B+树索引碎片严重
-- 查询性能下降30-50%
-- BIGINT作为主键
CREATE TABLE orders_bigint (
id BIGINT PRIMARY KEY,
user_id BIGINT,
created_at TIMESTAMP
);
-- 有序ID,索引紧凑,查询性能稳定
Snowflake算法
算法原理
Snowflake ID结构(64位):
0 | timestamp (41位) | datacenter_id (5位) | worker_id (5位) | sequence (12位)
| | | |
| | | |
符号位 毫秒级时间戳 数据中心ID 序列号
(1位) (可用69年) (32个数据中心) (每毫秒4096个)
总计: 1 + 41 + 5 + 5 + 12 = 64位
Go语言实现
type Snowflake struct {
mu sync.Mutex
epoch int64 // 起始时间戳(自定义纪元)
lastTimestamp int64
workerID int64
datacenterID int64
sequence int64
// 位配置
workerIDBits int64
datacenterIDBits int64
sequenceBits int64
maxWorkerID int64
maxDatacenterID int64
maxSequence int64
timestampShift int64
datacenterIDShift int64
workerIDShift int64
}
func NewSnowflake(workerID, datacenterID int64) (*Snowflake, error) {
sf := &Snowflake{
epoch: 1609459200000, // 2021-01-01 00:00:00 UTC
workerIDBits: 5,
datacenterIDBits: 5,
sequenceBits: 12,
}
sf.maxWorkerID = -1 ^ (-1 << sf.workerIDBits)
sf.maxDatacenterID = -1 ^ (-1 << sf.datacenterIDBits)
sf.maxSequence = -1 ^ (-1 << sf.sequenceBits)
sf.timestampShift = sf.sequenceBits + sf.workerIDBits + sf.datacenterIDBits
sf.datacenterIDShift = sf.sequenceBits + sf.workerIDBits
sf.workerIDShift = sf.sequenceBits
if workerID > sf.maxWorkerID || workerID < 0 {
return nil, fmt.Errorf("worker ID must be between 0 and %d", sf.maxWorkerID)
}
if datacenterID > sf.maxDatacenterID || datacenterID < 0 {
return nil, fmt.Errorf("datacenter ID must be between 0 and %d", sf.maxDatacenterID)
}
sf.workerID = workerID
sf.datacenterID = datacenterID
return sf, nil
}
func (sf *Snowflake) Generate() (int64, error) {
sf.mu.Lock()
defer sf.mu.Unlock()
timestamp := time.Now().UnixMilli()
// 时钟回拨检测
if timestamp < sf.lastTimestamp {
return 0, fmt.Errorf("clock moved backwards, refusing to generate id for %d milliseconds",
sf.lastTimestamp-timestamp)
}
if timestamp == sf.lastTimestamp {
// 同一毫秒内,序列号递增
sf.sequence = (sf.sequence + 1) & sf.maxSequence
if sf.sequence == 0 {
// 序列号溢出,等待下一毫秒
for timestamp <= sf.lastTimestamp {
timestamp = time.Now().UnixMilli()
}
}
} else {
// 新的毫秒,序列号从0开始
sf.sequence = 0
}
sf.lastTimestamp = timestamp
// 组合ID
id := ((timestamp - sf.epoch) << sf.timestampShift) |
(sf.datacenterID << sf.datacenterIDShift) |
(sf.workerID << sf.workerIDShift) |
sf.sequence
return id, nil
}
// 解析Snowflake ID
func (sf *Snowflake) Parse(id int64) (timestamp, datacenterID, workerID, sequence int64) {
timestamp = (id >> sf.timestampShift) + sf.epoch
datacenterID = (id >> sf.datacenterIDShift) & sf.maxDatacenterID
workerID = (id >> sf.workerIDShift) & sf.maxWorkerID
sequence = id & sf.maxSequence
return
}
时钟回拨问题处理
type SnowflakeWithClockBackup struct {
*Snowflake
backupWorkerID int64
}
func (sf *SnowflakeWithClockBackup) Generate() (int64, error) {
timestamp := time.Now().UnixMilli()
// 时钟回拨超过5ms,切换到备用workerID
if timestamp < sf.lastTimestamp {
offset := sf.lastTimestamp - timestamp
if offset > 5 {
// 切换到备用workerID,避免ID冲突
sf.workerID = sf.backupWorkerID
sf.sequence = 0
log.Warnf("Clock moved backwards by %dms, switched to backup worker ID %d",
offset, sf.backupWorkerID)
// 等待时钟追上
time.Sleep(time.Duration(offset) * time.Millisecond)
timestamp = time.Now().UnixMilli()
} else {
// 小幅回拨,短暂等待
time.Sleep(time.Duration(offset) * time.Millisecond)
timestamp = time.Now().UnixMilli()
if timestamp < sf.lastTimestamp {
return 0, fmt.Errorf("clock still behind after waiting")
}
}
}
// 正常生成逻辑
return sf.Snowflake.Generate()
}
Worker ID自动分配
// 基于ZooKeeper的Worker ID分配
type WorkerIDAllocator struct {
zkConn *zk.Conn
basePath string
}
func (w *WorkerIDAllocator) AllocateWorkerID() (int64, error) {
// 创建临时节点
workerPath := w.basePath + "/workers"
// 获取已有worker列表
children, _, err := w.zkConn.Children(workerPath)
if err != nil {
return 0, err
}
// 找到可用的worker ID
usedIDs := make(map[int64]bool)
for _, child := range children {
id, _ := strconv.ParseInt(child, 10, 64)
usedIDs[id] = true
}
for id := int64(0); id < 32; id++ {
if !usedIDs[id] {
// 创建临时节点,会话断开自动释放
path := fmt.Sprintf("%s/%d", workerPath, id)
_, err := w.zkConn.Create(path, []byte{}, zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
if err == nil {
return id, nil
}
}
}
return 0, fmt.Errorf("no available worker ID")
}
号段模式(Leaf-segment)
数据库号段方案
-- 号段表设计
CREATE TABLE id_generator (
biz_tag VARCHAR(128) PRIMARY KEY, -- 业务标识
max_id BIGINT NOT NULL, -- 当前最大ID
step INT NOT NULL, -- 号段步长
description VARCHAR(256),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 初始化
INSERT INTO id_generator (biz_tag, max_id, step, description)
VALUES ('order', 0, 1000, '订单ID生成器');
type SegmentGenerator struct {
db *sql.DB
bizTag string
step int64
mu sync.Mutex
current int64
maxID int64
}
func (g *SegmentGenerator) GetID() (int64, error) {
g.mu.Lock()
defer g.mu.Unlock()
if g.current >= g.maxID {
// 从数据库获取新号段
if err := g.loadNextSegment(); err != nil {
return 0, err
}
}
id := g.current
g.current++
return id, nil
}
func (g *SegmentGenerator) loadNextSegment() error {
// 使用乐观锁更新号段
result, err := g.db.Exec(`
UPDATE id_generator
SET max_id = max_id + step
WHERE biz_tag = ?
`, g.bizTag)
if err != nil {
return err
}
rowsAffected, _ := result.RowsAffected()
if rowsAffected == 0 {
return fmt.Errorf("failed to update segment")
}
// 获取更新后的max_id
var maxID int64
err = g.db.QueryRow(`
SELECT max_id FROM id_generator WHERE biz_tag = ?
`, g.bizTag).Scan(&maxID)
if err != nil {
return err
}
g.maxID = maxID
g.current = maxID - g.step
return nil
}
// 双Buffer优化:预加载下一个号段
type DoubleBufferGenerator struct {
db *sql.DB
bizTag string
step int64
mu sync.Mutex
buffer1 *Segment
buffer2 *Segment
active *Segment
}
type Segment struct {
current int64
maxID int64
}
func (g *DoubleBufferGenerator) GetID() (int64, error) {
g.mu.Lock()
defer g.mu.Unlock()
if g.active.current >= g.active.maxID {
// 切换到备用buffer
if g.active == g.buffer1 {
g.active = g.buffer2
} else {
g.active = g.buffer1
}
// 异步加载新的号段到非活跃buffer
go g.loadNextSegment(g.getInactiveBuffer())
}
id := g.active.current
g.active.current++
return id, nil
}
Redis序列号生成
type RedisIDGenerator struct {
client *redis.Client
prefix string
}
func (g *RedisIDGenerator) Generate(ctx context.Context) (int64, error) {
// 使用INCR命令生成递增ID
key := fmt.Sprintf("%s:id", g.prefix)
id, err := g.client.Incr(ctx, key).Result()
if err != nil {
return 0, err
}
return id, nil
}
// 带日期的ID生成
func (g *RedisIDGenerator) GenerateWithDate(ctx context.Context) (string, error) {
date := time.Now().Format("20060102")
key := fmt.Sprintf("%s:id:%s", g.prefix, date)
id, err := g.client.Incr(ctx, key).Result()
if err != nil {
return "", err
}
// 设置过期时间(保留7天)
g.client.Expire(ctx, key, 7*24*time.Hour)
return fmt.Sprintf("%s%06d", date, id), nil
}
美团Leaf分布式ID服务
架构设计
Leaf服务架构:
┌─────────────────────────────────────────────────────────┐
│ Leaf Server │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 号段模式 │ │ Snowflake │ │ 号段+雪花 │ │
│ │ (Leaf-segment)│ │ 模式 │ │ 混合模式 │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────────┴─────────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ ID生成器 │ │
│ └──────┬──────┘ │
└───────────────────────────┼─────────────────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ MySQL │ │ ZK │ │ Redis │
│ (号段) │ │(Worker) │ │ (缓存) │
└─────────┘ └─────────┘ └─────────┘
性能对比测试
func BenchmarkIDGenerators(b *testing.B) {
// UUID
b.Run("UUID", func(b *testing.B) {
for i := 0; i < b.N; i++ {
uuid.New().String()
}
})
// Snowflake
sf, _ := NewSnowflake(1, 1)
b.Run("Snowflake", func(b *testing.B) {
for i := 0; i < b.N; i++ {
sf.Generate()
}
})
// Redis INCR
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
redisGen := &RedisIDGenerator{client: rdb, prefix: "test"}
b.Run("Redis", func(b *testing.B) {
for i := 0; i < b.N; i++ {
redisGen.Generate(context.Background())
}
})
}
// 结果(单线程):
// UUID: ~200 ns/op
// Snowflake: ~500 ns/op
// Redis: ~2000 ns/op(网络延迟)
总结
分布式ID生成方案选择指南:
- UUID:适合无需排序、对性能要求不高的场景
- Snowflake:高并发、需要趋势有序的首选方案
- 号段模式:中等并发、依赖数据库的场景
- Redis INCR:简单场景、已有Redis基础设施
关键考虑因素:有序性需求、性能要求、基础设施依赖、时钟回拨处理。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。