文章目录
- 简介
- 一:UUID
- 二、雪花算法
- 三:Leaf-snowflake
- 四:数据库自增ID
- 五:使用Redis实现分布式ID生成
- 六:使用数据库分段(Leaf-segment)
- 七 :增强版Leaf-segment
- 八:Tinyid
- 九: 分布式键生成服务(如Zookeeper、etcd)
简介
在分布式系统中,生成唯一的ID
是一个核心问题,特别是在需要确保数据完整性和避免冲突的场景中,比如作为链路追踪的trace_id
,作为幂等性的主键id
, 分布式锁中为了避免释放的是别人的锁时,本地保存的唯一id
等。以下是对一些分布式唯一ID
生成方法的详细阐述,包括它们的工作原理、优缺点,以及对网络依赖性的考量
一:UUID
实现原理
-
工作方式:
UUID
是通过一系列算法生成的128
位数字,通常基于时间戳、计算机硬件标识符、随机数等元素。 -
全局唯一性:算法设计确保了即使在分布式系统中也能生成全局唯一的
ID
。
优缺点
-
优点:实现简单,无需网络交互,保证了
ID
的全球唯一性。 -
缺点:通常不能保证顺序性,
ID
较长,可能导致存储和索引效率低下。 -
网络依赖性:无网络依赖。
注意:工作中基本不使用uuid,这里是为了介绍分布式ID的完整性,以及UUID大家都熟知,所以一起介绍一下
uuid有两种包:
-
github.com/google/uuid
,仅支持V1
和V4
版本。 -
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字节,偏长。
-
无序。
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
开发的一种生成64
位ID
的服务,基于时间戳、节点ID
和序列号。
实现原理
-
工作方式:结合时间戳、工作机器的
ID
和序列号来生成64
位的ID
。时间戳保证了ID
的唯一性和顺序性,工作机器ID
保证了在多机环境下的唯一性。 -
时间戳:确保
ID
按时间顺序增长。
优缺点
-
优点:
ID
有时间顺序,长度适中,生成速度快。 -
缺点:对系统时钟有依赖,时钟回拨会导致ID冲突。(时钟回拨:服务器上的时间突然倒退回之前的时间。可能是人为的调整时间;也可能是服务器之间的时间校对。)
-
网络依赖性:通常无需网络交互,除非在多机器环境中同步机器
ID
。
由于雪花算法工作中还是可能用到的,所以介绍更详细一些。
Snowflake的核心思想是:使用41bit
作为毫秒数,10bit
作为机器的ID
(5
个bit
是数据中心(DC
,机房)),5
个bit
的机器ID
(也可能是容器id
)),12bit
作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096
个 ID),最后还有一个符号位,永远是01
。这样可以确保每个ID
都是唯一的。
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
提供了原子性的INCR
和INCRBY
命令,可用于生成唯一的递增数值。这些数值可以作为唯一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
,就可以拿到step
个id
。比如业务business_a
当前max_id
是3000
,取了1000
个号在本地使用,使用完后,可以更新DB
中对应的max_id
为4000
,这样business_a
本地就有1000
个新的id
可以用了
优点:
- 除了拥有自增
ID
的优点之外,在性能上比自增ID
更好,扩展灵活。 - 使用灵活、可配置性强。
- 缓存机制,突发状况下短时间内能保证服务正常运转。
缺点:
id
是有序自增,容易暴露信息,不可用于订单。- 在
leaf
的缓存ID
用完再去获取新号段的间隙,性能会有波动。 - 强依赖
DB
。
七 :增强版Leaf-segment
增强版是对上面描述的缺点2
进行的改进——双cache
。在leaf
的ID
消耗到一定百分比时,常驻的后台进程会预先去号段服务获取新的号段并缓存。具体消耗百分比、及号段step
根据业务消耗速度来定。
八:Tinyid
和增强版Leaf-segment
类似,也是号段模式,提前加载号段。
九: 分布式键生成服务(如Zookeeper、etcd)
分布式协调服务在集群中生成唯一ID
。
ZooKeeper
是使用了Znode
结构中的Zxid
实现顺序增ID
。Zookeeper
类似一个文件系统,每个节点都有唯一路径名(Znode
),Zxid
是个全局事务计数器,每个节点发生变化都会记录响应的版本(Zxid
),这个版本号是全局唯一且顺序递增的。这种架构还是出现了ZooKeeper
的单点问题。
实现原理
-
工作方式:这些服务提供了分布式锁和原子性操作来生成唯一的
ID
。 -
协调机制:通过集群协调机制保证
ID
的唯一性和顺序性。
优缺点
-
优点:提供了更加灵活和可控的
ID
生成方式,适合分布式环境。 -
缺点:引入外部依赖,增加了系统的复杂性。
-
网络依赖性:高度依赖网络,因为它们需要在多个节点之间协调
ID
的生成。