Post

Snowflake 算法详解

Snowflake 算法详解

Snowflake (雪花算法) 详解

在分布式系统中,生成全局唯一且有序的 ID 是一个常见需求。Twitter 开源的 Snowflake(雪花算法) 是目前最流行的解决方案之一。

它可以在分布式系统中生成唯一的 ID,并且 ID 大致保持有序(按时间递增)。

核心思想

Snowflake 生成的 ID 是一个 64 位的整数int64)。这 64 位被划分成不同的部分,每一部分代表不同的含义,从而保证了 ID 的唯一性。

ID 结构拆解

标准的 Snowflake 算法将 64 位划分为以下 4 个部分:

1
2
3
4
5
+------+----------------------+----------------+-----------+
| 1bit |        41bits        |     10bits     |   12bits  |
+------+----------------------+----------------+-----------+
| 符号 |       时间戳         |    机器ID      |   序列号  |
+------+----------------------+----------------+-----------+

1. 符号位 (1 bit)

  • 占用:第 1 位。
  • 含义:由于 int64 在很多编程语言中是有符号的,最高位为符号位。正数是 0,负数是 1。为了生成的 ID 是正整数,这一位固定为 0

2. 时间戳 (41 bits)

  • 占用:第 2 到 42 位。
  • 含义:记录生成 ID 的时间戳(毫秒级)。
  • 范围:$2^{41} - 1$ 毫秒,大约可以使用 69 年。
    • 注意:这里存储的不是绝对时间戳,而是当前时间 - 开始时间(自定义的 Epoch,例如项目的上线时间)。
    • $ (2^{41} - 1) / (1000 \times 60 \times 60 \times 24 \times 365) \approx 69 \text{年} $

3. 机器 ID (10 bits)

  • 占用:第 43 到 52 位。
  • 含义:用于标识生成 ID 的机器(Worker ID)。
  • 范围:$2^{10} = 1024$ 个节点。
  • 通常拆分:这 10 位通常又被拆分为 5 位数据中心 ID (Data Center ID)5 位工作机器 ID (Worker ID),这样可以支持 32 个数据中心,每个数据中心部署 32 台机器。

4. 序列号 (12 bits)

  • 占用:最后 12 位。
  • 含义:用于同一毫秒内生成多个 ID 时的区分。
  • 范围:$2^{12} = 4096$。意味着每台机器在同一毫秒内理论上最多可以生成 4096 个 ID。

算法优缺点

优点

  1. 高性能:生成 ID 不依赖数据库,完全在内存中进行,每秒可生成数百万 ID。
  2. 有序性:基于时间戳,ID 大致随时间递增,对数据库索引友好(如 MySQL 的 B+ 树)。
  3. 唯一性:通过时间戳、机器 ID 和序列号的组合,保证了分布式环境下的唯一性。
  4. 高可用:不依赖第三方服务(如 Redis、ZooKeeper),部署简单。

缺点

  1. 时钟回拨问题:严重依赖服务器系统时间。如果时钟回拨(时间倒退),可能会导致 ID 重复。
    • 解决方案:如果发现当前时间小于上次生成时间,拒绝生成 ID 或等待时钟追上。
  2. 机器 ID 分配:需要规划好 Worker ID 的分配机制,防止不同机器使用相同的 ID。

实现示例 (Go 语言)

下面是一个简化的 Go 语言实现示例,用于理解其逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package main

import (
    "errors"
    "fmt"
    "sync"
    "time"
)

// 定义常量
const (
    epoch             = 1640995200000 // 自定义起始时间 (2022-01-01 00:00:00 UTC)
    workerIDBits      = 5             // 机器ID占用的位数
    datacenterIDBits  = 5             // 数据中心ID占用的位数
    sequenceBits      = 12            // 序列号占用的位数

    maxWorkerID     = -1 ^ (-1 << workerIDBits)     // 31
    maxDatacenterID = -1 ^ (-1 << datacenterIDBits) // 31
    maxSequence     = -1 ^ (-1 << sequenceBits)     // 4095

    workerIDShift      = sequenceBits
    datacenterIDShift  = sequenceBits + workerIDBits
    timestampShift     = sequenceBits + workerIDBits + datacenterIDBits
)

type Snowflake struct {
    mu           sync.Mutex
    lastTimestamp int64
    workerID      int64
    datacenterID  int64
    sequence      int64
}

func NewSnowflake(workerID, datacenterID int64) (*Snowflake, error) {
    if workerID < 0 || workerID > maxWorkerID {
        return nil, errors.New("worker ID out of range")
    }
    if datacenterID < 0 || datacenterID > maxDatacenterID {
        return nil, errors.New("datacenter ID out of range")
    }
    return &Snowflake{
        workerID:     workerID,
        datacenterID: datacenterID,
        lastTimestamp: -1,
    }, nil
}

func (s *Snowflake) NextID() (int64, error) {
    s.mu.Lock()
    defer s.mu.Unlock()

    currentTimestamp := time.Now().UnixMilli()

    // 检查时钟回拨
    if currentTimestamp < s.lastTimestamp {
        return 0, errors.New("clock moved backwards")
    }

    if currentTimestamp == s.lastTimestamp {
        // 同一毫秒内,序列号自增
        s.sequence = (s.sequence + 1) & maxSequence
        if s.sequence == 0 {
            // 序列号溢出,等待下一毫秒
            for currentTimestamp <= s.lastTimestamp {
                currentTimestamp = time.Now().UnixMilli()
            }
        }
    } else {
        // 新的毫秒,序列号重置
        s.sequence = 0
    }

    s.lastTimestamp = currentTimestamp

    // 拼接 ID
    id := ((currentTimestamp - epoch) << timestampShift) |
        (s.datacenterID << datacenterIDShift) |
        (s.workerID << workerIDShift) |
        s.sequence

    return id, nil
}

func main() {
    node, _ := NewSnowflake(1, 1)
    for i := 0; i < 5; i++ {
        id, _ := node.NextID()
        fmt.Printf("Generated ID: %d\n", id)
    }
}

总结

Snowflake 算法是分布式 ID 生成的标准答案之一。理解其位运算逻辑和结构划分,有助于我们在实际业务中根据需求(如更高的并发、更长的寿命)对算法进行定制和优化。

This post is licensed under CC BY 4.0 by the author.