分布式中唯一ID生成算法

news/2024/11/17 21:44:11/文章来源:https://www.cnblogs.com/atongmuhao/p/18303755

前言

分布式系统中,难免会需要生成唯一ID作为标识符的需求。数据库主键,订单系统,日志系统,消息队列,会话管理,当并发量巨大且需要唯一标识信息的ID时,唯一ID生成算法就显得非常重要。

UUID

UUID(Universally Unique Identifier,通用唯一标识符)是一种标准化的唯一标识符生成算法,它能够在全球范围内保证生成的标识符是唯一的。UUID 根据不同的版本有不同的生成方式,其中比较常用的是 UUIDv1 和 UUIDv4。

UUIDv1

  • UUIDv1 基于主机的 MAC 地址和时间戳生成

结构:
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

//生成:
package mainimport ("fmt""github.com/google/uuid"
)func main() {id := uuid.New()fmt.Printf("UUIDv1 : %s\n" , id)
}

输出:UUIDv1 : 39d00ade-312e-455a-92f3-bbfdda307a30

UUIDv4

  • UUIDv4 是基于随机数生成的 UUID

结构:
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx

//生成
package mainimport ("fmt""github.com/google/uuid"
)func main() {id , _ := uuid.NewRandom()fmt.Printf("UUIDv4: %s\n", id)
}

输出:UUIDv4: d6a291ab-ddc5-4b5a-9640-c6c3a32fe9e0

Snowflake

看似uuid已经可以满足唯一性了,但是面对不同的场景,他的功能往往不够全面,
例如,在日志记录和审计跟踪中,为确保事件顺序的正确性,支持时间段的日志检索抑或在消息队列中为顺序处理业务逻辑,避免数据竞争和冲突,uuid往往不能完全胜任,为满足id更细致的排序,进化出了snoeflake雪花算法

  • Snowflake,雪花算法是由Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64-bit位分割成多个部分,每个部分代表不同的含义

  • 第一部分1 bit,是无意义的:
    因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。
  • 第二部分41 bit:表示的是时间戳,单位是毫秒,41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2 ^ 41 - 1 个毫秒值,换算成年就是表示 69 年的时间。
  • 第三部分是 10 个 bit:记录工作机器 id,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。 但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 2 ^ 5 个机房(32 个机房),每个机房里可以代表 2 ^ 5 个机器(32 台机器),这里可以随意拆分,比如拿出4位标识业务号,其他6位作为机器号。可以随意组合。
  • 第四部分这个是用来记录同一个毫秒内产生的不同 id。
    12 bit 可以代表的最大正整数是 2 ^ 12 - 1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分同一个毫秒内的 4096 个不同的 id。也就是同一毫秒内同一台机器所生成的最大ID数量为4096

  简单来说,你的某个服务假设要生成一个全局唯一 id,那么就可以发送一个请求给部署了 SnowFlake 算法的系统,由这个 SnowFlake 算法系统来生成唯一 id。这个 SnowFlake 算法系统首先肯定是知道自己所在的机器号,(这里姑且讲10bit全部作为工作机器ID)接着 SnowFlake 算法系统接收到这个请求之后,首先就会用二进制位运算的方式生成一个 64 bit 的 long 型 id,64 个 bit 中的第一个 bit 是无意义的。接着用当前时间戳(单位到毫秒)占用41 个 bit,然后接着 10 个 bit 设置机器 id。最后再判断一下,当前这台机房的这台机器上这一毫秒内,这是第几个请求,给这次生成 id 的请求累加一个序号,作为最后的 12 个 bit。

//源码:
func NewNode(node int64) (*Node, error) {// 加锁以防止在重新计算常量时的并发访问。mu.Lock()defer mu.Unlock() // 在函数返回时确保解锁互斥锁。// 已废弃:根据自定义的 NodeBits 或 StepBits 设置重新计算常量。nodeMax = -1 ^ (-1 << NodeBits)nodeMask = nodeMax << StepBitsstepMask = -1 ^ (-1 << StepBits)timeShift = NodeBits + StepBitsnodeShift = StepBits// 初始化一个新的 Node 实例。n := Node{}n.node = noden.nodeMax = nodeMaxn.nodeMask = nodeMaskn.stepMask = stepMaskn.timeShift = timeShiftn.nodeShift = nodeShift// 验证节点号是否在有效范围内。if n.node < 0 || n.node > n.nodeMax {return nil, errors.New("节点号必须在 0 到 " + strconv.FormatInt(n.nodeMax, 10) + " 之间")}// 基于当前系统时间设置起始时间。curTime := time.Now()n.epoch = curTime.Add(time.Unix(Epoch/1000, (Epoch%1000)*1000000).Sub(curTime))// 返回初始化后的 Node 实例。return &n, nil
}
// Generate 生成并返回一个唯一的雪花算法 ID
// 为了确保唯一性:
// - 确保您的系统保持准确的系统时间
// - 确保永远不要有多个节点使用相同的节点 ID 运行
func (n *Node) Generate() ID {// 加锁,保护并发访问n.mu.Lock()defer n.mu.Unlock()// 计算当前时间戳(毫秒)now := time.Since(n.epoch).Nanoseconds() / 1000000// 如果当前时间与上次生成的时间相同,则增加步长if now == n.time {n.step = (n.step + 1) & n.stepMask// 如果步长溢出,则等待直到时间变化if n.step == 0 {for now <= n.time {now = time.Since(n.epoch).Nanoseconds() / 1000000}}} else {// 如果当前时间不同,重置步长n.step = 0}// 更新记录的时间n.time = now// 构造雪花 IDr := ID((now)<<n.timeShift |(n.node << n.nodeShift) |(n.step),)return r
}
// Int64 返回雪花 ID 的 int64 表示
func (f ID) Int64() int64 {return int64(f)
}

实例:

package mainimport ("fmt""github.com/bwmarrin/snowflake"
)
const(stateId = 1
)
func main() {sf , _:= snowflake.NewNode(stateId)id := sf.Generate().Int64()fmt.Println(id)
}

输出:1812727190702264320

Snowflake 算法之所以能够实现时间排序,主要是因为它在设计上解决了 UUIDv1 中存在的一些问题,特别是时钟回退和精度问题
以下来自chatGPT,说的挺中肯的

1.	独立时间戳和序列号:•	Snowflake 算法中,生成的唯一 ID 包含了一个以毫秒为单位的时间戳和一个序列号。这两部分都是独立的,时间戳部分记录了生成 ID 的精确时间,序列号部分确保在同一毫秒内生成的 ID 是递增的。2.	自定义起始时间:•	Snowflake 算法可以设置一个起始时间(epoch),通常是一个固定的时间点(例如 Unix 时间戳的起始时间)。这样可以确保算法在不同系统上始终使用相同的时间基准,避免时钟回退问题带来的影响。3.	高精度时间戳:•	Snowflake 算法使用的是毫秒级别的时间戳,相比 UUIDv1 的 100 纳秒精度更为适中,既能满足高并发生成 ID 的需求,又不会因为精度过高导致性能问题。4.	节点标识和数据中心标识:•	Snowflake 算法中,节点标识和数据中心标识是作为配置参数传入的,确保了不同节点生成的 ID 在全局范围内是唯一的。这样即使在分布式环境下,也能保证生成的 ID 是唯一且时间顺序的。5.	简单的逻辑和高效性:•	Snowflake 算法的设计相对简单且高效,使其能够在高并发环境下快速生成 ID,而不会成为系统性能的瓶颈。

时间对比

Generated 10000 UUIDs in 7.605208ms

Generated 10000 Snowflake IDs in 1.7125ms

百度 UidGenerator

雪花算法提供了一个很好的设计思想,雪花算法生成的ID是趋势递增,不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的,而且可以根据自身业务特性分配bit位,非常灵活。

但是雪花算法强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。如果恰巧回退前生成过一些ID,而时间回退后,生成的ID就有可能重复。官方对于此并没有给出解决方案,而是简单的抛错处理,这样会造成在时间被追回之前的这段时间服务不可用。

由此便演化出了一些改进算法
以百度开源的基于Snowflake优化的的ID生成器为例子,UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略,适用于docker等虚拟化环境下实例自动重启、漂移等场景。
细节可参考click here

目前有个很大的问题是该算法只有java版本,如果有机会完全可以自己手写一个(现在还太菜)。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/744238.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2. DRF 解析器

目录Django DRF 解析器1. 解析器作用2. 使用解析器默认配置为视图类定义解析器修改全局配置 Django DRF 解析器 1. 解析器作用 request请求先进到APIView的dispatch方法, 如果有异常走到exception。 Json请求www-form 请求form-data请求可以看到不同类型的请求, request.data …

2. 解释器与PyCharm安装

1. 解释器安装 官网选择解释器版本 建议选择比最新版本低2~3个版本,当前选择3.10自定义安装

Epson机器人编程初级阶(二)

Wait 等待时间与信号控制,时间默认单位是秒Wait Sw(0) =On:等待输入0变为On状态 Wait 60.5:等待60.5秒后执行 Wait Sw(0) = Off And Sw(1) = On:等待输入0变为Off并且出入1变为On状态 Wait Memsw(0) = On Or Memsw(1) = On:等待储存位0变为啥On或存储位1变为On状态 Wait …

软件测试理论知识-分类和方法

一、软件测试分类汇总分类方法分类内容按开发阶段 单元测试、集成测试、系统测试、验收测试按测试实施组织 α、β、第三方按测试执行方式 静态测试、动态测试按是否查看代码 黑盒测试、白盒测试、灰盒测试按是否手工执行划分 手工测试、自动化测试按测试对象划分 性能测试、安…

文生SQL

主页个人微信公众号:密码应用技术实战 个人博客园首页:https://www.cnblogs.com/informatics/缘起 2022年12月ChatGPT的横空出世,掀起了LLM大模型的科技热潮,一时间文胜文, 文胜图,文生视频为大众所周知。SQL(Structured Query Language)是一种用于管理和操作关系型数据…

「代码随想录算法训练营」第十一天 | 二叉树 part1

二叉树的基本知识 链接:https://programmercarl.com/二叉树理论基础.html 要点:深度优先遍历前序遍历(递归法,迭代法) 中序遍历(递归法,迭代法) 后序遍历(递归法,迭代法)广度优先遍历层次遍历(迭代法)由于栈就是递归的一种实现结构,因此前中后序遍历的逻辑可以借…

从0到1打造一个 WebRTC 应用

🧑‍💻 写在开头 点赞 + 收藏 === 学会🤣🤣🤣前言 2020 年初突如其来的新冠肺炎疫情让线下就医渠道几乎被切断,在此背景下,微医作为数字健康行业的领军者通过在线问诊等形式快速解决了大量急需就医人们的燃眉之急。而作为微医 Web 端在线问诊中重要的一环-医患之间…

K8S 中的 CRI、OCI、CRI shim、containerd

哈喽大家好,我是咸鱼。 好久没发文了,最近这段时间都在学 K8S。不知道大家是不是和咸鱼一样,刚开始学 K8S、Docker 的时候,往往被 CRI、OCI、CRI shim、containerd 这些名词搞得晕乎乎的,不清楚它们到底是干什么用的。所以今天,咸鱼打算借这篇文章来解释一下这些名词,帮…

通过手机去访问本地写的h5页面(使用同一个局域网)

主要流程为: 打开cmd,然后输入一行指令1.npm install http-server -g(全局安装http-server,前提是有node环境,并且手机和电脑用的是同一个局域网内)2.然后通过cmd进入到你放html文件的文件夹内 3.通过http-server指令开启服务,cmd就会提示:

Turtlebot3在ROS Gazebo中使用OpenCV检测并跟踪球体

原文链接:https://www.youtube.com/watch?v=Rw6ATkORRG8一个小巧的机器人在虚拟世界中敏捷地追踪着一个滚动的球体。Turtlebot3,一个搭载ROS操作系统的智能机器人,在Gazebo仿真环境中,利用OpenCV的神奇力量,展现出令人惊叹的视觉追踪能力。Turtlebot3的"眼睛"是…

Python循环控制

本文介绍了Python编程语言中关于for循环和if条件控制的一些基本使用。包含了单层循环的退出机制和多层循环的退出机制,使得我们在满足特定条件时,可以直接结束多层循环。技术背景 循环控制是每一门编程语言的基础,最常用的就是for循环和while循环。使用循环可以很大程度上简…