【阅读】Leaf——美团点评分布式ID生成系统
文章目录
- 背景
- 常见实现方案
- UUID
- 优势
- 缺点
- 应用
- 类SnowFlake方案
- 优点
- 缺点
- 应用
- 数据库生成
- 优点
- 缺点
- MySQL 增强版
- 优点
- 缺点
- Leaf -segment方案
- 优点
- 缺点
- Leaf -segment方案-双Buffer优化
- 优点
- 缺点
- Leaf -snowflake方案
- 弱依赖ZK
- 时钟同步问题
- 优点
- 缺点
- 参考文档
背景
阅读了美团的ID生成器架构设计,记个笔记,同时强烈推荐阅读原文;
业务系统对ID号的要求有:
- 全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
- 趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。
- 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
- 信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。
3和4需求还是互斥的,无法使用同一个方案满足。
业务还对ID号生成系统的可用性要求极高,由此总结下一个ID生成系统应该做到如下几点:
- 平均延迟和TP999延迟都要尽可能低;
- 可用性5个9;
- 高QPS。
常见实现方案
UUID
UUID(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符;
优势
性能高,本地生成,没有网络消耗;
缺点
不易于存储;UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用;
信息不安全;基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置;
ID作为主键时在某些场景不适用;比如做DB主键的场景下,UUID就非常不适用;而且UUID的无序性会导致数据位置在MySQL索引中频繁变动;
应用
类SnowFlake方案
是一种以划分命名空间(UUID也算,由于比较常见,所以单独分析)来生成ID的一种算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等;理论上snowflake方案的QPS约为409.6w/s,这种分配方式可以保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不同的。
第一bit不用,41-bit的时间可以表示(1L<<41)/(1000L360024*365)=69年的时间,10-bit机器可以分别表示1024台机器【如果我们对IDC划分有需求,还可以将10-bit分5-bit给IDC,分5-bit给工作机器,这样就可以表示32个IDC,每个IDC下可以有32台机器,可以根据自身需求定义】,12个自增序列号可以表示2^12个ID;
优点
ID趋势递增,毫秒数在高位,自增序列在低位;
不依赖数据库等第三方系统,本地生成,稳定性高,性能也高;
分配灵活,根据自身业务特性分配bit位;
缺点
强依赖机器时钟,机器上的时钟回拨会导致ID重复或者服务不可用;
应用
MongoDB的objectID,通过“时间+机器码+pid+inc”共12个字节,通过4+3+2+3的方式最终标识成一个24长度的十六进制字符。
数据库生成
以MySQL举例;利用给字段设置auto_increment_increment
和auto_increment_offset
来保证ID自增,每次业务使用下列SQL读写MySQL得到ID号
begin;
REPLACE INTO Tickets64 (stub) VALUES (‘a’);
SELECT LAST_INSERT_ID();
commit;
replace into 跟 insert 功能类似,不同点在于:replace into 首先尝试插入数据到表中, 1. 如果发现表中已经有此行数据(根据主键或者唯一索引判断)则先删除此行数据,然后插入新的数据。 2. 否则,直接插入新数据。
要注意的是:插入数据的表必须有主键或者是唯一索引!否则的话,replace into 会直接插入数据,这将导致表中出现重复的数据。
优点
成本小,简单,通过MySQL就能实现;
ID号自增,在特殊场景下这也是优势;
缺点
强依赖DB,当DB异常时整个系统不可用;
DB主从切换时可能导致数据不一致,ID重复;
性能瓶颈在单台MySQL;
MySQL 增强版
N台机器,每台机器的步长需设置为N,每台的初始值依次为0,1,2…N-1;
优点
缺点
系统水平扩展很难;机器台数和步长确定后,扩容麻烦;
ID不能单调递增,只能趋势递增;
数据库压力依旧很大,每次获取ID都需要读写一次数据库;
或许可以通过预生成来减少数据库的压力;比如数据库预先生产千万级的数据到缓存中,业务先从缓存读取ID,缓存没有了再去数据库中读取;数据库中数据批量追加到缓存中;
Leaf -segment方案
Leaf分别在上述第二种和第三种方案上做了相应的优化,实现了Leaf-segment和Leaf-snowflake方案。
通过proxy server批量获取,每次获取一个segment(step决定大小)号段的值,用完后再去数据库中取;同时不同业务通过biz_tag字段区分,每个biz_tag的ID获取,互相隔离;
扩容通过biz_tag分库分表即可;
±------------±-------------±-----±----±------------------±----------------------------+
| Field | Type | Null | Key | Default | Extra |
±------------±-------------±-----±----±------------------±----------------------------+
| biz_tag | varchar(128) | NO | PRI | | |
| max_id | bigint(20) | NO | | 1 | |
| step | int(11) | NO | | NULL | |
| desc | varchar(256) | YES | | NULL | |
| update_time | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
±------------±-------------±-----±----±------------------±----------------------------+
biz_tag用来区分业务,max_id表示biz_tag目前被分配的ID号段的最大值,step表示每次分配的号段长度;(比如step为1000,那么当1000取完了才去数据库中读取)
架构设计:
Begin
UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx
SELECT tag, max_id, step FROM table WHERE biz_tag=xxx
Commit
优点
leaf服务扩容方便,性能能支撑大多数业务场景;
ID号码是趋势递增的8bytes的64位数字,用MySQL就能满足;
容灾性高,leaf内部有号段缓存,即使DB宕机段时间内服务仍然可用;
迁移性好,max_id可变,迁移方便;
缺点
ID号码不够随机,不太安全;
TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,所以TP999会出现尖刺;
DB宕机会导致系统不可用;
Leaf -segment方案-双Buffer优化
leaf取号段的时机是在号段消耗完才进行的;也就是当前号段的上界点的ID下发时间是下一号段的取回号段时间,过程中新的拉取ID请求需要等待(线程阻塞),特别是取DB的时候网络的抖动或者DB的慢查询会导致整个系统的响应时间变慢;
本架构设计在号段消耗完前,提前预拉取下一号段的数据;
内存中同时存在2个号段数据,号段通过异步线程滚动前进;
优点
每个biz_tag都有消费进度监控,比如号段长度设置为高峰期QPS的600倍;
每次请求都会判断下一个号段的状态,从而更新下一个号段;
缺点
Leaf -snowflake方案
Leaf分别在上述第二种和第三种方案上做了相应的优化,实现了Leaf-segment和Leaf-snowflake方案。
snowflake方案适合订单类,不需要趋势递增的场景;
leaf-snowflake方案仍然使用snowflake方案的bit位设计,“1+42+10+12”;
通过zookeeper服务注册管理每个leaf服务,并且zk负责下发每个leaf服务的work id,作为标识启动后续ID生成程序;
弱依赖ZK
在本机缓存一个worker ID文件,当zk出现问题时,通过读取文件实现对zk的弱依赖;
时钟同步问题
如果机器时钟发生了回拨,那么就会发生重复ID号;启动顺序如下:
参见上图整个启动流程图,服务启动时首先检查自己是否写过ZooKeeper leaf_forever节点:
若写过,则用自身系统时间与leaf_forever/ s e l f 节点记录时间做比较,若小于 l e a f f o r e v e r / {self}节点记录时间做比较,若小于leaf_forever/ self节点记录时间做比较,若小于leafforever/{self}时间则认为机器时间发生了大步长回拨,服务启动失败并报警。
若未写过,证明是新服务节点,直接创建持久节点leaf_forever/${self}并写入自身系统时间,接下来综合对比其余Leaf节点的系统时间来判断自身系统时间是否准确,具体做法是取leaf_temporary下的所有临时节点(所有运行中的Leaf-snowflake节点)的服务IP:Port,然后通过RPC请求得到所有节点的系统时间,计算sum(time)/nodeSize。
若abs( 系统时间-sum(time)/nodeSize ) < 阈值,认为当前系统时间准确,正常启动服务,同时写临时节点leaf_temporary/${self} 维持租约。
否则认为本机系统时间发生大步长偏移,启动失败并报警。
每隔一段时间(3s)上报自身系统时间写入leaf_forever/${self}。
由于强依赖时钟,对时间的要求敏感,需要关闭机器的NTP同步;
并且在时钟回拨时不提供服务或者返回Error_Code,或者摘除节点并报警;
// 当前时间小于上次的发号时间
if (timestamp < lastTimeStamp) {
// 发生时钟回拨
}
优点
目前Leaf的性能在4C8G的机器上QPS能压测到近5万/s,TP999 1ms
缺点
参考文档
leaf 美团的分布式ID生成器
【阅读】Leaf——美团点评分布式ID生成系统相关推荐
- Leaf——美团点评分布式ID生成系统
背景 在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识.如在美团点评的金融.支付.餐饮.酒店.猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数 ...
- [分布式id]:Leaf——美团点评分布式ID生成系统
文章目录 摘要 1 分布式id相关概念 1.1 为什么需要分布式id 1.1.1 BTree 1.1.2 B+Tree 1.1.3 B+Tree相比于BTree优势 1.2 什么是分布式id 1.3 ...
- 美团开源分布式ID生成系统——Leaf源码阅读笔记(Leaf的号段模式)
Leaf 最早期需求是各个业务线的订单ID生成需求.在美团早期,有的业务直接通过DB自增的方式生成ID,有的业务通过redis缓存来生成ID,也有的业务直接用UUID这种方式来生成ID.以上的方式各自 ...
- 带你了解「美团、百度和滴滴」的分布式 ID 生成系统
文章目录 美团 背景 常见方法介绍 UUID 类snowflake方案 数据库生成 Leaf 方案实现 Leaf-segment 数据库方案 双 buffer 优化 Leaf 高可用容灾 Leaf-s ...
- 美团技术分享:深度解密美团的分布式ID生成算法
本文来自美团技术团队"照东"的分享,原题<Leaf--美团点评分布式ID生成系统>,收录时有勘误.修订并重新排版,感谢原作者的分享. 1.引言 鉴于IM系统中聊天消息I ...
- 在 .NET6.0 中实现 Leaf-segment 分布式 ID 生成系统
背景 分布式 ID (Distributed ID)是指在分布式系统中,为了保证全局唯一性而生成的一类 ID.在分布式系统中,由于存在多个节点同时生成 ID 的情况,因此需要采用一定的算法来保证生成的 ...
- 分布式 ID 生成系统 Leaf 的设计思路,源码解读
什么是分布式ID? ID 最大的特点是 唯一 而分布式 ID,就是指分布式系统下的 ID,它是 全局唯一 的. 为啥需要分布式ID呢? 这就和 唯一 息息相关了. 比如我们用 MySQL 存储数据,一 ...
- 滴滴开源的分布式id生成系统
ID Generator id生成器 分布式id生成系统,简单易用.高性能.高可用的id生成系统 简介 Tinyid是用Java开发的一款分布式id生成系统,基于数据库号段算法实现,关于这个算法可以参 ...
- Leaf-美团分布式id生成系统
概述 分布式id生成已经有业界较为成熟的方案.现在公司使用的是美团的Leaf的号码段模式.之所以不用雪花算法模式还是因为雪花算法的自身缺陷,即时间回拨问题.本文就从源码角度剖析leaf项目的两种id生 ...
最新文章
- linux shell ls 时间排序显示
- 《游戏引擎架构》笔记十四
- The Linux SG_IO ioctl in the 2.6 series
- 肉体之爱的解释圣经_可以解释的AI简介,以及我们为什么需要它
- 汇编Ring 3下实现 HOOK API
- [Spring-cloud-eureka]使用 gradle, spring boot,Spring cloud Eureka 搭建服务消费者
- Java编程的逻辑 (43) - 剖析TreeMap
- 《Servlet和JSP学习指南》一导读
- vs 生成get set_TVM代码学习 -- 代码生成流程(一)
- STM32的串口DMA收发以及双缓冲区的实现
- vue 判断是否function_vue2.0组件的prop验证中的Function类型怎么使用(向子组件传递函数对象的正确方法)?...
- cdr软件百度百科_cdr是什么软件?
- eclipse安装反编译软件jd-gui
- C++实现的windows系统下的WIFI管理
- ESP8266学习笔记:实现ESP8266的局域网内通信
- Android——广播
- 【论文阅读】ICRA2021: VDB-EDT An Efficient Euclidean Distance Transform Algorithm Based on VDB Data Struct
- java的日历类_java 中的Calendar日历类
- 解析SQL的表间血缘关系工具
- HTML+CSS期末网页设计前端作品(大三)