分布式ID生成算法|雪花算法 Snowflake | Go实现

写在前面

在分布式领域中,不可避免的需要生成一个全局唯一ID 。而在近几年的发展中有许多分布式ID生成算法,比较经典的就是 Twitter 的雪花算法(Snowflake Algorithm)。当然国内也有美团的基于snowflake改进的Leaf算法。那么今天我们就来介绍一下雪花算法。

雪花算法

算法来源: 世界上没有完全相同的两片雪花 。所以!雪崩的时候,没有任何一片雪花是相同的!

雪花算法的本质是生成一个64位的 long int 类型的id,可以拆分成一下几个部分:

  • 最高位固定位0。因为第一位为符号位,如果是1那么就是负数了。
  • 接下来的 41 位存储毫秒级时间戳,2^41 大概可以使用69年。
  • 再接来就是10位存储机器码,包括 5 位dataCenterId 和 5 位 workerId。最多可以部署2^10=1024台机器。
  • 最后12位存储序列号。统一毫秒时间戳时,通过这个递增的序列号来区分。即对于同一台机器而言,同一毫秒时间戳下可以生成 2^12=4096 个不重复id

雪花算法其实是强依赖于时间戳的,因为我们看上面生成的几个数字,我们唯一不可控的就是时间,如果发生了时钟回拨有可能会发生id生成一样了。

所以雪花算法适合那些与时间有强关联的业务 ,比如订单,交易之类的,需要有时间强相关的业务。

生成 ID 流程图

下面会结合代码讲述详细讲述这张图

代码实现

前置工作

既然是由上述的几个部分组成,那么我们可以先定义几个常量

go 复制代码
// 时间戳的 占用位数
timestampBits = 41
// dataCenterId 的占用位数
dataCenterIdBits = 5
// workerId 的占用位数
workerIdBits = 5
// sequence 的占用位数
seqBits = 12

并且定义各个字段的最大值,防止越界

go 复制代码
// timestamp 最大值, 相当于 2^41-1 = 2199023255551
timestampMaxValue = -1 ^ (-1 << timestampBits)
// dataCenterId 最大值, 相当于 2^5-1 = 31
dataCenterIdMaxValue = -1 ^ (-1 << dataCenterIdBits)
// workId 最大值, 相当于 2^5-1 = 31
workerIdMaxValue = -1 ^ (-1 << workerIdBits)
// sequence 最大值, 相当于 2^12-1 = 4095
seqMaxValue = -1 ^ (-1 << seqBits)

移动位数

go 复制代码
// workId 向左移动12位(seqBits占用位数)因为这12位是sequence占的
workIdShift = 12
// dataCenterId 向左移动17位 (seqBits占用位数 + workId占用位数)
dataCenterIdShift = 17
// timestamp 向左移动22位 (seqBits占用位数 + workId占用位数 + dataCenterId占用位数)
timestampShift = 22

定义雪花生成器的对象,定义上面我们介绍的几个字段即可

go 复制代码
type SnowflakeSeqGenerator struct {
	mu           *sync.Mutex
	timestamp    int64
	dataCenterId int64
	workerId     int64
	sequence     int64
}
go 复制代码
func NewSnowflakeSeqGenerator(dataCenterId, workId int64) (r *SnowflakeSeqGenerator, err error) {
	if dataCenterId < 0 || dataCenterId > dataCenterIdMaxValue {
		err = fmt.Errorf("dataCenterId should between 0 and %d", dataCenterIdMaxValue-1)
		return
	}

	if workId < 0 || workId > workerIdMaxValue {
		err = fmt.Errorf("workId should between 0 and %d", dataCenterIdMaxValue-1)
		return
	}

	return &SnowflakeSeqGenerator{
		mu:           new(sync.Mutex),
		timestamp:    defaultInitValue - 1,
		dataCenterId: dataCenterId,
		workerId:     workId,
		sequence:     defaultInitValue,
	}, nil
}

具体算法

timestamp存储的是上一次的计算时间,如果当前的时间比上一次的时间还要小,那么说明发生了时钟回拨,那么此时我们不进行生产id,并且记录错误日志。

go 复制代码
now := time.Now().UnixMilli()
if S.timestamp > now { // Clock callback
	log.Errorf("Clock moved backwards. Refusing to generate ID, last timestamp is %d, now is %d", S.timestamp, now)
	return ""
}

如果时间相等的话,那就说明这是在 同一毫秒时间戳内生成的 ,那么就进行seq的自旋,在这同一毫秒内最多生成 4095 个。如果超过4095的话,就等下一毫秒。

go 复制代码
if S.timestamp == now {
// generate multiple IDs in the same millisecond, incrementing the sequence number to prevent conflicts
	S.sequence = (S.sequence + 1) & seqMaxValue
	if S.sequence == 0 {
		// sequence overflow, waiting for next millisecond
		for now <= S.timestamp {
			now = time.Now().UnixMilli()
		}
	}
}

那么如果是不在同一毫秒内的话,seq直接用初始值就好了

go 复制代码
else {
	// initialized sequences are used directly at different millisecond timestamps
	S.sequence = defaultInitValue
}

如果超过了69年,也就是时间戳超过了69年,也不能再继续生成了

go 复制代码
tmp := now - epoch
if tmp > timestampMaxValue {
	log.Errorf("epoch should between 0 and %d", timestampMaxValue-1)
	return ""
}

记录这一次的计算时间,这样就可以和下一次的生成的时间做对比了。

go 复制代码
S.timestamp = now

timestamp + dataCenterId + workId + sequence 拼凑一起,注意一点是我们最好用字符串输出,因为前端js中的number类型超过53位会溢出的

go 复制代码
// combine the parts to generate the final ID and convert the 64-bit binary to decimal digits.
r := (tmp)<<timestampShift |
	(S.dataCenterId << dataCenterIdShift) |
	(S.workerId << workIdShift) |
	(S.sequence)

return fmt.Sprintf("%d", r)

完整代码 & 测试文件

go 复制代码
package sequence

import (
	"fmt"
	"sync"
	"time"

	"github.com/seata/seata-go/pkg/util/log"
)

// SnowflakeSeqGenerator snowflake gen ids
// ref: https://en.wikipedia.org/wiki/Snowflake_ID

var (
	// set the beginning time
	epoch = time.Date(2024, time.January, 01, 00, 00, 00, 00, time.UTC).UnixMilli()
)

const (
	// timestamp occupancy bits
	timestampBits = 41
	// dataCenterId occupancy bits
	dataCenterIdBits = 5
	// workerId occupancy bits
	workerIdBits = 5
	// sequence occupancy bits
	seqBits = 12

	// timestamp max value, just like 2^41-1 = 2199023255551
	timestampMaxValue = -1 ^ (-1 << timestampBits)
	// dataCenterId max value, just like 2^5-1 = 31
	dataCenterIdMaxValue = -1 ^ (-1 << dataCenterIdBits)
	// workId max value, just like 2^5-1 = 31
	workerIdMaxValue = -1 ^ (-1 << workerIdBits)
	// sequence max value, just like 2^12-1 = 4095
	seqMaxValue = -1 ^ (-1 << seqBits)

	// number of workId offsets (seqBits)
	workIdShift = 12
	// number of dataCenterId offsets (seqBits + workerIdBits)
	dataCenterIdShift = 17
	// number of timestamp offsets (seqBits + workerIdBits + dataCenterIdBits)
	timestampShift = 22

	defaultInitValue = 0
)

type SnowflakeSeqGenerator struct {
	mu           *sync.Mutex
	timestamp    int64
	dataCenterId int64
	workerId     int64
	sequence     int64
}

// NewSnowflakeSeqGenerator initiates the snowflake generator
func NewSnowflakeSeqGenerator(dataCenterId, workId int64) (r *SnowflakeSeqGenerator, err error) {
	if dataCenterId < 0 || dataCenterId > dataCenterIdMaxValue {
		err = fmt.Errorf("dataCenterId should between 0 and %d", dataCenterIdMaxValue-1)
		return
	}

	if workId < 0 || workId > workerIdMaxValue {
		err = fmt.Errorf("workId should between 0 and %d", dataCenterIdMaxValue-1)
		return
	}

	return &SnowflakeSeqGenerator{
		mu:           new(sync.Mutex),
		timestamp:    defaultInitValue - 1,
		dataCenterId: dataCenterId,
		workerId:     workId,
		sequence:     defaultInitValue,
	}, nil
}

// GenerateId timestamp + dataCenterId + workId + sequence
func (S *SnowflakeSeqGenerator) GenerateId(entity string, ruleName string) string {
	S.mu.Lock()
	defer S.mu.Unlock()

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

	if S.timestamp > now { // Clock callback
		log.Errorf("Clock moved backwards. Refusing to generate ID, last timestamp is %d, now is %d", S.timestamp, now)
		return ""
	}

	if S.timestamp == now {
		// generate multiple IDs in the same millisecond, incrementing the sequence number to prevent conflicts
		S.sequence = (S.sequence + 1) & seqMaxValue
		if S.sequence == 0 {
			// sequence overflow, waiting for next millisecond
			for now <= S.timestamp {
				now = time.Now().UnixMilli()
			}
		}
	} else {
		// initialized sequences are used directly at different millisecond timestamps
		S.sequence = defaultInitValue
	}
	tmp := now - epoch
	if tmp > timestampMaxValue {
		log.Errorf("epoch should between 0 and %d", timestampMaxValue-1)
		return ""
	}
	S.timestamp = now

	// combine the parts to generate the final ID and convert the 64-bit binary to decimal digits.
	r := (tmp)<<timestampShift |
		(S.dataCenterId << dataCenterIdShift) |
		(S.workerId << workIdShift) |
		(S.sequence)

	return fmt.Sprintf("%d", r)
}

测试文件

go 复制代码
func TestSnowflakeSeqGenerator_GenerateId(t *testing.T) {
	var dataCenterId, workId int64 = 1, 1
	generator, err := NewSnowflakeSeqGenerator(dataCenterId, workId)
	if err != nil {
		t.Error(err)
		return
	}
	var x, y string
	for i := 0; i < 100; i++ {
		y = generator.GenerateId("", "")
		if x == y {
			t.Errorf("x(%s) & y(%s) are the same", x, y)
		}
		x = y
	}
}
相关推荐
2301_764441335 小时前
LISA时空跃迁分析,地理时空分析
数据结构·python·算法
东北洗浴王子讲AI5 小时前
GPT-5.4辅助算法设计与优化:从理论到实践的系统方法
人工智能·gpt·算法·chatgpt
Billlly5 小时前
ABC 453 个人题解
算法·题解·atcoder
玉树临风ives5 小时前
atcoder ABC 452 题解
数据结构·算法
Dontla6 小时前
go语言Windows安装教程(安装go安装Golang安装)(GOPATH、Go Modules)
开发语言·windows·golang
feifeigo1236 小时前
基于马尔可夫随机场模型的SAR图像变化检测源码实现
算法
铁东博客6 小时前
Go实现周易大衍筮法三变取爻
开发语言·后端·golang
fengfuyao9856 小时前
基于STM32的4轴步进电机加减速控制工程源码(梯形加减速算法)
网络·stm32·算法
无敌昊哥战神7 小时前
深入理解 C 语言:巧妙利用“0地址”手写 offsetof 宏与内存对齐机制
c语言·数据结构·算法
小白菜又菜7 小时前
Leetcode 2075. Decode the Slanted Ciphertext
算法·leetcode·职场和发展