百度 UidGenerator 源码解析

本文深入解析了百度的UidGenerator,它是基于Twitter的Snowflake算法实现的Java全局唯一ID生成器。UidGenerator支持自定义workerId位数和初始化策略,适用于虚拟化环境。文章详细介绍了Snowflake算法的结构,包括时间戳、工作机器ID和序列号,并分析了UidGenerator的源码,特别是DefaultUidGenerator和CachedUidGenerator的实现,以及RingBuffer在提高性能中的作用。此外,还探讨了位运算在UidGenerator中的应用。
摘要由CSDN通过智能技术生成

简介

先来看一下官方介绍:

雪花算法

雪花算法(Snowflake)是一种生成分布式全局唯一 ID 的算法,生成的 ID 称为 Snowflake IDs 或 snowflakes。这种算法由 Twitter 创建,并用于推文的 ID。Discord 和 Instagram 等其他公司采用了修改后的版本。一个 Snowflake ID 有 64 位。前 41 位是时间戳,表示了自选定的时期以来的毫秒数。接下来的 10 位代表计算机 ID,防止冲突。其余 12 位代表每台机器上生成 ID 的序列号,这允许在同一毫秒内创建多个 Snowflake ID。SnowflakeID 基于时间生成,故可以按时间排序。此外,一个 ID 的生成时间可以由其自身推断出来,反之亦然。该特性可以用于按时间筛选 ID,以及与之联系的对象。

第 1 位

该位不用主要是为了保持 ID 的自增特性,若使用了最高位,int64 会表示为负数。在 Java 中由于 long 类型的最高位是符号位,正数是 0,负数是 1,一般生成的 ID 为正整数,所以最高位为 0

41 位时间戳

毫秒级的时间,一般实现上不会存储当前的时间戳,而是时间戳的差值(当前时间减去固定的开始时间),这样可以使产生的 ID 从更小值开始;

41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2 ^ 41 - 1 个毫秒值,换算成年就是表示 69 年的时间。

(1L << 41) / (1000L 60 60 24 365) = (2199023255552 / 31536000000) ≈ 69.73 年。

10 位工作机器 ID

Twitter 实现中使用前 5 位作为数据中心标识,后 5 位作为机器标识,可以部署 1024 (2^10)个节点。意思就是最多代表 2 ^ 5 个机房(32 个机房),每个机房里可以代表 2 ^ 5 个机器(32 台机器)。具体的分区可以根据自己的需要定义。比如拿出 4 位标识业务号,其他 6 位作为机器号。

12 位序列号

支持同一毫秒内同一个节点可以生成 4096 (2^12)个 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。

优点:

  • 理论上 Snowflake 方案的 QPS 约为 409.6w/s(1000 * 2^12),这种分配方式可以保证在任何一个 IDC 的任何一台机器在任意毫秒内生成的 ID 都是不同的。

缺点

  • 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

UidGenerator

UidGenerator 是 Java 实现的,基于 Snowflake 算法的唯一 ID 生成器。UidGenerator 以组件形式工作在应用项目中,支持自定义 workerId 位数和初始化策略,从而适用于 docker 等虚拟化环境下实例自动重启、漂移等场景。在实现上,UidGenerator 通过借用未来时间来解决 sequence 天然存在的并发限制;采用 RingBuffer 来缓存已生成的 UID, 并行化 UID 的生产和消费,同时对 CacheLine 补齐,避免了由 RingBuffer 带来的硬件级「伪共享」问题。最终单机 QPS 可达 600 万。

UidGenerator 的实现跟 SnowFlake 原始算法不太一样,不过以下参数均可通过 Spring 进行自定义:

  • sign(1bit) 固定 1bit 符号标识,即生成的 UID 为正数。

  • delta seconds (28 bits) 当前时间,相对于时间基点"2016-05-20"的增量值,单位:秒,最多可支持约 8.7 年

  • worker id (22 bits) 机器 id,最多可支持约 420w 次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略。

  • sequence (13 bits) 每秒下的并发序列,13 bits 可支持每秒 8192 个并发。

RingBuffer 环形数组,数组每个元素成为一个 slot。RingBuffer 容量,默认为 Snowflake 算法中 sequence 最大值,且为 2^N。可通过 boostPower 配置进行扩容,以提高 RingBuffer 读写吞吐量。

Tail 指针、Cursor 指针用于环形数组上读写 slot:

  • Tail 指针 表示 Producer 生产的最大序号(此序号从 0 开始,持续递增)。Tail 不能超过 Cursor,即生产者不能覆盖未消费的 slot。当 Tail 已赶上 curosr,此时可通过 rejectedPutBufferHandler 指定 PutRejectPolicy

  • Cursor 指针 表示 Consumer 消费到的最小序号(序号序列与 Producer 序列相同)。Cursor 不能超过 Tail,即不能消费未生产的 slot。当 Cursor 已赶上 tail,此时可通过 rejectedTakeBufferHandler 指定 TakeRejectPolicy

CachedUidGenerator 采用了双 RingBuffer,Uid-RingBuffer 用于存储 Uid、Flag-RingBuffer 用于存储 Uid 状态 (是否可填充、是否可消费)

由于数组元素在内存中是连续分配的,可最大程度利用 CPU cache 以提升性能。但同时会带来「伪共享」FalseSharing 问题,为此在 Tail、Cursor 指针、Flag-RingBuffer 中采用了 CacheLine 补齐方式。

关于更多伪共享的知识,可以参考:https://www.cnblogs.com/cyfonly/p/5800758.html,

总结来说,伪共享会导致性能问题,解决了能提升性能,就算不解决也不会出现数据不一致等严重的问题。

RingBuffer 填充时机

  • 初始化预填充 RingBuffer 初始化时,预先填充满整个 RingBuffer.

  • 即时填充 Take 消费时,即时检查剩余可用 slot 量 (tail - cursor),如小于设定阈值,则补全空闲 slots。阈值可通过 paddingFactor 来进行配置

  • 周期填充 通过 Schedule 线程,定时补全空闲 slots。可通过 scheduleInterval 配置,以应用定时填充功能,并指定 S

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值