自学内容网 自学内容网

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

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。还有集群协调机制。


原文地址:https://blog.csdn.net/qq_44794715/article/details/144079744

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!