开源通用高性能的分布式id序列组件

饭宝  金牌会员 | 2023-6-30 23:58:05 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 935|帖子 935|积分 2805

原文地址:https://ntopic.cn/p/2023062101/
分布式id序列说明

业务数据的存储,少不了数据记录的id序列。
id序列(或称序列)的生成方式有很多种,比如当前时间戳、数据库的序列值(Oracle的序列,MySQL的自增ID等)、UUID等方式。
这些生成方式都有一定的局限性,如时间戳在业务量较大时容易重复、Oracle序列和MySQL的自增ID限定了数据库类型(且MySQL的自增ID只能保证单库唯一,在分库分表的场景下也不适用)、UUID容易重复且无法保证递增等。
同时,一般业务数据的id序列通常会带上一些业务信息,比如增加业务标识前缀、增加年月日等信息。业务id序列的处理变得多样,则进一步要求业务id序列的生成通用且高效。
通用分布式id序列组件

为了屏蔽业务获取id序列因数据库类型、分库分表等带来的研发和维护成本,我们把分布式id序列的获取抽取为一个通用组件,对业务统一接口和规范。
通用分布式id序列实现方式有很多,本文主要介绍一种基于数据表的实现方式,通过一张表记录所有的业务序列名和值,业务根据序列名获取下一个序列值(和Oracle序列类型,但是无需为每个序列创建序列,因此更简单):

本方案的设计主要考量点:

  • 通用性:仅依赖一张序列数据表,JDBC支持的数据库均可使用,包括SQLite、MySQL、Oracle、OceanBase等。
  • 高性能:本地缓存一个序列区间,缓存使用完之前无DB交互;缓存的区间可设置,区间越大,DB访问越少,性能越高。
  • 分布式:受益于集中式的序列数据表,保证了序列全局唯一。
分布式id序列组件设计

组件接口设计

组件接口只有1个,就是获取序列:
  1. /**
  2. * Author: obullxl@163.com
  3. * Copyright (c) 2020-2023 All Rights Reserved.
  4. */
  5. package cn.ntopic.sequence;
  6. /**
  7. * 分布式序列服务
  8. *
  9. * @author obullxl 2023年06月21日: 新增
  10. */
  11. public interface NTSequence {
  12.     /**
  13.      * 默认序列名称
  14.      */
  15.     String DEFAULT_SEQUENCE_NAME = "DEFAULT";
  16.     /**
  17.      * 序列名称最大长度
  18.      */
  19.     int MAX_SEQUENCE_NAME_LENGTH = 64;
  20.     /**
  21.      * 获取下一个序列值
  22.      *
  23.      * @return 获取默认序列的新的唯一的序列值 {@link #DEFAULT_SEQUENCE_NAME}
  24.      * @throws IllegalArgumentException 参数非法
  25.      */
  26.     default long next() {
  27.         return this.next(DEFAULT_SEQUENCE_NAME);
  28.     }
  29.     /**
  30.      * 获取下一个序列值
  31.      *
  32.      * @param sequenceName 序列名称,非空,1~64字符,业务可随意指定(如:用户模块为`USER`,订单模块为`ORDER`等)
  33.      * @return 新的唯一的序列值
  34.      * @throws IllegalArgumentException 参数非法
  35.      */
  36.     long next(String sequenceName);
  37. }
复制代码
组件可控参数

在追求通用性和性能的同时,以参数的方式供个性化调控:

  • id序列数据表名:默认值为nt_sequence;但对于不同的业务,对数据表名有要求规范(如:表名前置等),因此序列数据表名可设置
  • id序列起始值:默认值为1,即序列值从1开始递增;但对于存量业务,id值起始值需要比存量最大值要大,否则容易重复
  • id序列最大值:默认值为99999999,序列值递增到最大值,则循环从起始值开始
  • 序列更新重试次数:默认值为10,当缓存序列用尽,需要查询和更新序列数据表,比较存在网络通讯和DB操作,不可避免存在异常失败,失败后会进行重试
  • 序列缓存大小:默认值为1000,值越大,访问DB越少,性能越高,序列的连续性越差(如:缓存大小为1000,当序列用到400时,服务器重启了,那么401~1000直接的序列就丢失了);相反,值越小,访问DB越多,性能越低,序列的连续性越好。
  1. /**
  2. * 属性-数据源
  3. */
  4. private final DataSource ntDataSource;
  5. /**
  6. * 属性-重试次数
  7. */
  8. private int retryTimes = 10;
  9. /**
  10. * 属性-数据表名
  11. */
  12. private String tableName = "nt_sequence";
  13. /**
  14. * 属性-序列获取步长(即序列缓存大小)
  15. */
  16. private long step = 1000L;
  17. /**
  18. * 属性-序列最小值
  19. */
  20. private long minValue = 1L;
  21. /**
  22. * 属性-序列最大值
  23. */
  24. private long maxValue = 99999999L;
复制代码
序列组件使用

目前JAR包已经发布,通过Gitee进行仓库托管,也可直接使用,2步即可:

  • 第一步:在项目源代码的根pom.xml中,设置仓库地址:
  1. <repositories>
  2.    <repository>
  3.       <id>Gitee-obullxl</id>
  4.       <url>https://gitee.com/obullxl/maven-repository/master/repository</url>
  5.    </repository>
  6. </repositories>
复制代码

  • 第二步:引用JAR包,仅需要依赖本JAR包,无其他JAR包依赖:
  1. <dependency>
  2.     <groupId>cn.ntopic</groupId>
  3.     <artifactId>sequence-jdbc</artifactId>
  4.     <version>1.0.1</version>
  5. </dependency>
复制代码
在业务代码中调用序列组件样例:
  1. // 1. 构建数据源
  2. DruidDataSource dataSource = new DruidDataSource();
  3. dataSource.setUrl("jdbc:sqlite:/Users/obullxl/CodeSpace/sequence-jdbc/SequenceJDBC.sqlite");
  4. dataSource.setDriverClassName("org.sqlite.JDBC");
  5. dataSource.setPoolPreparedStatements(false);
  6. dataSource.setMaxPoolPreparedStatementPerConnectionSize(-1);
  7. dataSource.setTestOnBorrow(true);
  8. dataSource.setTestOnReturn(false);
  9. dataSource.setTestWhileIdle(true);
  10. dataSource.setValidationQuery("SELECT '1' FROM sqlite_master LIMIT 1");
  11. // 2. 初始化序列组件
  12. NTSequenceImpl ntSequence = new NTSequenceImpl(dataSource);
  13. ntSequence.setTableName(tableName);
  14. ntSequence.createTable();
  15. ntSequence.init();
  16. // 3. 获取序列值
  17. @Autowire
  18. @Qualifier("ntSequence")
  19. private NTSequence ntSequence;
  20. // 获取`DEFAULT`默认序列ID
  21. long newId1 = ntSequence.next();
  22. long newId2 = ntSequence.next();
  23. long newId3 = ntSequence.next();
  24. // 获取`USER`用户ID:
  25. long newUserId1 = ntSequence.next("USER");
  26. long newUserId2 = ntSequence.next("USER");
  27. long newUserId3 = ntSequence.next("USER");
  28. // 获取`ORDER`订单ID:
  29. long newOrderId1 = ntSequence.next("ORDER");
  30. long newOrderId2 = ntSequence.next("ORDER");
  31. long newOrderId3 = ntSequence.next("ORDER");
复制代码
完整的使用方法,可参数源代码仓库说明文档(README.md):
分布式id序列测试用例

因为代码较大,请直接查看源代码:

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

饭宝

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表