• 美团分布式 ID 框架 Leaf 介绍和使用


    一、Leaf

    在当今日益数字化的世界里,软件系统的开发已经成为了几乎所有行业的核心。然而,随着应用程序的规模不断扩大,以及对性能和可扩展性的需求不断增加,传统的软件架构和设计模式也在不断地面临挑战。其中一个主要挑战就是如何有效地处理分布式环境中的唯一标识问题。这正是分布式ID 的重要性所在。

    分布式ID的实现方式有多种多样,常见的包括 UUIDSnowflake 算法、TwitterSnowflake 算法、基于数据库的自增长ID 等。每种方式都有其适用的场景和优缺点。

    比如常见的 UUID , 标准型式包含3216进制数字,以连字号分为五段,形式为8-4-4-4-1236个字符,优点是性能非常高,本地生成,没有网络消耗,但缺点也显而易见,首先不易于存储,UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。其次信息不安全,基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。也不适合作为DB的主键。MySQL官方有明确的建议主键要尽量越短越好。

    基于数据库的自增长ID 的方式,实现起来非常简单,并且ID是单向自增顺序的,但缺点也很明显,过度依赖于 DB 数据库,在并发量高的情况下数据库成为了性能瓶颈。

    基于Snowflake 算法的方式,可以解决上述提到的问题,并且稳定性和灵活性都非常高,但强依赖于机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

    既然如此,那下面我们来认识更强大的分布式ID生成器 Leaf ,它是美团开源的分布式 ID 生成器,旨在解决分布式系统中的唯一标识生成问题,确保在分布式环境下生成的 ID 具有全局唯一性、顺序性和高性能。

    Leaf 实现了Leaf-segmentLeaf-snowflake两种方案。

    Leaf-segment是一种基于数据库的分布式 ID 生成方案,原始基于数据库的自增长ID 方案,每次获取ID都得读写一次数据库,造成数据库压力大,该方案利用proxy server批量获取,每次获取一个segment(step决定大小)号段的值。用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力。各个业务不同的发号需求用biz_tag字段来区分,每个biz-tagID获取相互隔离,互不影响。如果以后有性能需求需要对数据库扩容,不需要上述描述的复杂的扩容操作,只需要对biz_tag分库分表就行。

    Leaf-snowflake方案完全沿用snowflake方案的bit位设计,对于workerID的分配,使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID,对于时钟回拨问题,解决方案如下:

    在这里插入图片描述

    更多介绍可以参考官方信息:

    官方介绍地址:https://tech.meituan.com/2017/04/21/mt-leaf.html

    github:https://github.com/Meituan-Dianping/Leaf.git

    下面一起来实践下Leaf的使用。

    首先拉取 Leaf SpringBoot 封装依赖源码:

    git clone -b feature/spring-boot-starter https://github.com/Meituan-Dianping/Leaf.git
    
    • 1
    cd leaf
    
    • 1

    使用 MavenLeaf 打到本地仓库中

    mvn clean install -Dmaven.test.skip=true 
    
    • 1

    在这里插入图片描述

    打包成功后,可以创建一个 SpringBoot 项目,在 pom 中加入下面依赖:

    <dependency>
         <artifactId>leaf-boot-starterartifactId>
         <groupId>com.sankuai.inf.leafgroupId>
         <version>1.0.1-RELEASEversion>
         <exclusions>
             <exclusion>
                 <groupId>com.alibabagroupId>
                 <artifactId>druidartifactId>
             exclusion>
             <exclusion>
                 <groupId>mysqlgroupId>
                 <artifactId>mysql-connector-javaartifactId>
             exclusion>
         exclusions>
     dependency>
    
     <dependency>
         <groupId>com.alibabagroupId>
         <artifactId>druidartifactId>
         <version>1.1.6version>
     dependency>
    
     <dependency>
         <groupId>mysqlgroupId>
         <artifactId>mysql-connector-javaartifactId>
     dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    二、Leaf-segment 方式使用

    首先创建leaf使用的数据库:

    CREATE DATABASE leaf
    
    • 1

    创建ID规则表:

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    写入两个 biz_tag

    insert into leaf_alloc(biz_tag, max_id, step, description) values('test1', 1, 2000, '测试1');
    insert into leaf_alloc(biz_tag, max_id, step, description) values('test2', 1, 2000, '测试2');
    
    • 1
    • 2

    项目中加入leaf 和数据库配置:

    leaf:
      name: test1
      segment:
        enable: true
        url: jdbc:mysql://localhost:3306/leaf?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
        username: root
        password: root
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    生成ID测试:

    @Slf4j
    @SpringBootTest
    class LeafIdApplicationTests {
    
        @Resource
        private SegmentService segmentService;
    
        @Test
        void contextLoads() {
            // 生成 1000 个ID
            StopWatch sw = new StopWatch();
            sw.start();
            for (int i = 0; i < 1000; i++) {
                long id1 = segmentService.getId("test1").getId();
                long id2 = segmentService.getId("test2").getId();
                log.info("id1: {}, id2: {}", id1, id2);
            }
            sw.stop();
            log.info(sw.prettyPrint());
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述
    可以看到在约 0.178 秒的时间,为两个业务场景生成了 1000ID

    三、Leaf-snowflake 方式使用

    这种模式依赖于 Zookeeper ,所以在实验前你需要有一个运行中的 Zookeeper 服务。

    这种模式操作ZK使用 curator,因此需要引入 curator 的依赖:

    <dependency>
        <groupId>org.apache.curatorgroupId>
        <artifactId>curator-recipesartifactId>
        <version>2.12.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在配置文件中开启Leaf-snowflake 模式:

    leaf:
      name: test1
      segment:
        enable: true
        url: jdbc:mysql://localhost:3306/leaf?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
        username: root
        password: root
      snowflake:
        enable: true
        address: 127.0.0.1
        port: 2181
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    生成ID测试:

    @Slf4j
    @SpringBootTest
    class LeafIdApplicationTests {
    
        @Resource
        private SegmentService segmentService;
    
        @Resource
        private SnowflakeService snowflakeService;
    
        @Test
        void contextLoads() {
            // 生成 1000 个ID
            StopWatch sw = new StopWatch();
            sw.start();
            for (int i = 0; i < 1000; i++) {
                long id1 = snowflakeService.getId("test1").getId();
                long id2 = snowflakeService.getId("test2").getId();
                log.info("id1: {}, id2: {}", id1, id2);
            }
            sw.stop();
            log.info(sw.prettyPrint());
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    可以看到相比于上面数据库模式,仅需要约 0.0234105 秒,性能更高,而且做到ID不是顺序+1式增长。

  • 相关阅读:
    23种设计模式之桥接模式
    剑客决斗算法课设分享(三种方法)
    深度学习技巧应用29-软件设计模式与神经网络巧妙结合,如何快速记忆软件设计模式
    使用kaliber与imu_utils进行IMU、相机+IMU联合标定
    国产操作系统安装之openEuler-22.03-LTS
    Day739.GEO经纬度数据结构&自定义数据结构 -Redis 核心技术与实战
    区块链概念集合
    QT点击事情实现图片切换
    使用Scanner类进行控制台输入
    2022ICPC 网络赛第二场 B Non-decreasing Array(区间dp)
  • 原文地址:https://blog.csdn.net/qq_43692950/article/details/136374892