Dubbo 学习笔记
· 阅读需 1 分钟
雪花算法(Snowflake)是 Twitter 开源的分布式 ID 生成算法,核心目标是在分布式系统中生成全局唯一、趋势递增的 64 位整数 ID。它解决了传统 UUID 过长、数据库自增 ID 无法分布式部署的问题,广泛应用于微服务、分布式数据库、消息队列等场景。本文将从原理、特性、Go 实现三个维度带你掌握雪花算法。
雪花算法将 64 位整数(int64)拆分为 4 个部分(不同实现的分段可能略有差异,以下是最经典的版本):
| 位段 | 位数 | 作用 |
|---|---|---|
| 符号位 | 1 | 固定为 0(保证 ID 为正数) |
| 时间戳 | 41 | 存储毫秒级时间戳(相对于某个起始时间),可支持约 69 年(2^41/1000/3600/24/365 ≈ 69) |
| 机器/节点 ID | 10 | 标识分布式节点(可拆分为 5 位数据中心 + 5 位机器,支持 1024 个节点) |
| 序列号 | 12 | 同一毫秒内的自增序列(支持每个节点每毫秒生成 4096 个 ID) |
计算验证:1 + 41 + 10 + 12 = 64,刚好占满 int64 位空间。
以下是一个完整、健壮的雪花算法 Go 实现,包含时钟回拨防护、并发安全、可配置节点 ID 等特性:
package main
import (
"errors"
"fmt"
"sync"
"time"
)
// Snowflake 雪花算法生成器
type Snowflake struct {
mu sync.Mutex // 保证并发安全
epoch int64 // 起始时间戳(毫秒)
nodeID int64 // 节点ID
sequence int64 // 毫秒内序列号
lastStamp int64 // 上一次生成ID的时间戳(毫秒)
// 位段掩码与偏移量(根据64位结构定义)
nodeIDBits int64 = 10 // 节点ID位数
sequenceBits int64 = 12 // 序列号位数
nodeIDMax int64 = -1 ^ (-1 << nodeIDBits) // 节点ID最大值(1023)
sequenceMax int64 = -1 ^ (-1 << sequenceBits) // 序列号最大值(4095)
nodeIDShift int64 = sequenceBits // 节点ID偏移量
timeStampShift int64 = sequenceBits + nodeIDBits // 时间戳偏移量
}
// NewSnowflake 创建雪花算法实例
// epoch: 起始时间戳(毫秒),如 1704067200000(2024-01-01 00:00:00)
// nodeID: 节点ID(0-1023)
func NewSnowflake(epoch int64, nodeID int64) (*Snowflake, error) {
// 校验节点ID范围
if nodeID < 0 || nodeID > (-1 ^ (-1 << 10)) {
return nil, errors.New("node ID must be between 0 and 1023")
}
return &Snowflake{
epoch: epoch,
nodeID: nodeID,
sequence: 0,
lastStamp: -1,
}, nil
}
// 获取当前毫秒级时间戳
func (s *Snowflake) now() int64 {
return time.Now().UnixMilli()
}
// NextID 生成下一个唯一ID
func (s *Snowflake) NextID() (int64, error) {
s.mu.Lock()
defer s.mu.Unlock()
// 1. 获取当前时间戳
now := s.now()
// 2. 处理时钟回拨
if now < s.lastStamp {
return 0, fmt.Errorf("clock moved backwards. Refusing to generate ID for %d milliseconds", s.lastStamp-now)
}
// 3. 同一毫秒内,序列号自增
if now == s.lastStamp {
s.sequence = (s.sequence + 1) & s.sequenceMax
// 序列号溢出(同一毫秒生成超过4096个ID)
if s.sequence == 0 {
// 等待到下一毫秒
for now <= s.lastStamp {
now = s.now()
}
}
} else {
// 不同毫秒,重置序列号
s.sequence = 0
}
// 4. 更新上一次时间戳
s.lastStamp = now
// 5. 组合64位ID
// 时间戳部分:(now - epoch) << 22
// 节点ID部分:nodeID << 12
// 序列号部分:sequence
id := ((now - s.epoch) << s.timeStampShift) | (s.nodeID << s.nodeIDShift) | s.sequence
return id, nil
}
// 测试示例
func main() {
// 自定义起始时间(2024-01-01 00:00:00)
epoch := int64(1704067200000)
// 节点ID(假设当前节点为1)
nodeID := int64(1)
// 创建雪花算法实例
sf, err := NewSnowflake(epoch, nodeID)
if err != nil {
panic(err)
}
// 并发生成10个ID测试
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
id, err := sf.NextID()
if err != nil {
fmt.Printf("生成ID失败:%v\n", err)
return
}
fmt.Printf("第%d个ID:%d\n", idx+1, id)
}(i)
}
wg.Wait()
}
mu:互斥锁,保证多 goroutine 下生成 ID 的并发安全;epoch:算法的起始时间戳(自定义,建议设置为项目上线时间),减少时间戳占用的位数;nodeID:节点唯一标识,需在分布式环境中提前分配(如 0-1023);lastStamp:上一次生成 ID 的时间戳,用于检测时钟回拨和同一毫秒的序列号递增;第1个ID:1234567890123456789
第2个ID:1234567890123456790
第3个ID:1234567890123456791
...
第10个ID:1234567890123456798
上述实现中,时钟回拨直接返回错误,实际生产中可优化为:
分布式环境中,节点 ID 需保证唯一,常见分配方式:
✅ 适合:分布式系统全局唯一 ID、订单号、日志 ID、消息 ID 等; ❌ 不适合:需要严格连续的 ID(雪花算法是趋势递增,非严格连续)、需要跨系统长期兼容的 ID(若修改 epoch 或节点 ID 规则会导致 ID 重复)。
| 方案 | 优点 | 缺点 |
|---|---|---|
| 雪花算法 | 高性能、全局唯一、趋势递增 | 依赖时钟、节点 ID 需唯一、时钟回拨风险 |
| UUID | 无需配置、完全唯一 | 过长(128位)、无序、不利于索引 |
| 数据库自增 ID | 简单、严格递增 | 分布式部署困难、性能瓶颈 |
Cassandraweb-vitals是Google发起的,旨在提供各种质量信号的统一指南,我们相信这些质量信号对提供出色的网络用户体验至关重要。多年来,Google 提供了许多性能测量和性能报告工具。一些开发者对这些工具的使用十分在行,而另一些开发者则发现大量的工具和指标令人应接不暇。
总之,Web Vitals则是希望提出一些标准,来帮助开发者去测量,优化页面,给到用户更好的体验。