基于乐观锁实现的美团Leaf Segment的唯一ID生成器

背景:

分布式唯一ID主键几乎是每个应用都需要的一个功能,不论是生成订单还是为了表示记录的唯一性,都要使用到唯一ID的服务类,那么生成唯一ID除了常见的UUID,数据库自增主键生成外,还有哪些可选呢?本文就来介绍下基于乐观锁实现的美团的Leaf Segment的分布式唯一主键生成算法

基于乐观锁实现的Leaf Segment分布式唯一ID生成器:

这种算法对数据库自增主键时对数据库太过依赖和每次获取自增ID都要读写DB的性能损耗的优化,该算法可以容许短时间内的DB网络波动,并且其性能几乎不会受到DB IO性能的影响,那么下面我们就来看一下基于"段号分段"实现的Leaf Segment算法实现,其大概思路是首先从数据库中取出一段的段号放到内存中,比如第一次获取[0,1000)的段号到内存中进行ID的分配,当分配到1000的时候会向数据库中在申请[1000,2000)的段号进行分配,所以分配ID几乎都是内存中的操作,性能非常好,下面我们来看下如何实现:

一.创建数据库表
CREATE TABLE `uniq_id` (
  `biz_tag` varchar(100) DEFAULT '0' COMMENT '业务',
  `max_id` bigint(20) DEFAULT '0' COMMENT '当前已经使用的最大的号ID',
  `step` int(11) DEFAULT '0' COMMENT '当前已经使用的最大的号ID',
  Unique KEY (biz_tag)
) ENGINE=InnoDB AUTO_INCREMENT=1;

数据表中重要的字段包括:

a. biz_tag表示不同的业务标记,比如订单域和用户域分别是两个不同的标识

b.max_id当前业务已经获取到的最大的段号,比如2000,表示订单域或者是用户域已经分配到了2000这个ID值了

c. step表示每个业务每次获取的段号的范围,比如100,表示业务每次获取100个段号到内存中进行分配,一直到这100个段号分配完之前,不会再次和DB通信申请新的段号范围

二.多线程更新数据库字段时保证数据库更新的原子性

我们再向数据库申请段号范围时都是使用多线程的形式,那么我们如何保证多个线程拿到的段号不会重复呢?比如会不会把数据库的[2000,2100)这个段号范围重复给了不同的线程从而导致ID重复呢?如果我们在更新数据库时没有采取任何措施的话是可能发生这种情况的,那么如何防止这种情况的发生?我们可以使用DB的乐观锁来实现原子更新:

update uniq_id set max_id=max_id + step where biz_tag=#{biz_tag} and max_id=#{old_max_id}

只有当max_id和更新db前取出来的max_id一样时才会更新db的记录,否则就循环重试,通过这种乐观锁的更新控制机制,就可以保证不会出现分配相同的ID段号的情况

三.如何应付DB的抖动?

如果我们按照内存中分批完Db给的段号范围之后才去DB获取下一个段号范围的形式的话,有可能此时DB抖动的话,此时所有的线程都会卡主等待获取段号范围的线程的返回以便可以获取到唯一ID,然而此时获取段号范围的线程由于DB抖动,一直没法获取到段号范围,这就导致了整个应用的阻塞,造成严重影响,那么我们怎么避免这个问题呢?也就是我们怎么应付DB偶尔的抖动呢?

我们可以参照Hbase中双MemStore的实现,也就是维护在内存中维护两个不同的Segment结构,当第一个Segment中ID的分配已经达到比如10%时,就开启异步线程去获取新的段号更新第二个Segment,这样当第一个Segment的ID分配完后,直接就可以切换到第二个Segment继续分配了,不需要再去DB获取段号范围了,通过异步去更新另一个Segment段号范围的形式循环使用这两个Segment,这样就可以轻松的应付DB的抖动了

参考文献:

https://tech.meituan.com/2017/04/21/mt-leaf.html

https://juejin.cn/post/7191431147593662523

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
美团leaf(全局唯一ID生成服务)是一种分布式ID生成算法,旨在解决分布式系统中生成全局唯一ID的需求。它的设计目标是高效、高可用、趋势递增且趋势递增性能好。 leaf算法的核心思想是将全局ID空间划分为不同的区域,每个区域内包含多个段(Segment),每个段都有一个起始值和一个结束值。每次需要生成ID时,从对应的段中取出一个ID,然后将段的当前值加上一个步长,作为下一个段的起始值。 具体来说,leaf算法的实现有以下几个关键步骤: 1. 初始化:在启动时,leaf服务会加载配置信息,并初始化每个区域内的段。每个段都有一个起始值和一个结束值,初始时起始值为0,结束值为步长(默认为10000)。 2. 生成ID:当需要生成一个新的ID时,leaf服务会根据请求中指定的区域号选择对应的区域,然后从该区域内的段中获取一个ID。获取ID的过程如下: - 如果当前段的可用ID已用尽,则需要从下一个段中获取新的起始值,并将当前段的结束值更新为新的起始值加上步长。 - 如果下一个段不存在,或者获取下一个段出现异常,则需要重新初始化该区域内的所有段。 - 返回当前段的起始值作为生成的ID。 3. 高可用性:leaf服务采用多节点部署,通过一个中心化的配置中心来协调各个节点之间的状态和配置信息。当某个节点发生故障或者需要扩容时,其他节点能够接管该节点的工作,确保服务的高可用性。 总体而言,leaf算法通过将全局ID空间划分为多个段,每个段都有一个起始值和一个结束值,通过段之间的切换来实现高效地生成全局唯一ID。它具有高可用性、趋势递增且趋势递增性能好等特点,适用于大规模分布式系统中需要生成全局唯一ID的场景。 这是对leaf算法的简要介绍。如果您对leaf算法还有其他问题,欢迎继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值