76.Go分布式ID总览

文章目录

简介

在分布式系统中,生成唯一的ID是一个核心问题,特别是在需要确保数据完整性和避免冲突的场景中,比如作为链路追踪的trace_id,作为幂等性的主键id, 分布式锁中为了避免释放的是别人的锁时,本地保存的唯一id等。以下是对一些分布式唯一ID生成方法的详细阐述,包括它们的工作原理、优缺点,以及对网络依赖性的考量

一:UUID

实现原理

  • 工作方式:UUID是通过一系列算法生成的128位数字,通常基于时间戳、计算机硬件标识符、随机数等元素。

  • 全局唯一性:算法设计确保了即使在分布式系统中也能生成全局唯一的ID

优缺点

  • 优点:实现简单,无需网络交互,保证了ID的全球唯一性。

  • 缺点:通常不能保证顺序性,ID较长,可能导致存储和索引效率低下。

  • 网络依赖性:无网络依赖。

注意:工作中基本不使用uuid,这里是为了介绍分布式ID的完整性,以及UUID大家都熟知,所以一起介绍一下

uuid有两种包:

  • github.com/google/uuid,仅支持V1V4版本。

  • github.com/gofrs/uuid ,支持全部五个版本。

下面简单说下五种版本的区别:

Version 1,基于mac地址、时间戳。

Version 2,based on timestamp,MAC address and POSIX UID/GID (DCE 1.1)

Version 3,Hash获取入参并对结果进行MD5。

Version 4,纯随机数。

Version 5,based on SHA-1 hashing of a named value。

特点

  • 5个版本可供选择。

  • 定长36字节,偏长。

  • 无序。

go 复制代码
package mian

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

func main() {
    // Version 1:时间+Mac地址
    id, err := uuid.NewV1()
    if err != nil {
        fmt.Printf("uuid NewUUID err:%+v", err)
    }
    // id: f0629b9a-0cee-11ed-8d44-784f435f60a4 length: 36
    fmt.Println("id:", id.String(), "length:", len(id.String()))

    // Version 4:是纯随机数,error会在内部报panic
    id, err = uuid.NewV4()
    if err != nil {
        fmt.Printf("uuid NewUUID err:%+v", err)
    }
    // id: 3b4d1268-9150-447c-a0b7-bbf8c271f6a7 length: 36
    fmt.Println("id:", id.String(), "length:", len(id.String()))
}

二、雪花算法

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

实现原理

  • 工作方式:结合时间戳、工作机器的ID和序列号来生成64位的ID。时间戳保证了ID的唯一性和顺序性,工作机器ID保证了在多机环境下的唯一性。

  • 时间戳:确保ID按时间顺序增长。

优缺点

  • 优点:ID有时间顺序,长度适中,生成速度快。

  • 缺点:对系统时钟有依赖,时钟回拨会导致ID冲突。(时钟回拨:服务器上的时间突然倒退回之前的时间。可能是人为的调整时间;也可能是服务器之间的时间校对。)

  • 网络依赖性:通常无需网络交互,除非在多机器环境中同步机器ID

由于雪花算法工作中还是可能用到的,所以介绍更详细一些。

Snowflake的核心思想是:使用41bit作为毫秒数,10bit作为机器的ID5bit是数据中心(DC,机房)),5bit的机器ID(也可能是容器id)),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是01。这样可以确保每个ID都是唯一的。

go 复制代码
package main

import(
    "fmt"
    "github.com/bwmarrin/snowflake"
)

func main() {
// 参数为机器id
  node, err := snowflake.NewNode(1)
    if err != nil {
      fmt.Println(err)
      return
    }

    id := node.Generate().String()    
    // id: 1552614118060462080 length: 19
    fmt.Println("id:", id, "length:", len(id))
}

三:Leaf-snowflake

时钟回拨

服务器上的时间突然倒退回之前的时间。可能是人为的调整时间;也可能是服务器之间的时间校对。

实现方案

Zookeeper顺序增、全局唯一的节点版本号,替换了原有的机器地址。解决了时钟回拨的问题。强依赖ZooKeeper、大流量下可能存在网络瓶颈。下图的方案在Leaf-snowflake 中通过缓存一个ZooKeeper文件夹,提高可用性。运行时,时差小于5ms会等待时差两倍时间,如果时差大于5ms报警并停止启动。

四:数据库自增ID

实现原理

  • 工作方式:基于中央数据库的序列生成器,如自增主键ID,每次请求时递增序列值。

  • 顺序性:保证了生成ID的顺序性和唯一性。

优缺点

  • 优点:简单可靠,保证顺序性。

  • 缺点:

    • 可能成为系统的单点故障,对数据库有较高的依赖。
    • 由于有序递增,易暴露业务量。受到数据库性能限制,对高并发场景不友好。
    • bigint最大是2^64-1,但是数据库单表肯定放不了这么多,那么就涉及到分表。
    • 如果业务量真的太大了,主键的自增id涨到头了,会发生什么?报错:主键冲突。
    • 网络依赖性:高度依赖网络,所有ID生成请求都需要访问中央数据库。

正是由于这众多缺点,所以工作中是不用它的,不过可以作为一种思路。

五:使用Redis实现分布式ID生成

Redis是一个高性能的键值数据库,它可以用于生成分布式唯一标识符。

实现原理

  • 利用Redis的原子操作:Redis提供了原子性的INCRINCRBY命令,可用于生成唯一的递增数值。这些数值可以作为唯一ID

  • 分布式环境中的应用:在分布式环境中,可以部署多个Redis实例。每个实例可以独立生成ID,或者通过配置不同的起始值和步长来确保ID的全局唯一性。

  • 高性能和可靠性:Redis的高性能确保了即使在高负载下也能快速生成ID,同时Redis的持久化和复制特性提高了系统的可靠性。

优缺点分析

  • 优点:快速、简单且易于扩展;支持高并发环境。

  • 缺点:依赖于外部服务(Redis),需要管理和维护额外的基础设施。

  • 网络依赖性:高度依赖网络。

六:使用数据库分段(Leaf-segment)

这种方法涉及到使用数据库来生成和管理ID段,以实现分布式ID的生成。

实现原理

  • ID段的分配:在数据库中预设一个起始ID和步长,每个应用实例或服务节点从数据库中获取一个ID段,然后在本地生成ID,直到该段用完再从数据库获取新的段。

  • 减少数据库交互:每个节点在消耗完一个ID段之前不需要与数据库交互,这减少了数据库的负载,并提高了ID生成的效率。

  • 避免冲突:通过确保每个节点获取的ID段不重叠,可以保证生成的ID在全系统范围内是唯一的。

优缺点分析

  • 优点:减少了对数据库的频繁访问,提高了性能;适合在分布式系统中使用。

  • 缺点:管理复杂性:管理不同的ID段需要额外的逻辑和数据库设计。可能的ID浪费:如果某个服务或实例在用完其ID段之前下线或重启,可能导致分配的ID未被完全使用。

  • 网络依赖性:对网络的依赖相对较低,只在申请新的ID段时需要访问数据库。

实际例子

把数据库自增主键换成了计数法。每个业务分配一个biz_tag、并记录各业务最大id(max_id)、号段跨度(step)等数据。这样每次取号只需要更新biz_tag对应的max_id,就可以拿到stepid。比如业务business_a当前max_id3000,取了1000个号在本地使用,使用完后,可以更新DB中对应的max_id4000,这样business_a本地就有1000个新的id可以用了

优点

  1. 除了拥有自增ID的优点之外,在性能上比自增ID更好,扩展灵活。
  2. 使用灵活、可配置性强。
  3. 缓存机制,突发状况下短时间内能保证服务正常运转。

缺点

  1. id是有序自增,容易暴露信息,不可用于订单。
  2. leaf的缓存ID用完再去获取新号段的间隙,性能会有波动。
  3. 强依赖DB

七 :增强版Leaf-segment

增强版是对上面描述的缺点2进行的改进------双cache。在leafID消耗到一定百分比时,常驻的后台进程会预先去号段服务获取新的号段并缓存。具体消耗百分比、及号段step根据业务消耗速度来定。

八:Tinyid

和增强版Leaf-segment类似,也是号段模式,提前加载号段。

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

分布式协调服务在集群中生成唯一ID

ZooKeeper是使用了Znode结构中的Zxid实现顺序增IDZookeeper类似一个文件系统,每个节点都有唯一路径名(Znode),Zxid是个全局事务计数器,每个节点发生变化都会记录响应的版本(Zxid),这个版本号是全局唯一且顺序递增的。这种架构还是出现了ZooKeeper的单点问题。

实现原理

  • 工作方式:这些服务提供了分布式锁和原子性操作来生成唯一的ID

  • 协调机制:通过集群协调机制保证ID的唯一性和顺序性。

优缺点

  • 优点:提供了更加灵活和可控的ID生成方式,适合分布式环境。

  • 缺点:引入外部依赖,增加了系统的复杂性。

  • 网络依赖性:高度依赖网络,因为它们需要在多个节点之间协调ID的生成。

相关推荐
lmryBC4920 分钟前
获取golang变量的类型
开发语言·后端·golang
pen-ai38 分钟前
【NLP】 3. Distributional Similarity in NLP(分布式相似性)
人工智能·分布式·自然语言处理
DemonAvenger1 小时前
实战:从零仿NSQ打造高性能消息队列 - Go开发者的进阶之路
分布式·架构·go
YGGP2 小时前
【Gee】项目总结:模仿 GIN 实现简单的 Golang Web 框架
golang
MiniFlyZt4 小时前
使用Redis如何实现分布式锁?(超卖)
数据库·redis·分布式
尤宸翎6 小时前
Julia语言的饼图
开发语言·后端·golang
老朋友此林6 小时前
Redisson 实现分布式锁源码浅析
java·redis·分布式
穆韵澜6 小时前
SQL语言的云计算
开发语言·后端·golang
涂瑷菡9 小时前
Bash语言的进程管理
开发语言·后端·golang
穆骊瑶10 小时前
Python语言的代码重构
开发语言·后端·golang