分布式全局唯一ID: snowflake 算法

在分布式系统中,经常需要生成全局唯一ID。雪花算法是由Twitter开源的一种生成全局唯一ID的算法,在业中广泛应用。本文将介绍雪花算法,以及Go如何使用雪花算法来生成全局唯一ID。

snowflake

雪花算法的核心思想是将一个64bit的整数分成多个部分,包括: 时间戳、机器ID、序列号等, 不依赖mysql、redis、内存等,通过服务器时间、定义的工作机器ID和序号来保证全局唯一性,同时具体一定的有序性。生成的值大概是这样的:

js 复制代码
1765396306500521984
1765757491058053120
1765758879200710656

组成部分

json 复制代码
|    最高位    |      中位         |     中下位      |       末位           |
+--------------------------------------------------------------------------+
| 1 Bit Unused | 41 Bit Timestamp |  10 Bit NodeID  |   12 Bit Sequence ID |
+--------------------------------------------------------------------------+
  • 最高位占1 bit,值固定为 0
  • 中位占 41 bit,值为毫秒级时间戳,总共可以容纳约70年左右的时间
  • 中下位占 10 bit,值为工作机器的 ID,值的上限为 1024
  • 末位占 12 bit,值为当前毫秒内 0开始不断累加,值的上限为 4096

所以,SnowFlake一毫秒内最多可以生成全局唯一ID的数量为:1024 X 4096 = 4194304

Go实现snowflake

参考[4]中的Example, 看看Go如何实现snowflake

go 复制代码
package main

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

// 定义常量
const (
	epoch           = 1704038400000 // 2024-01-01的时间戳
	workerIDBits    = 5             // 机器ID所占的位数
	sequenceBits    = 8             // 序列号所占的位数
	maxWorkerID     = -1 ^ (-1 << workerIDBits)
	maxSequence     = -1 ^ (-1 << sequenceBits)
	workerIDShift   = sequenceBits
	timestampShift  = workerIDBits + sequenceBits
)

// 定义结构体
type Snowflake struct {
	mu           sync.Mutex
	workerID     int64
	lastTimestamp int64
	sequence     int64
}

// 创建Snowflake实例
func NewSnowflake(workerID int64) (*Snowflake, error) {
	if workerID < 0 || workerID > maxWorkerID {
		return nil, errors.New("invalid worker ID")
	}
	return &Snowflake{
		workerID: workerID,
	}, nil
}

// 生成ID的方法
func (s *Snowflake) GenerateID() int64 {
	s.mu.Lock()
	defer s.mu.Unlock()

	timestamp := time.Now().UnixNano() / 1000000
	if timestamp < s.lastTimestamp {
		panic("clock moved backwards")
	}

	if timestamp == s.lastTimestamp {
		s.sequence = (s.sequence + 1) & maxSequence
		if s.sequence == 0 {
			// 当前毫秒的序列号已经用完,等待下一毫秒
			for timestamp <= s.lastTimestamp {
				timestamp = time.Now().UnixNano() / 1000000
			}
		}
	} else {
		s.sequence = 0
	}

	s.lastTimestamp = timestamp

	id := ((timestamp - epoch) << timestampShift) | (s.workerID << workerIDShift) | s.sequence
	return id
}

// 使用示例
func main() {
	snowflake, err := NewSnowflake(1)
	if err != nil {
		panic(err)
	}

	id := snowflake.GenerateID()
	println(id)
}

可以看到,实现的代码简练,不过短短的数十行。通过传入node值,调用NewSnowflake生成一个ID生成器,执行函数GenerateID生成唯一id。让我们写一下基准测试,看看性能如何。

go 复制代码
func BenchmarkGenerate(b *testing.B) {
	node, err := NewNode(1)
	if err != nil {
		b.Fatal(err)
	}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		_ = node.Generate()
	}
}

输出:

js 复制代码
BenchmarkGenerate
BenchmarkGenerate-4        64616             28031 ns/op

当然,snowflake的缺点也是比较明显的:

  1. 服务器的时间发生了错乱或者回拨, 有很大 概率生成重复的 ID
  2. 中位是 41 bit 的毫秒级时间戳,也就是从当前起始到41 bit 耗尽, 只够用70 年左右

bwmarrin/snowflake

bwmarrin/snowflake 是一个非常简单的、稳定且完整的Twitter 第三方雪花生成器。它提供了:

  • 解析现有雪花 ID 的方法。
  • 将雪花 ID 转换为多种其他数据类型并返回的方法。
  • JSON Marshal/Unmarshal 函数可在 JSON API 中轻松使用雪花 ID。
  • 单调时钟计算可防止时钟漂移。
go 复制代码
go get github.com/bwmarrin/snowflake

一个入门级的例子

go 复制代码
package main

import (
    "fmt"

    "github.com/bwmarrin/snowflake"
)

func main() {

    // Create a new Node with a Node number of 1
    node, err := snowflake.NewNode(1)
    if err != nil {
       fmt.Println(err)
       return
    }

    // Generate a snowflake ID.
    id := node.Generate()

    // Print out the ID in a few different ways.
    fmt.Printf("Int64  ID: %d\n", id)
    fmt.Printf("String ID: %s\n", id)
    fmt.Printf("Base2  ID: %s\n", id.Base2())
    fmt.Printf("Base64 ID: %s\n", id.Base64())

    // Print out the ID's timestamp
    fmt.Printf("ID Time  : %d\n", id.Time())

    // Print out the ID's node number
    fmt.Printf("ID Node  : %d\n", id.Node())

    // Print out the ID's sequence number
    fmt.Printf("ID Step  : %d\n", id.Step())

    // Generate and print, all in one.
    fmt.Printf("ID       : %d\n", node.Generate().Int64())
}

输出

js 复制代码
Int64  ID: 1765396306500521984
String ID: 1765396306500521984
Base2  ID: 1100001111111111100101001011001011000110000000001000000000000
Base64 ID: MTc2NTM5NjMwNjUwMDUyMTk4NA==
ID Time  : 1709738253604
ID Node  : 1
ID Step  : 0
ID       : 1765396306500521985

为了和上例进行对比,这里我们做了基准测试,仅供参考:

go 复制代码
func BenchmarkGenerate(b *testing.B) {
    node, err := snowflake.NewNode(1)
    if err != nil {
       fmt.Println(err)
       return
    }

    b.ResetTimer()

    for i := 0; i < b.N; i++ {
       _ = node.Generate()
    }
}
js 复制代码
BenchmarkGenerate
BenchmarkGenerate-4      1000000              1119 ns/op

更多的snowflake算法实现可以参考:链接【3】【4】

总结

本文简要介绍了雪花算法, 核心是使用时间戳、机器ID、序列号等生成一个64bit整数的全局唯一ID。文中还介绍了Go语言是如何实践的,并介绍了使用snowflake的几个第三方库bwmarrin/snowflake、sonyflake等,并对比了几个基准测试结果,简单且高效。

参考

  1. github.com/twitter-arc...
  2. github.com/bwmarrin/sn...
  3. github.com/sony/sonyfl...
  4. github.com/rs/xid
  5. mp.weixin.qq.com/s/zT4kKkaGd...
  6. zhuanlan.zhihu.com/p/85837641
相关推荐
XINGTECODE6 分钟前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
程序猿进阶12 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺16 分钟前
Spring Boot框架Starter组件整理
java·spring boot·后端
凡人的AI工具箱38 分钟前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
先天牛马圣体43 分钟前
如何提升大型AI模型的智能水平
后端
java亮小白19971 小时前
Spring循环依赖如何解决的?
java·后端·spring
2301_811274311 小时前
大数据基于Spring Boot的化妆品推荐系统的设计与实现
大数据·spring boot·后端
运维&陈同学1 小时前
【zookeeper01】消息队列与微服务之zookeeper工作原理
运维·分布式·微服务·zookeeper·云原生·架构·消息队列
时差9532 小时前
Flink Standalone集群模式安装部署
大数据·分布式·flink·部署
菠萝咕噜肉i2 小时前
超详细:Redis分布式锁
数据库·redis·分布式·缓存·分布式锁