这是我参与「第三届青训营 -后端场」笔记创作活动的第3篇笔记
一、雪花算法是什么?
雪花算法是 Twitter 公司发明的一种算法,主要目的是解决在分布式环境下,ID 怎样生成的问题。
二、在分布式中雪花算法的实现
分布式 ID 生成规则硬性要求:全局唯一,不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
按照这个要求我们可以构造出雪花 ID 的格式:
- 最高位是符号位,始终为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位0 | 41位时间戳 | 12位0 | 10位0 |
| 机器 id | 1位0 | 41位0 | 12位机器 id | 10位0 |
| 序列号(int64) | 1位0 | 41位0 | 12位0 | 10位序列号 |
(序列号只能取0~4095,且处于64位的低10位,不需要做任何的处理)
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位序列号,最后通过位运算拼接在一起。