打印
[资料干货]

分享: 来学一学大厂里面常见的分布式ID方案

[复制链接]
2637|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
科叼|  楼主 | 2024-6-3 16:41 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
crm志字辈小蚂蚁
技术大厂机会,前后端/测试可投
一. 业界常用的几种分布式ID方案主要是那么几种 :
基于雪花算法:通常基于雪花 + 自身的系统特性来进行一定的改造
基于分布式锁 + 号段 + 雪花算法: 美团的 Leaf 就是采用这种方式 ,会更适用于并发高的场景
基于数据库的分布式ID: 不同于老一代的数据库,现在很多新一代分布式数据库都带有ID生成的功能

除了以上这几种常见的方式 , 还有一些不太常见或者存在一定缺陷的分布式ID方式 :

UUID:长度较长 ,性能较差, 并没有实现系统间的可见和通信,所以还是存在重复的可能
基于组件的分布式ID: 基于外部组件来实现通信 / 上锁 / 数据生成
常见的包括 Redis , 或者 MongoDB 本身的 ObjectId 生成方式
问题在于并发不高 ,而且代码可能会很复杂,需要控制锁的粒度

二. 浅学一下大厂的实现方案
Leaf-snowflake :
即常规的雪花算法,但是在雪花的基础上做了一些变动
通过 Zookeeper 来配置 WorkerId ,最大的特性在于WorkerID 的可重用
本机系统上缓存 WorkerID 文件
启动时会校验时钟时间,时钟要大于之前机器时间(会和其他的机器时间做对比)
3.2 雪花 : 百度的uid-generator
**ID 格式 **
sign(1bit) : 固定1bit符号标识,即生成的UID为正数
delta seconds (28 bits) : 当前时间,相对于时间基点"2016-05-20"的增量值
时间不依赖于系统时间,而是基于 AtomicLong 进行自增
带来的后果就是时间可能会和实际时间偏移
存在借用未来时间的现象

worker id (22 bits) : 机器id,最多可支持约420w次机器启动
每次启动时初始化,数据库自增实现
可以自定义 WorkerId 位数和初始化策略 (这样的话就可以适配不同的集群范围)
sequence (13 bits)  :每秒下的并发序列,13 bits可支持每秒8192个并发
RingBuffer 的使用,不实时计算并发ID,预生成多个分布式ID进行保存

❓总结:
可以看到 , 百度认为机器ID的位数需要得更多,可能是由于服务器多,启动比较频繁导致
对于单机而言 ,每秒每条业务线 8000 并发已经很高了

❓精华 : sequence ID 的生成
一般简单的实现方式是通过加锁 synchronized 或者通过原子变量实现一个单机的自增
百度采用了RingBuffer的形式 ,来实现 sequence 的生成
总结一下 ,在我看来主要是 :预生成 + 预占 + 消费
百度的精髓在于 RingBuffer 实现的环形数组,实现了并发位ID的生产消费关系,在这个数组中存在两个指针 :
Tail指针: 当前生产的 ID 序列号
Cursor 指针: 当前消费的 ID 序列号
CachedUidGenerator采用了双RingBuffer,Uid-RingBuffer用于存储Uid、Flag-RingBuffer用于存储Uid状态(是否可填充、是否可消费)
由于RingBuffer是一个环形,所以要保证生产不能超过消费(PutRejectPolicy策略),还要避免消费追上了生产 (TakeRejectPolicy)
同时百度案例里面也提到了伪共享的问题,在 RingBuffer 中连续分配的数组可以通过 CPU Cache 最大程度的提升性能。 但是由于多个线程对同一缓存行竞争,会带来缓存行的失效和写前的缓存行校验,就带来了额外的性能损耗。

❓ 问题一 : 伪共享是什么意思 ?
不同核心(或处理器)共享同一缓存行而导致的性能问题 。
当多个核心同时修改或读取同一缓存行中的不同数据时,由于缓存的一致性协议,这个缓存行会被加载到多个核心的缓存中。
所以当多线程处理不相干的变量时会相互影响 , 造成伪共享拖慢速度。 (缓存竞争 / 无效化等)

❓ 问题二 :怎么解决伪共享 ?
核心点 :CPU 是以缓存行的形式进行读取的
基于这个概念 ,比较常见的解决方式是通过特殊的结构,或者通过填充缓存行的方式 ,确保不同的数据项被存储在不同的缓存行中,从而避免不必要的缓存行竞争

❓ 问题三:百度 UID Generator 具体解决思路 ?
RingBuffer填充时机
程序启动时,将RingBuffer填充满,缓存着8192个id
在调用getUID()获取id时,检测到RingBuffer中的剩余id个数小于总个数的50%,将RingBuffer填充满,使其缓存8192个id
定时填充
CacheLine补齐 : 通过 缓存行填充 , 避免tail和cursor落到同一个缓存行上
❓ 问题四:借用未来时间?? ?
简单点说 ,就是由于每秒只有 8192 个空位 ,所以当超过这个空位的时候 ,会借用 delta seconds 未来的位置,也就是借用未来时间。
也就是使用了未来的时间来生成id,【最多】可支持约8.7年

3.3  号段 :美团 Leaf-segment  @Leaf——美团点评分布式ID生成系统

Leaf-segment :
这种方案的核心在于每次从DB中拿取一个号段,那么锁的竞争主要在拿号段的过程中
为了解决这种问题,美团采用第一个号段使用百分之10的时候就提前拿后面一个号段
缺点在于号段是可以推导和预测的,比如预测销量。
所以很多现实里面会进行跳段,跳过多个数到下一个数。

3.4 号段 :滴滴Tinyid
Tinyid扩展了leaf-segment算法,支持了多db(master),同时提供了java-client(sdk)使id生成本地化,获得了更好的性能与可用性
总结和思考
分布式ID方案真的不难,业界常用的就是这些。
这些概念点也基本上写烂了,但是执意发出来的原因是想看过这些之后得出一些自己的理解。

想法心得 :
雪花算法 是 鼻祖 ,其他的算法或多或少在这个基础上进行了改造。充分说明了这个方案的优秀。
几大厂商里面 ,使用难度方面美团和百度的都比较简单 ,引入 SDK 通常就能实现 ,最多需要部署时间服务器,上手难度较低。 滴滴的方案需要部署多套服务器 ,还要引入 client 消费 ,依赖性比较强了。
技术深度最深的应该是 百度的 uid-generator  , 设计的思路感悟很多。 滴滴的 Tinyid 走出了另外一条路线,没有局限于雪花的思路。

三. 阶段总结 :

使用特权

评论回复

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

92

主题

100

帖子

1

粉丝