leaf分布式id生成-号段模式原理
[toc]
基于mysql最简单分布式ID实现
mysql自带自增主键功能,我们可以建一个表,主键id设置成自增,然后需要获取id,就往该表中插入一条数据,获取一下新增id即可。
这种方式优点就是简单。缺点也很明显
- 随着时间推移,表中数据越来越多,毕竟获得一个id就要插入一条数据。
- 所有获取id的都去请求这个数据库,很显然,高并发场景下单机会很乏力。
flickr分布式id解决方案
针对1.1基于mysql实现的分布式id各种缺点,flickr公司提出了一种基于mysql生成分布式id的解决方案。我们先看下它是怎么做的。
先创建一个表。
1 | CREATE TABLE `Tickets64` ( |
该表有2个字段,id就是bigint类型的mysql自增主键,stub作业务key。每次获取分布式id的时候,根据业务id执行下面这两条sql就可以了。
1 | REPLACE INTO Tickets64 (stub) VALUES (‘a’); |
第1行就是更新a这条数据的主键id,如果这个stub存在a的话,就更新一下这个id,不存在就新插入一条。
可以测试下,比如现在表中有这么一条数据。

执行: REPLACE INTO Tickets64 (stub) VALUES (‘a’);

如果这个stub存在a的话,就更新一下这个id。
不存在就新插入一条:

第2行就是获取当前connection最新插入的id。
到这里,1.1方案 缺点1数据量越来越多的问题就解决了。对于缺点2,单机高并发问题,它也提出来解决方案。
那就是多机器部署。使用多台mysql数据库,通过设置自增步长与起始id 达到多台数据库协调生成分布式id。
比如使用2台mysql,分别有一张Tickets64 表。这个时候,设置表的自增主键步长是2,mysql-01表中的起始id是1 ,mysql-02表中的id起始是2。这样他们的id就错开了。不同mysql实例生成的id就是这个样子。
这样就完美解决了单机高并发乏力的问题。
但是问题又来了!!!要搞几台mysql实例来用作分布式id生成,一开始业务量小,搞多了资源浪费,搞少了后期大流量的时候,还是扛不住,到时候再想加机器,就得调整自增步长,起始id,就会非常麻烦,非常麻烦,扩展性非常差。虽然flicker解决方案存在扩展性问题,但是一般公司还是可以使用的。
号段+mysql
到这里看看leaf是怎样基于号段+mysql实现的分布式id生成的。
1 | CREATE TABLE `leaf_alloc` ( |
biz_tag :就是业务标识
max_id:既是起始id也是最大id
step:步长或者是段长
description:描述,这个不用管
update_time:更新时间,这个不用管
比如说要整一个订单id生成的,这个时候插入一条数据即可。

这个时候我就要生成id,leaf是这样做的:
先去判断内存中 有没有这个biz_tag对应的atomicInteger,没有就执行这两个sql
1
2update leaf_alloc set max_id=max_id+step where biz_tag='order';
select * from leaf_alloc where biz_tag='order';
可以看到,这个时候数据库中的max_id就变成了3001,同时还将 max_id 与step 查到leaf服务内存中。将max_id与step相加之前的那个值设置到一个原子类中。将现在的max_id设置到一个max变量中。
下次来获取order的分布式id,看 atomicInteger中的数值小于max 值,直接就拿atomicInteger 进行累加了。直到atomicInteger大于max ,这个时候再重复执行一次步骤1。
这样大部分的生成id都是在leaf服务中完成的,只有很少请求是在数据库完成的,而且step数值越大,对mysql压力就会越小,因为都是在leaf服务中完成的自增长。而且leaf 支持集群部署,mysql行锁保证多leaf实例更新同一条数据不会出现问题,同时扩展由mysql 转向了leaf服务,保证高并发,多数leaf服务内存完成id生成,保证高性能,集群部署,保证高可用。
这里也存在几个问题 :
- 当id生成服务id号段用光了,去mysql 中申请号段慢的问题,会造成一卡一卡的感觉。
- 这种预占号段,一旦id生成服务挂了/停了,再起来的时候,号段就丢了(毕竟是放在服务内存中的),而且是永久丢了。
- 强依赖数据库,数据库单机问题,瓶颈问题。
解决:
- 第一个问题解决方案就是 在id 生成服务中 对每个号段生成一个阈值,一旦号段使用到了这个阈值,就起个后台线程将下个号段先申请过来,这也就是双buffer 缓冲机制,不管是美团的leaf 还是滴滴的tinyid 都用了这种机制,保证高并发,高性能。
还有就是 增加id生成服务实例数量, 加大step ,减少申请号段次数, 你由原来一次申请1000个id 变成一次申请10000个id ,减少db交互。 - 第二个问题 号段丢了,这个说实话,也没办法,不能保证完全连续,关键完全连续也没啥用。而且号段模式只是趋势递增的。
- 第三个问题 ,如果是性能瓶颈的话,还好说,拿biz 这个字段进行分库分表。 单机问题的话,只能就是主从了。
总结
leaf基于服务内存的增长,保证了高性能,基于mysql的行锁,可实现多leaf服务并发修改同一条数据场景下不会出现问题,支持集群部署,保证高并发,同时扩展由mysql转向服务本身,保证高扩展性,只能说巧妙,要说它的缺点,就是leaf服务宕机,会出现id不连续的情况,某段id丢失,集群部署获取id的时候,同一时刻会出现忽大忽小的情况,但是整体是呈现增长趋势的。
转载自:
https://inetyoung.blog.csdn.net/article/details/118423254
https://inetyoung.blog.csdn.net/article/details/119935951