概述

分布式id生成已经有业界较为成熟的方案。现在公司使用的是美团的Leaf的号码段模式。之所以不用雪花算法模式还是因为雪花算法的自身缺陷,即时间回拨问题。本文就从源码角度剖析leaf项目的两种id生成模式。

Leaf这种分布式id生成系统是美团自研发,具有全局唯一、高可用、高明发、低延迟、接入灵活(支持http、rpc)等优点。

之前我们做全局唯一id的时候使用过雪花算法和UUID.这两者的缺点很明显。

1、雪花算法无法解决始终回拨,当然leaf的雪花算法一定程度上克服了这一个缺点。

2、UUID算法,uuid的值结构不够友好,杂乱无章,对mysql这种需要顺序id的情况不够友好。

tips:Leaf的性能在4C8G的机器上QPS能压测到近5万/s,TP999 1ms

看完本文,您能够收获:

  • 代码级别的理解两种生成模式
  • 两种模式的优缺点。

主要内容

1、 号段模式

1.1、号码段模式的概念

Leaf项目的号码段模式强依赖DB,在数据库上添加了一个库表。结构如下。

CREATE TABLE `leaf_alloc` (`biz_tag` varchar(128) NOT NULL DEFAULT '',`max_id` bigint(20) NOT NULL DEFAULT 1,`step` int(11) NOT NULL,`description` varchar(256) DEFAULT NULL,`update_time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

基于这个数据库表,我们要在leaf下配置两个参数:

leaf.name=zjtest
leaf.zk.list=172.1X.48.XX

号码段的含义大体就是各个服务实例在请求leaf项目的时候leaf会加载一批号码加载到leaf当中。leaf项目维护了以下结构。

每一个biz_tag都会被包装成SegmentBuffer结构,而每一个SegementBuffer里面包含两个Segment.Segment也叫双Buffer模式。当前使用一个Buffer,如果当前Buffer使用到某个阈值就会开启一个线程,该线程请求数据库请求一批号码段缓存到新的Buffer当中。双Buffer能够解决号码耗尽加载数据库造成业务卡顿的现象。

除了双Buffer优化,号码段模式还对缓存的号码多少做了优化。具体的根据更新时间感知当前leaf服务的请求频繁程度调整每次请求个数。根据上一次的更新周期T和号段长度step来决定这一次更新的号段长度,T<15分钟则step=step*2,15<T<30,step不变,t>30则step=step/2。

1.2代码步骤

1.2.1、初始化阶段

1、获取库表中所有的biz_tag字段集合。

2、将其保存在内存insertTagsSet中。并将tag为key,包装成Segment放入缓存当中。

3、启动定时任务updateCacheFromDb,每隔1分钟执行一次

3.1、获取库表中所有的biz_tag字段集合。

3.2、将其保存在内存insertTagsSet中。并将tag为key,包装成Segment放入缓存当中。

1.2.2、获取号码节点

1、判断初始化是否成功,不成功抛异常。

2、判断缓存是否有该请求key。如果没有该key则抛异常

3、如果有该key则执行以下步骤

        初次请求:从库表中缓存号码段缓存

3.1、更新库表该key的maxId,用step值给maxId增加一个step

3.2、将结果返回到内存,更新号码段缓存SegmentBuffer

        再次请求:从本地缓存的号码段获取值

3.1、为buffer加读锁。

3.2、根据状态确认是否添加一个线程从db中获取新号段

3.3、释放读锁

3.4、为buffer再加写锁

3.5、获取segment的value字段,并+1,如果小于segment的max字段,则将value作为结果返回。

3.6、释放写锁

在从号码段缓存获取号码的过程中,每次都在判断以下几个状态

1、nextReady=false。如果这样说明本地没有可用号码段

2、segment的剩余号码量是否小于一个step的90%,说明这个剩余号码量不足了

3、获取该SegmentBuffer的threadRunning=false,如果为true说明该号码段有线程已经执行,不能重复启动线程。如果为false说明该SegmentBuffer可以启动一个线程。

如果满足以上三个条件,则启动一个线程。该线程逻辑如下:

1、获取双buffer中没有使用的segment 的下标。

2、更新数据库最大值,让然是当前max+1个step

3、然后将结果犯规给步骤1获得的闲置segment当中。

4、设置buffer的nextReady=true

5、threadRunning=false

1.2.3、优点

  • leaf的segment方式可以很方便的进行水平扩展,轻松支撑比较大的业务场景。
  • id号码趋势是递增的,对mysql的主键兼容性较好。
  • 号段都缓存到了本地一部分,在本地号段消耗完毕之前,如果db挂了还可以继续提供服务。
  • 从源码我们看出每一个服务器的初始值都是max_id开始的一段,所以如果迁移老业务,可以方面用该特性调整max_id的值,平滑迁移db行数据。

1.2.4、缺点

  • ID 号码不够随机,能够泄露发号数量的信息,不太安全。
  • DB 长时间宕机会造成整个系统不可用。

2、SNOWFLAKE 模式

2.1、SNOWFLAKE 模式的概念

leaf中在普通的雪花算法上做了优化,对少量的时钟回拨做了兼容。同时workId字段是通过zk上维护。同时snowFlake模式对wokId缓存到了本地内存,如果zk宕机也会正常使用,对zk形成了弱依赖。

2.2、初始化阶段

1、连接zk

2、获取/forever节点,判断其是否存在

2.1、如果不存在,

2.1.1、创建/forever/ip:port节点,并在其上写入数据(Endpoint对象),包括:ip,port和当前时间撮。

2.1.2、在本地创建文件workerID.properties,并写入数据workerID=0

2.1.3、在线程池(schedule-upload-time)设置一个周期任务,每三秒钟同步时间到2.1创建的节点下。

2.2、如果存在

2.2.1、远程获取zk的目录/forever下所有数据

2.2.2、从zk上获取取出本实例注册的workid,如果存在

则:

1、通过2.2.2获取的workId,更新本地workId信息.

2、检查从zk获取的时间撮是否小于当前时间。

2.1、如果不是,抛异常。服务无法启动

3、启动周期任务,每三秒钟同步时间到zk.

4、将远程获取的workId同步到本地文件workerID.properties中。

2.2.2、从zk上获取取出本实例注册的workid,如果不存在

1.创建/forever/ip:port节点到zk,返回/sonwflake/${leaf.name}/snowflake/zjtest/forever/10.8.0.132:2181-0000000000

2、获取远程workId(workId>=0 && workId<=2013)

3、启动周期任务,每三秒钟同步时间到zk.

4、将远程获取的workId同步到本地文件workerID.properties中。

2.3、获取id阶段(初次获取)

1、获取当前时间

2、如果是新开始,从1-100随机获取一个值(例如27)

3、lastTimestamp字段更新为当前时间

4、包装id

long id = ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence;

举个例子1584855141251547163 (19位)

5、包装成Result返回给业务。

2.4、获取id阶段(并发获取)

1、如果lastTimestamp和当前时间相同,说明发生并发获取

2、并发度为4095,在这个范围内获取上一次sequence,并+1

3、如果获取sequence==0,

3.1、调整sequence为1-100之内随机数

3.2、时间撮调整为lastTimestamp之后的时间。

获取id阶段(时间回拨)

1、比较当前时间和上一次获取时间

2、如果当前时间比上一次获取时间小,并且在5秒范围内

2.1、等待一倍的时间

2.2、如果仍然小于上一次获取时间抛异常

3、如果回拨严重,直接抛异常。 new Result(-3, Status.EXCEPTION);

2.5、特点:

存内存算法

单台机器并发为4096

发生时钟回拨的时候,代码手动调整了时间,如果时间差异小于5秒,则代码自动阻塞了最大10秒的时间。如果回拨严重会给业务直接抛异常。

总结

leaf的两个分布式生成id模式各有自己的优缺点,在选择的时候可以根据实际情况去选择。

segment模式依赖数据库,雪花算法需要引入zk。

segment模式是缓存号码段,号码的顺序性强,雪花算法直接计算号码,号码顺序性不强。

segment模式使用了很多优化手段例如双buffer做预缓存,加载时间调整缓存批次中号码数量;雪花模式在雪花算法的基础上做了兼容少量时间回拨问题。

Leaf-美团分布式id生成系统相关推荐

  1. 美团开源分布式ID生成系统——Leaf源码阅读笔记(Leaf的号段模式)

    Leaf 最早期需求是各个业务线的订单ID生成需求.在美团早期,有的业务直接通过DB自增的方式生成ID,有的业务通过redis缓存来生成ID,也有的业务直接用UUID这种方式来生成ID.以上的方式各自 ...

  2. Leaf:美团分布式ID生成服务开源

    Leaf是美团基础研发平台推出的一个分布式ID生成服务,名字取自德国哲学家.数学家莱布尼茨的一句话:"There are no two identical leaves in the wor ...

  3. Leaf:美团分布式ID生成服务开源 1

    Leaf是美团基础研发平台推出的一个分布式ID生成服务,名字取自德国哲学家.数学家莱布尼茨的一句话:"There are no two identical leaves in the wor ...

  4. 分布式 ID 生成系统 Leaf 的设计思路,源码解读

    什么是分布式ID? ID 最大的特点是 唯一 而分布式 ID,就是指分布式系统下的 ID,它是 全局唯一 的. 为啥需要分布式ID呢? 这就和 唯一 息息相关了. 比如我们用 MySQL 存储数据,一 ...

  5. 带你了解「美团、百度和滴滴」的分布式 ID 生成系统

    文章目录 美团 背景 常见方法介绍 UUID 类snowflake方案 数据库生成 Leaf 方案实现 Leaf-segment 数据库方案 双 buffer 优化 Leaf 高可用容灾 Leaf-s ...

  6. 滴滴开源的分布式id生成系统

    ID Generator id生成器 分布式id生成系统,简单易用.高性能.高可用的id生成系统 简介 Tinyid是用Java开发的一款分布式id生成系统,基于数据库号段算法实现,关于这个算法可以参 ...

  7. 在 .NET6.0 中实现 Leaf-segment 分布式 ID 生成系统

    背景 分布式 ID (Distributed ID)是指在分布式系统中,为了保证全局唯一性而生成的一类 ID.在分布式系统中,由于存在多个节点同时生成 ID 的情况,因此需要采用一定的算法来保证生成的 ...

  8. 【阅读】Leaf——美团点评分布式ID生成系统

    文章目录 背景 常见实现方案 UUID 优势 缺点 应用 类SnowFlake方案 优点 缺点 应用 数据库生成 优点 缺点 MySQL 增强版 优点 缺点 Leaf -segment方案 优点 缺点 ...

  9. Leaf——美团点评分布式ID生成系统

    背景 在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识.如在美团点评的金融.支付.餐饮.酒店.猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数 ...

最新文章

  1. Python 动态载入模块
  2. 常量池在堆还是方法区_第九章_方法区
  3. 主机路由在计算机中的应用
  4. 第5章 MySQL高可用架构设计
  5. linux mysql dump命令_linux下mysql命令
  6. python win7 win10_Python如何获取Win7,Win10系统缩放大小
  7. 分析了 200w 行 OpenHarmony 2.0 源码后,有了这些发现
  8. python实现:用类实现一个图书馆,实现借书,入库,还书,查书,等功能,要求数据可以保存到文件中,退出后下次可以找回数据...
  9. matplotlib绘图(折线图,直方图,柱状图,饼图,散点图,三维,动图)
  10. 图像识别与人工智能的联系
  11. 一周新闻纵览:谷歌浏览器信息泄露,出卖个人信息获利终落网,严查App偷窥乱象
  12. KK录像机怎么用?KK录像机使用方法介绍
  13. mean函数 median函数【Matlab】
  14. 第二章 存储器层次结构
  15. 关于页眉的奇偶页不同设置和奇数页页眉展示每一章名称的设置方法
  16. 人宅系列课程年度总结
  17. CIO40知识星球—5年工程师升职IT主管(22-27岁)
  18. 手机与电脑局域网内数据互通
  19. Linux常用快捷键、文件管理和查询
  20. 面向工业智能制造的组态系统设计思路与实现

热门文章

  1. [*****]我的联系方式
  2. 防火墙策略梳理思路及前沿想法
  3. 计算10的阶乘(10!)
  4. 计算机三级考试嵌入式系统重点精简汇总
  5. 【Python】货币转换 I
  6. C++课程设计《最短路径》
  7. Android短视频播放器组件库GSYVideoPlayer和仿抖音切换DKVideoPlayer可以悬浮框及滑动小屏播放
  8. mysql安装教程详细步骤
  9. Allegro PCB放置mark点
  10. 如何使用Arduino开发板连接PS2无线控制器