【分布式系统】唯一性ID的实现

发布于:2024-11-29 ⋅ 阅读:(40) ⋅ 点赞:(0)

1、UUID(通用唯一标识符)

1、UUID本身
一种用于标识信息的标准化方法。一个128位的数字,常表示为32个十六进制数字,以连字符分隔成五组:8-4-4-4-12。

版本: UUID有不同的版本,最常见的是基于时间戳和随机数生成的版本1和版本4。
唯一性: 由于UUID的长度和生成机制,可以保证在大多数情况下生成的UUID是唯一的。
应用: 在各种系统中广泛应用,用于唯一标识实体、会话、交易等。

2、设计方法:
时间戳: 将当前时间戳作为UUID的一部分,以确保在同一时刻生成的UUID是唯一的。

节点标识: 在分布式系统中,将每个节点的唯一标识符(如机器ID)纳入UUID的生成过程,以防止在不同节点上生成相同的UUID。

随机数生成器: 将随机数作为UUID的一部分,以增加唯一性,但在分布式系统中要确保随机数生成器是足够随机的。

考虑时钟回拨问题: 如果使用时间戳作为UUID的一部分,需要考虑时钟回拨可能导致的重复UUID问题。可以采用一些机制来解决时钟回拨带来的潜在问题,比如使用递增序列号。

一致性哈希算法: 基于节点信息和数据内容计算哈希值,然后将哈希值转换为UUID。这样可以确保相同的数据在不同节点上生成的UUID是一致的。

时钟回拨问题
指在分布式系统中,当某个节点的系统时间发生回拨(即向过去跳跃)时可能导致的一系列问题。
引起的原因:手动调整时间;网络时间协议(NTP)校准;系统重启或故障;虚拟机迁移;
解决方向:使用逻辑时钟;增加容错机制;使用稳定的时钟;时间校正算法;设计时避免依赖绝对时间;

package main

import (
    "fmt"
    "github.com/google/uuid"
)

func main() {
    // 生成一个新的 UUID
    newUUID := uuid.New()

    fmt.Printf("Generated UUID: %s\n", newUUID)
}

2、数据库序列(自增ID)

简单,工作方式:基于中央数据库的序列生成器,如自增ID,每次请求时递增序列值。顺序性:保证了生成ID的顺序性和唯一性。

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/mattn/go-sqlite3"
)

func main() {
	// 连接到 SQLite 数据库
	db, err := sql.Open("sqlite3", "test.db")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer db.Close()

	// 创建一个包含自增ID的表
	_, err = db.Exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("Table created successfully")
}
//连接到 SQLite 数据库,并创建了一个名为 users 的表,该表包含一个自增的整数类型的ID列和一个文本类型的 name 列。
//不同的数据库(如 MySQL、PostgreSQL 等)可能有不同的语法和方式来实现自动递增的ID列

3、雪花算法(Twitter Snowflake)

Twitter开发的一种生成64位ID的服务,基于时间戳、机器ID和序列号。

时间戳(41位): 用于表示ID生成的时间戳,通常精确到毫秒级。
机器ID(10位): 标识生成ID的机器的唯一标识,通常使用数据中心ID与机器ID的组合。
序列号(12位): 在同一毫秒内,通过累加的方式生成序列号,确保在同一节点上生成的ID是唯一的。

雪花算法的优点包括高性能、高可用性和ID趋势递增。在实际应用中,可根据需求调整时间戳的位数、机器ID的位数和序列号的位数来适应不同的场景。
一般用于替代传统自增ID的方式。

package main

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

const (
	epoch time.Duration = 1609459200000 // 2021-01-01 的时间戳,用于生成时间戳部分
	workerIDBits       = 5               // 机器 ID 的位数
	sequenceBits       = 12              // 序列号的位数
	maxWorkerID        = -1 ^ (-1 << workerIDBits)
	maxSequence        = -1 ^ (-1 << sequenceBits)
)

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

func NewSnowflake(workerID uint) *Snowflake {
	if workerID > maxWorkerID {
		panic("worker ID 超出范围")
	}
	return &Snowflake{
		lastTime: 0,
		workerID: workerID,
		sequence: 0,
	}
}

func (s *Snowflake) GenerateID() uint64 {
	s.mu.Lock()
	defer s.mu.Unlock()

	currentTime := time.Now().UnixNano() / 1e6 // 获取当前时间的毫秒数

	if s.lastTime == currentTime {
		s.sequence = (s.sequence + 1) & maxSequence
		if s.sequence == 0 {
			for currentTime <= s.lastTime {
				currentTime = time.Now().UnixNano() / 1e6
			}
		}
	} else {
		s.sequence = 0
	}

	s.lastTime = currentTime

	id := uint64((currentTime-epoch)<<22 | int64(s.workerID)<<17 | int64(s.sequence))
	return id
}

func main() {
	sf := NewSnowflake(1) // 设定一个机器 ID
	id := sf.GenerateID()
	fmt.Println(id)
}

4、使用Redis实现分布式ID生成

Redis是一个高性能的键值数据库,它可以用于生成分布式唯一标识符。
不同Redis实例通过配置不同的起始步长来区分。
这个的实现原理利用了Redis的原子操作。

package main

import (
	"fmt"
	"github.com/go-redis/redis/v8"
	"log"
	"time"
)

var redisClient *redis.Client

func init() {
	redisClient = redis.NewClient(&redis.Options{
		Addr:     "localhost:6379", // Redis 服务器地址
		Password: "",               // Redis 密码,如果有的话
		DB:       0,                // 选择使用的数据库
	})
}

func generateID(key string) (int64, error) {
	val, err := redisClient.Incr(key).Result()
	if err != nil {
		return 0, err
	}
	return val, nil
}

func main() {
	key := "distributed_id_generator" // Redis 中的键名

	// 生成 5 个分布式 ID
	for i := 0; i < 5; i++ {
		id, err := generateID(key)
		if err != nil {
			log.Fatalf("Failed to generate ID: %v", err)
		}
		fmt.Printf("Generated ID: %d\n", id)
		time.Sleep(time.Millisecond) // 可选的延迟,以避免生成相同的 ID
	}
}

package main

import (
	"fmt"
	"github.com/go-redis/redis/v8"
	"log"
	"time"
)

var redisClients map[string]*redis.Client

func init() {
	redisClients = make(map[string]*redis.Client)
	redisClients["instance1"] = redis.NewClient(&redis.Options{
		Addr:     "localhost:6379", // Redis 实例1的地址
		Password: "",               // Redis 密码,如果有的话
		DB:       0,                // 选择使用的数据库
	})
	redisClients["instance2"] = redis.NewClient(&redis.Options{
		Addr:     "localhost:6380", // Redis 实例2的地址
		Password: "",               // Redis 密码,如果有的话
		DB:       0,                // 选择使用的数据库
	})
}

func generateID(key string, start int64) (int64, error) {
	val, err := redisClients[key].IncrBy(key, start).Result()
	if err != nil {
		return 0, err
	}
	return val, nil
}

func main() {
	key1 := "distributed_id_generator_instance1" // Redis 实例1中的键名
	key2 := "distributed_id_generator_instance2" // Redis 实例2中的键名

	// 生成 5 个分布式 ID
	for i := 0; i < 5; i++ {
		id1, err := generateID(key1, 1000) // 指定实例1的起始步长为1000
		if err != nil {
			log.Fatalf("Failed to generate ID: %v", err)
		}
		fmt.Printf("Instance 1 - Generated ID: %d\n", id1)

		id2, err := generateID(key2, 2000) // 指定实例2的起始步长为2000
		if err != nil {
			log.Fatalf("Failed to generate ID: %v", err)
		}
		fmt.Printf("Instance 2 - Generated ID: %d\n", id2)

		time.Sleep(time.Millisecond) // 可选的延迟,以避免生成相同的 ID
	}
}

5、使用数据库分段(Database Segment)

6、分布式键生成服务(如Zookeeper、etcd)

分布式协调服务在集群中生成唯一ID。
也是利用这些服务提供的分布式锁和原子性操作来生成唯一的ID。还有集群协调机制。


网站公告

今日签到

点亮在社区的每一天
去签到