项目学习之Golang 版本简易雪花算法|青训营笔记

366 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第3篇笔记

一、雪花算法是什么?

雪花算法是 Twitter 公司发明的一种算法,主要目的是解决在分布式环境下,ID 怎样生成的问题。

二、在分布式中雪花算法的实现

分布式 ID 生成规则硬性要求:全局唯一,不能出现重复的ID号,既然是唯一标识,这是最基本的要求。 按照这个要求我们可以构造出雪花 ID 的格式: 雪花.png

  • 最高位是符号位,始终为0,不可用。
  • 41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
  • 10位的机器标识,10位的长度最多支持部署1024个节点。
  • 12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。

三、生成雪花 ID 的具体步骤

1、生成 41bit - 时间戳

  • 通过time.Now().UnixMilli()获取 Unix 的毫秒时间戳。
  • 取时间戳和 0x1FFFFFFFFFF 的并结果。(0x代表16进制,一个F是4个二进制的1,0x1FFFFFFFFFF 是41个二进制1)
  • 因为你的时间戳是64位,现在填充了低41位,高23位会变成0(64位时间戳与41个1的并结果),你现在的 id 是23个0 + 41位时间戳(23*0 - 41位时间戳)
  • 然后向左边移动22位,那么你的 id 会变成 (1*0 - 41位时间戳 - 22*0)
  • 左边移动的原因:留10位工作机器 id 和12位序列号(留出低22位)

2、生成 10bit - 工作机器id

  • 工作机器id可以选择取0~1023(方便分辨)
  • 比如你取1,那你的机器 id 就是 63*0 - 1
  • 左移12位,变成42*0 - 10位工作机器 id - 12*0 完成上面两步,你的现在有的 id 有
你构造的第64位63 ~ 23位22 ~ 11位10 ~ 1位
时间戳1位041位时间戳12位010位0
机器 id1位041位012位机器 id10位0
序列号(int64)1位041位012位010位序列号

(序列号只能取0~4095,且处于64位的低10位,不需要做任何的处理)

微信图片_20220602090833.jpg

3、构造你的雪花ID

把你自己构造的时间戳、机器 ID、序列号、使用或操作,那么很明显你构造生成的 ID 格式为

1*0 - 41位时间戳 - 12位机器 id - 10位序列号

四、SnowflakeId 可供参考

import "time"

var (
	machineID     int64 // 机器 id 占10位, 十进制范围是 [ 0, 1023 ]
	serialNumber  int64 // 序列号占 12 位,十进制范围是 [ 0, 4095 ]
	lastTimeStamp int64 // 上次的时间戳(毫秒级), 1秒=1000毫秒, 1毫秒=1000微秒,1微秒=1000纳秒
)

func init() {
        // 程序初始化,初始化更新时间
	lastTimeStamp = time.Now().UnixMilli()
}

func SetMachineId(mid int64) {
	// 把机器 id 左移 12 位,让出 12 位空间给序列号使用
	machineID = mid << 12
}

func GetSnowflakeId() int64 {
        // 读当前时间
	curTimeStamp := time.Now().UnixMilli()
	// 如果是同一毫秒(与上一个请求时间对比)
	if curTimeStamp == lastTimeStamp {
		serialNumber++
		// 序列号占 12 位,十进制范围是 [ 0, 4095 ]
		if serialNumber > 4095 {
			// 休眠一毫秒
			time.Sleep(time.Millisecond)
                        // 再次读当前时间
			curTimeStamp = time.Now().UnixMilli()
                        // 以当前时间作为下一个请求的对比时间
			lastTimeStamp = curTimeStamp
			serialNumber = 0
		}

		// 取 41 位的二进制数 0x1FFFFFFFFFF 1111111111 1111111111 1111111111 1111111111 1( 这里共 41 个 1 )和时间戳进行并操作
		// 并结果( 右数 )第 42 位必然是 0,  低 41 位也就是时间戳的低 41 位
		rightBinValue := curTimeStamp & 0x1FFFFFFFFFF
		// 机器 id 占用10位空间,序列号占用12位空间,所以左移 22 位; 经过上面的并操作,左移后的第 1 位,必然是 0
		rightBinValue <<= 22
                // 生成你构造雪花ID
		id := rightBinValue | machineID | serialNumber
		return id
	}
	if curTimeStamp > lastTimeStamp {
		serialNumber = 0
		lastTimeStamp = curTimeStamp
		// 取 41 位的二进制数 0x1FFFFFFFFFF 1111111111 1111111111 1111111111 1111111111 1( 这里共 41 个 1 )和时间戳进行并操作
		// 并结果( 右数 )第 42 位必然是 0,  低 41 位也就是时间戳的低 41 位
		rightBinValue := curTimeStamp & 0x1FFFFFFFFFF
		// 机器 id 占用10位空间,序列号占用12位空间,所以左移 22 位; 经过上面的并操作,左移后的第 1 位,必然是 0
		rightBinValue <<= 22
                // 生成你构造雪花ID
		id := rightBinValue | machineID | serialNumber
		return id
	}
	if curTimeStamp < lastTimeStamp {
		return 0
	}
	return 0
}

func main() {
        // 设置机器码为3,默认0.
	SetMachineId(3)
        // 生成 100 个雪花id
	for i := 0; i < 100; i++ {
		fmt.Printf("%v\n", GetSnowflakeId())
	}
}

结果:

生成了100个不同的ID,使用二进制方法打印出来可以发现规律。

五、总结

一句话概括雪花ID生成的格式:第一位固定为0 + 41位时间戳 + 10位为机器码(分布式机器 ID)+ 12位序列号,最后通过位运算拼接在一起。