分布式ID

什么是分布式 ID

在分布式系统中,经常需要一些全局唯一的 ID 对数据、消息、http 请求等进行唯一标识。那么这个全局唯一 ID 就叫分布式 ID

为什么需要分布式 ID

如果 id 我们使用的是数据库的自增长类型,在分布式系统中需要分库和分表时,会有两个相同的表,有可能产生主键冲突,电商订单号,采用自增方式,是最简单的生成规则。但是!这种与流水号相同的订单号很容易就被竞争对手看出你公司真实的运营信息

分布式 ID 特点

  • 全局唯一
  • 高性能
  • 高可用

常见分布式 ID 解决方案

时间戳

在高并发时,可能会产生冲突

UUID

生成足够简单,本地生成无网络消耗,具有唯一性,缺点:无序的字符串,不具备趋势自增特性,没有具体的业务含义,长度过长 16 字节 128 位,36 位长度的字符串,存储以及查询对 MySQL 的性能消耗较大,MySQL 官方明确建议主键要尽量越短越好,作为数据库主键 UUID 的无序性会导致数据位置频繁变动,严重影响性能

数据库自增 ID

实现简单,ID 单调自增,数值类型查询速度快,缺点:DB 单点存在宕机风险,无法扛住高并发场景

数据库的号段模式

号段模式是当下分布式 ID 生成器的主流实现方式之一,号段模式可以理解为从数据库批量的获取自增 ID,每次从数据库取出一个号段范围,例如(1,1000),代表 1000 个 ID,具体的业务服务将本号段,生成 1 ~ 1000 的自增 ID 并加载到内存,由于多业务端可能同时操作,所以采用版本号 version 乐观锁方式更新,这种分布式 ID 生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多

基于 Redis 模式

利用 Redis 的 incr 命令实现 ID 的原子性自增,缺点:要考虑到 Redis 持久化的问题。Redis 有两种持久化方式 RDBAOF,RDB 会定时打一个快照进行持久化,假如连续自增但 Redis 没及时持久化,而这会 Redis 挂掉了,重启 Redis 后会出现 ID 重复的情况,AOF 会对每条写命令进行持久化,即使 Redis 挂掉了也不会出现 ID 重复的情况,但由于 incr 命令的特殊性,会导致 Redis 重启恢复的数据时间过长

雪花算法

雪花算法(Snowflake),是 twitter 公司内部分布式项目采用的 ID 生成算法

美团(Leaf)

Leaf 由美团开发,支持号段模式和 snowflake 算法模式,可以切换使用

雪花算法

结构

image-20211002163042130

snowflake 生成的是 Long 类型的 ID,一个 Long 类型占 8 个字节,每个字节占 8 比特,也就是说一个 Long 类型占 64 个比特

Snowflake ID 组成结构

  • 正数位(占 1 比特):第一个 bit 位(1bit):Java 中 Long 的最高位是符号位代表正负,正数是 0,负数是 1,一般生成 ID 都为正数,所以默认为 0
  • 时间戳(占 41 比特):时间戳部分(41bit):毫秒级的时间,不建议存当前时间戳,而是用(当前时间戳 - 固定开始时间戳)的差值,可以使产生的 ID 从更小的值开始,41 位的时间戳可以使用 69 年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69 年
  • 机器ID(占 5 比特):工作机器 id(10bit):也被叫做 workId,这个可以灵活配置,机房或者机器号组合都可以
  • 数据中心(占 5 比特):工作机器 id(10bit):也被叫做 workId,这个可以灵活配置,机房或者机器号组合都可以
  • 自增值(占 12 比特):序列号部分(12bit),自增值支持同一毫秒内同一个节点可以生成 4096 个 ID

存在的问题

雪花算法目前存在时间回拨问题,而且不同的机器也无法完全保证时间一样,所以可能会出现重复问题

美团(Leaf)

GitHub 地址:https://github.com/Meituan-Dianping/Leaf

下载源码

image-20211002222429225

下载后,进入目录编译

mvn clean install -Dmaven.test.skip=true

image-20211002222654968

创建 maven 工程

image-20211003093049339

添加依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.18.RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

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

    <!--zk-->
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>2.6.0</version>
        <exclusions>
            <exclusion>
                <artifactId>log4j</artifactId>
                <groupId>log4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

创建 leaf 数据库,然后 SQL 脚本如下:

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')

image-20211003094700421

在 resource 当中添加 leaf.properties 配置文件内容如下:

leaf.name=com.sankuai.leaf.opensource.test
leaf.segment.enable=true
leaf.segment.url=jdbc:mysql://127.0.0.1:3306/leaf?serverTimezone=UTC
leaf.segment.username=root
leaf.segment.password=1234

leaf.snowflake.enable=true
leaf.snowflake.address=192.168.101.88
leaf.snowflake.port=2181

创建控制器:

TestController.java

/**
 * @author BNTang
 * @version 1.0
 * @project distributedId
 * @description 美团leaf
 * @since Created in 2021/10/3 003 9:48
 **/
@RestController
public class TestController {
    @Resource
    private SegmentService segmentService;
    @Resource
    private SnowflakeService snowflakeService;

    /**
     * 分段式
     *
     * @return Id
     */
    @GetMapping("/segment")
    public Long segment() {
        return segmentService.getId("leaf-segment-test").getId();
    }

    /**
     * 雪花算法
     *
     * @return Id
     */
    @GetMapping("/snowflake")
    public Result snowflake() {
        return snowflakeService.getId("imooc");
    }
}

创建启动类

SpringMainApp.java

/**
 * @author BNTang
 * @version 1.0
 * @project distributedId
 * @description
 * @since Created in 2021/10/3 003 9:56
 **/
@SpringBootApplication
@EnableLeafServer
public class SpringMainApp {
    public static void main(String[] args) {
        SpringApplication.run(SpringMainApp.class, args);
    }
}

启动工程,访问 http://localhost:8080/segment 效果如下图所示:

image-20211003100442404

雪花算法本分不演示,因博主没有搭建 zk 环境,所以演示不了,雪花算法效果也是很简单,访问第二个接口即可生成

注意事项

  • zookeeper 要打开
posted @ 2021-10-02 14:58  BNTang  阅读(640)  评论(0编辑  收藏  举报