sonyflake算法
简介
是一种分布式ID生成器,它可以在不依赖中心节点的情况下,生成唯一的64位整数。
sonyflake算法的原理
是将64位分成三部分:时间戳、机器ID和序列号。
+-----------------------------------------------------------------------------+
| 标识位 | 39位保存时间戳 | 8位序列号 | 16位保存机器号 |
+-----------------------------------------------------------------------------+
| 1 Bit Unused | 39 Bit Timestamp | 8 Bit Sequence ID | 16 Bit Machine ID |
+-----------------------------------------------------------------------------+
结合源码讲解原理
TODO!!!
使用go的多线程来调用sonyflake算法生成唯一ID
package main
import (
"github.com/sony/sonyflake"
"sync"
)
var wg sync.WaitGroup
func main() {
var st sonyflake.Settings //创建setting实例
sf, err := sonyflake.New(st) //创建snoyflake实例
if err != nil {
return
}
for i := 0; i < 10; i++ {
wg.Add(1) // 启动一个goroutine就登记+1
go generateID(sf) //使用goroutine来并发执行
}
wg.Wait() // 等待所有登记的goroutine都结束(否则会因为main的主协程退出了,其他协程也推出导致不会产生任何id,因此要等全部协程全部执行完)
}
func generateID(sf *sonyflake.Sonyflake) (uid uint64) {
defer wg.Done() // goroutine结束就登记-1
uid, err := sf.NextID() //根据当前sf的情况产生id
if err != nil {
return
}
return uid
}
通过对生成的id进行decompose来探讨具体生产过程
对上文的代码进行了修改,移除了函数调用,而直接使用匿名函数
package main
import (
"fmt"
"github.com/sony/sonyflake"
"sync"
)
var wg sync.WaitGroup
func main() {
var st sonyflake.Settings //创建setting实例
sf, err := sonyflake.New(st) //创建snoyflake实例
if err != nil {
return
}
for i := 0; i < 10000; i++ {
wg.Add(1) // 启动一个goroutine就登记+1
//使用goroutine来进行并发,并使用**匿名函数**来承载“生成id”和“分解id”的功能
go func(sf *sonyflake.Sonyflake) {
defer wg.Done() // goroutine结束就登记-1
uid, err := sf.NextID() //根据当前sf的情况产生id
if err != nil {
return
}
idMap := sonyflake.Decompose(uid) //分解生成的id,查看其组成部分
fmt.Println("生成id:", uid, "分解id:", idMap)
}(sf)
}
wg.Wait() // 等待所有登记的goroutine都结束(否则会因为main的主协程退出了,其他协程也推出导致不会产生任何id,因此要等全部协程全部执行完)
}
运行的输出:
生成id: 487952909680902543 分解id: map[id:487952909680902543 machine-id:399 msb:0 sequence:166 time:29084259848]
生成id: 487952909680968079 分解id: map[id:487952909680968079 machine-id:399 msb:0 sequence:167 time:29084259848]
生成id: 487952909681033615 分解id: map[id:487952909681033615 machine-id:399 msb:0 sequence:168 time:29084259848]
生成id: 487952909681099151 分解id: map[id:487952909681099151 machine-id:399 msb:0 sequence:169 time:29084259848]
......
生成id: 487952909837533583 分解id: map[id:487952909837533583 machine-id:399 msb:0 sequence:252 time:29084259857]
生成id: 487952909837599119 分解id: map[id:487952909837599119 machine-id:399 msb:0 sequence:253 time:29084259857]
生成id: 487952909837664655 分解id: map[id:487952909837664655 machine-id:399 msb:0 sequence:254 time:29084259857]
生成id: 487952909837730191 分解id: map[id:487952909837730191 machine-id:399 msb:0 sequence:255 time:29084259857]
生成id: 487952909837795727 分解id: map[id:487952909837795727 machine-id:399 msb:0 sequence:0 time:29084259858]
生成id: 487952909837861263 分解id: map[id:487952909837861263 machine-id:399 msb:0 sequence:1 time:29084259858]
生成id: 487952909837926799 分解id: map[id:487952909837926799 machine-id:399 msb:0 sequence:2 time:29084259858]
......
生成id: 487952910008648079 分解id: map[id:487952910008648079 machine-id:399 msb:0 sequence:47 time:29084259868]
生成id: 487952910008713615 分解id: map[id:487952910008713615 machine-id:399 msb:0 sequence:48 time:29084259868]
生成id: 487952910008779151 分解id: map[id:487952910008779151 machine-id:399 msb:0 sequence:49 time:29084259868]
生成id: 487952910008844687 分解id: map[id:487952910008844687 machine-id:399 msb:0 sequence:50 time:29084259868]
生成id: 487952910008910223 分解id: map[id:487952910008910223 machine-id:399 msb:0 sequence:51 time:29084259868]
生成id: 487952910008975759 分解id: map[id:487952910008975759 machine-id:399 msb:0 sequence:52 time:29084259868]
......
可以看到,虽然并发会使得程序以无法预知的方向前进,但是最终生成的id却是整体递增的
此外,仔细观察map中time这一项,会发现很多time都是相同的,这是因为sonyflake生成id的单位时间是10ms,10ms以内的都属于同一个time,那对于10ms以内的id生成,要怎么做呢
sonyflake的解决办法是增加sequence值,如果time是一个单位时间的开始(也就是10ms区间的开始),那么sequence此时值为0,这是这个单位时间内生成的第一个id1
如果此时到了第2ms,且此时需要生成一个id2,由于此时还在10ms这个时间段内,因此time是和id1相同的,此时将id2的sequence加1,其他保持不变,就得到了比id1大的id2(这是因为sequence的位置比time的位置高,即sequence在time的左边,也就是说在二进制中,sequence的权重比time的大,因此即使time相同,只要sequence比id1大,那么id2就id1大)
另外,从中间那一段可以看到sequence的最大值是255,也就是uint64(1<<8 -1) ,对于sequence超过255的情况,那么就把sequence置为0并且把time加1
UUID算法
为了得到一个全局唯一 ID,很自然地就会想到 UUID 算法。但是,UUID 算法有明显的缺点:
- UUID 太长了。16 个字节(128 位),通常以 36 长度的字符串表示,很多场景不适用。
- 非纯数字。UUID 中会出现 ABCDEF 这些十六进制的字母,因此,在数据库和代码中,自然就不能存储在整型字段或变量中。因此,在数据库中以它作为主键,建立索引的代价比较大,性能有影响。
- 不安全。UUID 中会包含网卡的 MAC 地址。
从 UUID 的缺点我们也能推导出一个『好』ID 的标准应该有哪些:
- 最好是由纯数字组成。
- 越短越好,最好能存进整型变量和数据库的整型字段中。
- 信息安全。另外,『ID 连续』并非好事情。
- 在不连续的情况下,最好是递增的。即便不是严格递增,至少也应该是趋势递增。
sonyflake缺陷
sonyflake的结构是8bit 做为序列号,每 10 毫秒最大生成 256 个,1 秒最多生成 25600 个,比原生的 Snowflake 少好多。这意味着,Sonyflake 的使用场景并发量并没有 Snowfake 那么大。
为什么说是“低16位ip”
在snoyflake主程序中,有用到这样一个函数,函数名为lower16BitPrivateIP ,最后return的是四位ip中的最后两位
func lower16BitPrivateIP(interfaceAddrs types.InterfaceAddrs) (uint16, error) {
ip, err := privateIPv4(interfaceAddrs)
if err != nil {
return 0, err
}
return uint16(ip[2])<<8 + uint16(ip[3]), nil
}
我第一反应这不是高16位ip吗,怎么说是低16位ip,但是查了资料发现:我们通常从左到右阅读和解析数字和文字。在ip中(包括二进制中),"高"和"低"是指数值的权重,而不是它们在字符串中的位置。对于最右边的数字来说,权重最低,越往左边权重越高
此外,这里的lower16BitPrivateIP 获取过程是这样的:
-
假设
ip[2] = 153 = 1001 1001 并且 ip[3] = 130 = 1000 0010 -
那么把
ip[2]左移8位得到的是1001 1001 0000 0000 (也就是在原有的基础上补0) -
此时把这个左移8位的
ip[2]与ip[3]相加,二进制的加法是按位相加,所以ip[2]左移8位的补0的地方,与ip[3]相加刚好完全等于ip[3] -
所以
ip[2]<<8 + ip[3] = 1001 1001 1000 0010由于
ip[2]原本是uint8(如下图),左移后变为16位,因此转型成uint16


浙公网安备 33010602011771号