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 算法有明显的缺点:

  1. UUID 太长了。16 个字节(128 位),通常以 36 长度的字符串表示,很多场景不适用。
  2. 非纯数字。UUID 中会出现 ABCDEF 这些十六进制的字母,因此,在数据库和代码中,自然就不能存储在整型字段或变量中。因此,在数据库中以它作为主键,建立索引的代价比较大,性能有影响。
  3. 不安全。UUID 中会包含网卡的 MAC 地址。

从 UUID 的缺点我们也能推导出一个『好』ID 的标准应该有哪些:

  1. 最好是由纯数字组成。
  2. 越短越好,最好能存进整型变量和数据库的整型字段中。
  3. 信息安全。另外,『ID 连续』并非好事情。
  4. 在不连续的情况下,最好是递增的。即便不是严格递增,至少也应该是趋势递增。

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
    Untitled

posted @ 2023-11-19 14:00  ShengOasis  阅读(622)  评论(0)    收藏  举报