首页 > 其他分享 >Leaf-美团的分布式ID生成器

Leaf-美团的分布式ID生成器

时间:2023-10-02 16:12:47浏览次数:45  
标签:Leaf 美团 生成器 druid snowflake leaf 1.0 segment ID

简介

在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数据库的自增ID显然不能满足需求;特别一点的如订单、骑手、优惠券也都需要有唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。概括下来,那业务系统对ID号的要求有哪些呢?

  1. 全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
  2. 趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。
  3. 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
  4. 信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。

上述123对应三类不同的场景,3和4需求还是互斥的,无法使用同一个方案满足。

同时除了对ID号码自身的要求,业务还对ID号生成系统的可用性要求极高,想象一下,如果ID生成系统瘫痪,整个美团点评支付、优惠券发券、骑手派单等关键动作都无法执行,这就会带来一场灾难。

由此总结下一个ID生成系统应该做到如下几点:

  • 平均延迟和TP999延迟都要尽可能低;
  • 可用性5个9;
  • 高QPS。

常见方法介绍

UUID

UUID(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例:550e8400-e29b-41d4-a716-446655440000,到目前为止业界一共有5种方式生成UUID,详情见IETF发布的UUID规范 A Universally Unique IDentifier (UUID) URN Namespace

优点:

  • 性能非常高:本地生成,没有网络消耗。
    缺点:
  • 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
  • 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。
  • ID作为主键时在特定的环境会存在一些问题,比如做DB主键的场景下,UUID就非常不适用:
    1. MySQL官方有明确的建议主键要尽量越短越好,36个字符长度的UUID不符合要求。
    2. 对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能。

类snowflake方案

这种方案大致来说是一种以划分命名空间(UUID也算,由于比较常见,所以单独分析)来生成ID的一种算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等,比如在snowflake中的64-bit分别表示如下图(图片来自网络)所示:

image

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,理论上snowflake方案的QPS约为409.6w/s,这种分配方式可以保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不同的。

这种方式的优缺点是:

优点:

  • 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
  • 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
  • 可以根据自身业务特性分配bit位,非常灵活。

缺点:

  • 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

MongoDB官方文档 ObjectID可以算作是和snowflake类似方法,通过“时间+机器码+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;

image

这种方案的优缺点如下:

优点:

  • 非常简单,利用现有数据库系统的功能实现,成本小,有DBA专业维护。
  • ID号单调自增,可以实现一些对ID有特殊要求的业务。

缺点:

  • 强依赖DB,当DB异常时整个系统不可用,属于致命问题。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。
  • ID发号性能瓶颈限制在单台MySQL的读写性能。

Leaf特性

Leaf在设计之初就秉承着几点要求:

  • 全局唯一,绝对不会出现重复的ID,且ID整体趋势递增。
  • 高可用,服务完全基于分布式架构,即使MySQL宕机,也能容忍一段时间的数据库不可用。
  • 高并发低延时,在CentOS 4C8G的虚拟机上,远程调用QPS可达5W+,TP99在1ms内。
  • 接入简单,直接通过公司RPC服务或者HTTP调用即可接入。

Leaf开源版本提供了两种ID的生成方式:

  • 号段模式:低位趋势增长,较少的ID号段浪费,能够容忍MySQL的短时间不可用。
  • Snowflake模式:完全分布式,ID有语义。

使用docker部署

源代码地址
支持springboot自动配置的代码分支

数据库初始化

CREATE DATABASE leaf
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;

insert into leaf_alloc(biz_tag, max_id, step, description) values('leaf-segment-test', 1, 2000, 'Test leaf Segment Mode Get Id')

仅开启snowflake

docker pull uid1024/leaf-rest:1.0.1
docker run -d --name leaf-rest \
-p 8089:80  \
uid1024/leaf-rest:1.0.1 \
--leaf.snowflake.address=42.192.20.119 \
--leaf.snowflake.port=2181

api路径为 http://42.192.20.119:8089/api/snowflake/get/{key}http://42.192.20.119:8089/api/snowflake/get/test

snowflake和segment都开启

docker pull uid1024/leaf-rest:1.0.1
docker run -d --name leaf-rest \
-p 8089:80  \
uid1024/leaf-rest:1.0.1 \
--leaf.snowflake.address=42.192.20.119 \
--leaf.snowflake.port=2181 \
--leaf.segment.enable=true \
--leaf.segment.url="jdbc:mysql://42.192.20.119:3310/leaf?characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai" \
--leaf.segment.username=root \
--leaf.segment.password=abdyou000

配置zookeeper地址和数据库地址。参数必须在image名称之后,这是因为image的Dockerfile为

FROM openjdk:8-jre-slim

EXPOSE 80

ADD target/docker-leaf-1.0.1-RELEASE.jar /tmp/docker-leaf-1.0.1-RELEASE.jar
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -jar /tmp/docker-leaf-1.0.1-RELEASE.jar "$0" "$@"

$0 表示命令本身,$@ 为所有参数

http://42.192.20.119:8089/api/snowflake/get/test 访问出错,docker错误日志为

java.sql.SQLException: validateConnection false
        at com.alibaba.druid.pool.DruidAbstractDataSource.validateConnection(DruidAbstractDataSource.java:1249) ~[druid-1.0.18.jar!/:1.0.18]
        at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1474) ~[druid-1.0.18.jar!/:1.0.18]
        at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:1969) ~[druid-1.0.18.jar!/:1.0.18]

原因是使用的druid数据库连接池版本太低,而代码中没有设置com.alibaba.druid.pool.DruidAbstractDataSource#validationQuery字段,所以报错。

自定义实现

项目源码中leaf.properties的结构和com.sankuai.inf.leaf.plugin.LeafSpringBootProperties的结构是不符的。

leaf.name=com.sankuai.leaf.opensource.test
leaf.segment.enable=false
leaf.jdbc.url=    #应该是leaf.segment.url 后面的同理
leaf.jdbc.username=
leaf.jdbc.password=

leaf.snowflake.enable=false
#leaf.snowflake.address=
#leaf.snowflake.port=

下载依赖

<dependency>
   <groupId>com.sankuai.inf.leaf</groupId>
   <artifactId>leaf-core</artifactId>
   <version>1.0.1-RELEASE</version>
 </dependency>

官方没有提供maven依赖

<dependency>
   <groupId>com.tencent.devops.leaf</groupId>
   <artifactId>leaf-core</artifactId>
   <version>1.0.2-RELEASE</version>
</dependency>

这个应该是腾讯提供的一个依赖,但com.tencent.devops.leaf.segment.dao.IDAllocMapper中使用的表有的大写,有的小写,运行时报错(表不存在)。最后只能将源码和maven依赖拷贝到自己的项目中,项目地址,也会出现druid版本过低的问题,可以手动设置com.alibaba.druid.pool.DruidAbstractDataSource#validationQuery字段的值或者修改druid的版本。

使用

参考

Leaf——美团点评分布式ID生成系统
Leaf:美团分布式ID生成服务开源
美团Leaf分布式ID,rest 服务-docker镜像仓库
如何向Docker容器传递参数
对mysql中last_insert_id()的新理解

标签:Leaf,美团,生成器,druid,snowflake,leaf,1.0,segment,ID
From: https://www.cnblogs.com/strongmore/p/17205162.html

相关文章

  • 可迭代、迭代器、生成器
    可迭代可以通过isinstance(object,Itreable)判断也可以通过如下方式判断dir()方法,如果有实现__iter__就说明是可迭代的当然也可能只是闲了__getitem__方法,尝试按顺序获取元素,也不会抛出异常因此,我们可以通过能不能用for去迭代或者使用iter()来尝试生成迭代器来......
  • 免费 AI 代码生成器 Amazon CodeWhisperer 初体验
    文章作者:浪里行舟简介随着ChatGPT的到来,不由让很多程序员感到恐慌。虽然我们阻止不了AI时代到来,但是我们可以跟随AI的脚步,近期我发现了一个神仙AI代码生产工具CodeWhisperer,它是一项基于机器学习的服务,其根据自然语言注释和集成开发环境(IDE)中的代码,生成代码建议,帮助......
  • 结对项目-Java实现四则运算题目生成器
    软件工程21级计科1班作业要求结对项目作业目标了解结对合作开发流程,提高合作开发效率项目成员姓名学号江卓颖3121004699詹慧丹3221004855Github地址github仓库链接PSP表PSP2.1PersonalSoftwareProcessStages预估耗时(分钟)实际耗......
  • 小学四则运算题目生成器
    这个作业属于哪个课程软件工程这个作业要求在哪里在这里这个作业的目标写个小学四则运算题目生成器,学会与同伴合作做项目陈威衡3121005206郑贵南3121005237github连接需求分析表达式中的分数都用真分数的形式表示生成的表达式里只包含四则运算和括号每......
  • Biwen.QuickApi代码生成器功能上线
    [QuickApi("hello/world")]publicclassMyApi:BaseQuickApi<Req,Rsp>{}使用方式:dotnetaddpackageBiwen.QuickApidotnetaddpackageBiwen.QuickApi.SourceGenerator最后简简单单注册路由:app.MapGenQuickApis("api");Biwen.QuickApi使用REPR......
  • Java函数AI生成器:秒级体验,高质量生成
    职业使然,之前我自己试用并给小伙伴们推荐过不少大模型相关的产品,这些产品看起来牛逼plus,但是它们要么对国内有限制,要么是申请制/邀请制,自己没个资想搞个账号去体验一下都难如登天,忍不住吐槽某大厂虽然早早的对外宣布面向全民开发,但实际上提交了手机号码还在等待排队上车,掐指一算,有......
  • Java实现四则运算生成器
    这个作业属于哪个课程计科二班这个作业要求在哪里结对项目这个作业的目标熟悉结对编程项目成员龙新超3121004921github链接:龙新超github艾孜海尔江3121004900github链接:海尔江githubPSP表格PSP2.1PersonalSoftwareProcessStages预估耗时(分钟)实......
  • Python高频面试题——生成器(最通俗的讲解)
    生成器定义在Python中,使用了yield的函数被称为生成器(generator)。跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。在调用生成器运行的过程中,每次遇到yield时函数会暂停并保存当前所有的运行信息,返回yield的值,并在......
  • RAS非对称加解密-RAS加解密和签名和验签,密钥生成器(java代码)
    RAS非对称加解密-RAS加解密和签名和验签,密钥生成器(java代码)RSA算法是一种非对称加解密算法。服务方生成一对RSA密钥,即公钥+私钥,将公钥提供给调用方,调用方使用公钥对数据进行加密后,服务方根据私钥进行解密。1.RAS密钥生成器2.RAS加解密和签名和验签代码13.RAS实现签名......
  • 直播软件开发,随机密码生成器
    直播软件开发,随机密码生成器方法调用 publicstaticvoidmain(String[]args){    //排除字符0OoB81lI,包含大写字母,包含小写字母,包含数字,包含特殊字符,长度8,生成10000个,特殊字符集    generatePassword("0OoB81lI",true,true,true,true,8,10000,"~!@^*%");......