返回归档
🌐工程与设计

全局唯一ID生成方案

文章目录

uuid

是什么

uuid的全称是 universally unique identifier ,即通用唯一识别号,它由16字节(128byte)组成,可以用于唯一的标识信息。

格式为 8-4-4-4-12的16进制格式

123e4567-e89b-12d3-a456-426614174000

使用 - 分隔

不同的UUID版本

1. UUID v1 (基于时间)

UUID v1 是基于时间戳生成的,并使用了机器的MAC地址来保证唯一性。其结构如下:

  • 前 60 位表示时间戳,单位是100纳秒(从1582年10月15日以来的时间)。
  • 13 位表示时钟序列,用来防止同一时间生成多个UUID时产生冲突。
  • 剩下的 48 位为节点标识符,通常为设备的 MAC 地址。

由于包含MAC地址,因此UUID v1可能会暴露硬件信息。

2. UUID v2 (DCE 安全)

UUID v2 类似于 v1,但根据用户的本地标识(如UID或GID)和时间戳生成。这种UUID较少使用。

3. UUID v3 (基于命名空间的散列)

UUID v3 使用命名空间和用户指定的名称生成。它基于MD5哈希算法,即根据命名空间和名称的MD5哈希值生成唯一标识符。此版本可以确保同一名称在同一命名空间下生成的UUID始终相同。

命名空间有四种标准类型:

  • DNS:基于域名系统
  • URL:基于URL
  • OID:基于对象标识符
  • X.500 DN:基于X.500目录名

4. UUID v4 (随机生成)

UUID v4 是目前最常用的版本,使用随机数生成器生成UUID。其128位中的122位是随机生成的,剩余6位用于版本信息和其他元数据。这使得v4版本的UUID具有较高的随机性和唯一性。

生成UUID v4的关键是使用高质量的随机数生成器,保证足够的随机性。

5. UUID v5 (基于命名空间的散列)

UUID v5 类似于 v3,但它使用的是SHA-1哈希算法而不是MD5,因此具有更高的安全性。和v3一样,v5生成的UUID对于相同的命名空间和名称也会一致。

使用场景

  • 文件、资源的唯一标识
  • 会话标识符
  • 分布式系统中的标识符:微服务架构中生成全局唯一的请求 ID、事务 ID、任务 ID 等。
  • 消息队列中的信息ID

雪花算法

是什么

0 - 0000000000 0000000000 0000000000 0000000000 0 - 0000000000 - 000000000000

雪花算法生成的ID通常是一个64位的整数,它的位结构如下:

  • 第一段1位未使用,永远固定为0,因为ID为正数
  • 第二段41位,为毫秒级时间(41位的长度可以使用69年)
  • 第三段10位,为workID(10位的长度最多能部署1024个节点)
  • 第四段为12位,表示每毫秒内的计数(12位的计数顺序号支持每个节点每毫秒支持产生4096个ID序号)

按照 1024个节点计算:每毫秒可以生成的ID序列号有 1024*4096=4194304,足以满足绝大多数业务场景

  1. 获取当前时间戳:计算当前时间与纪元时间的差值,以毫秒为单位。
  2. 生成ID
    • 将时间戳左移(41位),数据中心ID左移(5位),机器ID左移(5位),并将序列号添加到最低位。
    • 组合所有部分,生成一个64位的整数ID。
((当前时间 - 服务时间) << timestampLeftShift) 
        | (机器ID << workerIdShift) 
        | sequence;

雪花算法存在的问题

SnowFlake强依赖 时间戳,所以时间的变动会造成SnowFlake的算法产生错误;使用时需要注意时钟回拨的现象

具体实现
package main

import (
	"errors"
	"fmt"
	"sync"
	"time"
)

const (
	workerIDBits     = 10                      // 节点ID长度
	sequenceBits     = 12                      // 序列号长度
	workerIDShift    = sequenceBits            // 节点ID左移位数
	timestampShift   = sequenceBits + workerIDBits // 时间戳左移位数
	sequenceMask     = -1 ^ (-1 << sequenceBits)   // 序列号掩码
	epoch            = int64(1577836800000)    // 起始时间戳(2020-01-01)
	maxWorkerID      = -1 ^ (-1 << workerIDBits)   // 最大节点ID
)

type Snowflake struct {
	mu        sync.Mutex
	timestamp int64
	workerID  int64
	sequence  int64
}

func NewSnowflake(workerID int64) (*Snowflake, error) {
	if workerID < 0 || workerID > maxWorkerID {
		return nil, errors.New("worker ID out of range")
	}
	return &Snowflake{
		timestamp: 0,
		workerID:  workerID,
		sequence:  0,
	}, nil
}

func (s *Snowflake) tilNextMillis(lastTimestamp int64) int64 {
	timestamp := time.Now().UnixNano() / int64(time.Millisecond)
	for timestamp <= lastTimestamp {
		timestamp = time.Now().UnixNano() / int64(time.Millisecond)
	}
	return timestamp
}

func (s *Snowflake) NextID() (int64, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	timestamp := time.Now().UnixNano() / int64(time.Millisecond)
	if timestamp < s.timestamp {
		return 0, fmt.Errorf("clock moved backwards. Refusing to generate id for %d milliseconds", s.timestamp-timestamp)
	}

	if timestamp == s.timestamp {
		s.sequence = (s.sequence + 1) & sequenceMask
		if s.sequence == 0 {
			timestamp = s.tilNextMillis(s.timestamp)
		}
	} else {
		s.sequence = 0
	}

	s.timestamp = timestamp

	id := ((timestamp - epoch) << timestampShift) |
		(s.workerID << workerIDShift) |
		s.sequence

	return id, nil
}

func main() {
	sf, err := NewSnowflake(1)
	if err != nil {
		fmt.Printf("Error initializing Snowflake: %v\n", err)
		return
	}

	for i := 0; i < 10; i++ {
		id, err := sf.NextID()
		if err != nil {
			fmt.Printf("Error generating ID: %v\n", err)
			continue
		}
		fmt.Println(id)
	}
}