当前位置: 首页 > 工具软件 > go-snowflake > 使用案例 >

golang中的分布式ID生成器snowflake原理解析

安高义
2023-12-01

安装包 go get -u github.com/bwmarrin/snowflake

概念

总体而言,snowflake生成的ID是一个 int64 的整形,它由一下几个部分组成。
1、unused 1bit
2、time 41bit 毫秒
3、datacenter_id 5bit 数据中心ID
4、work_id 5bit 机器ID
5、sequence_id 12bit 循环自增ID,到达最大值 1111 1111 1111 后归零,如果时间来到了下一毫秒也会归零。

															 work_id-5bit
																   |
																   |
	0|0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0|0000 0|0000 0|0000 0000 0000
	|							|							|					|
	|							|							|					|
unused-1bit				time_ms-41bit					center_id-5bit		sequence_id-12bit

1、数据中心ID加机器ID共有10位,因此最大值为1024,当然也可以合并一起叫做Node。

2、同一个Node上在同一毫秒内可以产生 2^12 = 4096 条,每秒就是 409.6 万条。

3、表示 timestamp 的 41 位,可以支持我们使用 69 年。当然,我们的时间毫秒计数不会真的从 1970 年开始记,那样我们的系统跑到 2039/9/7 23:47:35 就不能用了,所以这里的 timestamp 只是相对于某个时间的增量,比如我们的系统上线是 2018-08-01,那么我们可以把这个 timestamp 当作是从 2018-08-01 00:00:00.000 的偏移量,这个后面看源码。

4、timestamp , datacenter_id , worker_id 和 sequence_id 这四个字段中,timestamp 和 sequence_id 是由程序在运行期生成的。但 datacenter_id 和worker_id 需要我们在部署阶段就能够获取得到,并且一旦程序启动之后,就是不可更改的了(想想,如果可以随意更改,可能被不慎修改,造成最终生成的 id 有冲突)。

5、一般不同数据中心的机器,会提供对应的获取数据中心 id 的 API,所以datacenter_id 我们可以在部署阶段轻松地获取到。而 worker_id 是我们逻辑上给机器分配的一个 id,这个要怎么办呢?比较简单的想法是由能够提供这种自增 id 功能的工具来支持。获取到 worker_id 之后,就把这个 worker_id 直接持久化到本地,以避免每次上线时都需要获取新的 worker_id 。让单实例的 worker_id 可以始终保持不变。

6、一个可行的方案是,预先在一个数据库中设置好,服务器IP对应的datacenter_id和worker_id,在启动的时候调一次API拿到自己的参数。

7、考虑到集群中即使有单个 id 生成服务的实例挂了,也就是损失一段时间的一部分id,其他服务依然可以生成自己的ID。

8、Node参数要节省着使用,如果有幸你的公司活过了69年,那么剩下的Node值就派上用场了。

使用示例

package main

import (
	"fmt"

	"github.com/bwmarrin/snowflake"
)

func main() {
	node, err := snowflake.NewNode(1)
	if err != nil {
		fmt.Println(err)
		return
	}
	id := node.Generate()
	// Print out the ID in a few different ways.
	fmt.Printf("Bit    ID: %b\n", id)          //1010111110111111110111011110101111101000000000001000000000000
	fmt.Printf("Int64  ID: %d\n", id)          //1583010585308565504
	fmt.Printf("String ID: %s\n", id)          //1583010585308565504
	fmt.Printf("Base2  ID: %s\n", id.Base2())  //1010111110111111110111011110101111101000000000001000000000000
	fmt.Printf("Base64 ID: %s\n", id.Base64()) //MTU4MzAxMDU4NTMwODU2NTUwNA==

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

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

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

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

不够64位的高位补0,因此没打印出来。

源码部分

设置初始时间,默认值为1288834974657,接收一个毫秒的时间戳,timestamp 部分存储的就是增量的毫秒时间

// Epoch is set to the twitter snowflake epoch of Nov 04 2010 01:42:54 UTC in milliseconds
// You may customize this to set a different epoch for your application.
Epoch int64 = 1288834974657

实例化节点

// NewNode returns a new snowflake node that can be used to generate snowflake
// IDs
func NewNode(node int64) (*Node, error) {

	// re-calc in case custom NodeBits or StepBits were set
	// DEPRECATED: the below block will be removed in a future release.
	mu.Lock()
	nodeMax = -1 ^ (-1 << NodeBits)
	nodeMask = nodeMax << StepBits
	stepMask = -1 ^ (-1 << StepBits)
	timeShift = NodeBits + StepBits
	nodeShift = StepBits
	mu.Unlock()

	n := Node{}
	n.node = node
	n.nodeMax = -1 ^ (-1 << NodeBits)
	n.nodeMask = n.nodeMax << StepBits
	n.stepMask = -1 ^ (-1 << StepBits)
	n.timeShift = NodeBits + StepBits
	n.nodeShift = StepBits

	if n.node < 0 || n.node > n.nodeMax {
		return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(n.nodeMax, 10))
	}

	var curTime = time.Now()
	// add time.Duration to curTime to make sure we use the monotonic clock if available
	n.epoch = curTime.Add(time.Unix(Epoch/1000, (Epoch%1000)*1000000).Sub(curTime))

	return &n, nil
}

重点看生成ID的方法

// Generate creates and returns a unique snowflake ID
// To help guarantee uniqueness
// - Make sure your system is keeping accurate system time
// - Make sure you never have multiple nodes running with the same node ID
func (n *Node) Generate() ID {
	// 互斥锁
	n.mu.Lock()

	// 增量的时间
	now := time.Since(n.epoch).Nanoseconds() / 1000000

	// 同一个毫秒的时候,sequence_id 自增
	if now == n.time {
		// sequence_id自增,如果达到了最大值,就会回到0
		n.step = (n.step + 1) & n.stepMask

		if n.step == 0 {
			for now <= n.time {
				now = time.Since(n.epoch).Nanoseconds() / 1000000
			}
		}
	} else {
		n.step = 0
	}

	n.time = now

	// 拼接
	r := ID((now)<<n.timeShift |
		(n.node << n.nodeShift) |
		(n.step),
	)

	n.mu.Unlock()
	return r
}
 类似资料: