一键开箱-id 生成器本地化生产 (附源码)

一.背景

1.1 背景

  • 业务背景:

目前梳理 SaaS 系统中存在以下几种 Id 生成的场景.

财务系统: 财务在生产财务单的时候,获取财务单 Id ,满足分布式场景下能够获取全局Id即可.

支付系统 :订单系统在进行外部提单过程中,需要将生成的订单号与外部的支付平台做对接.在具体开发调试过程中出现这种情况.客户需要支持私有化交付.我们在 SaaS 的开发环境部署联调时,已经与外部的支付平台进行了对接.完成联调结束后.在客户的私有化环境中再次进行测试回归出现了,出现了因为订单号重复导致了支付失败的场景.

商品系统: 商品系统在生产具体的Id形式上有几种业务侧的诉求.

场景 具体逻辑 示例
SPUId SPUId 生产支持固定位数(可配),起始值(可配)的自增流水Id 固定6位.起始值.100000.流水Id:100000,100001,100002等
类目Id 类目Id .支持固定前缀(可配).流水固定位数(可配)的流水自增 固定前缀Ca.流水固定位数(6),Ca000001,Ca000002
  • 技术背景:

现有 SaaS 的不同系统中存在不同的 Id 生产的逻辑和服务,大概可以分为两种方案.

  • 第一种:基于中心化的 Id 服务来获得.
  • 第二种:基于本地化的 Id 数据库来生产. 结合目前的 SaaS系统 会有私有化部署的场景,那就是需要去考虑部署成本的因素.且目前的中心化方案可能带来的单点故障问题.基于以上的一个背景

1.2 目标

  • 支持分布式全局
  • 支持私有化部署独立部署
  • 支持自定义规则 Id 生产
  • 性能目标:单机4c8g下 1000 qps 目标

二.技术方案

2.1 技术调研对比

常见方案 优势 对比目标后的缺点
UUID 使用简单,支持分布式场景,理论上存在碰撞可能(几率极低) 分布式支持;独立部署支持;不支持自定义规则;最重要的原因是利用UUID 作为分布式数据库业务Id 的场景下,由于B+树作为底层的数据结构会带来大量的页分裂和反复的插入以及翻转,并以此带来的大量性能损耗
雪花算法 支持分布式;性能优秀 依赖物理机时钟,时钟回拨回带来碰撞问题;不支持自定义规则;
Redis 支持分布式;性能优秀; 额外的Redis服务成本,不支持自定义规则
ZK 支持分布式; 额外的ZK服务成本;不支持自定义规则

2.2 结合现有业务的最终选择

抛开业务场景的下的技术方案设计都是刷流氓.基于目前咱们的技术目标.选用了一个基于数据库(不额外支出其他资源)的且支持自定义规则的设计方案.

markdown 复制代码
- 支持分布式全局
- 支持私有化部署独立部署
- 支持自定义规则 **Id** 生产
- 性能目标:单机4c8g下 1000 **qps** 目标

三.详细设计

3.1 方案流程

3.1.1 配置初始化

3.1.2 生产 Id 流程

3.2 类图

  • IdAutoBootConfig

  • spring.factories 入口方法

  • 能力1:用以保证引入的业务方能够自动扫描本身的包体,扫描对应的bean到spring container

  • 能力2:用以扫描对应的 mybatis mapper 注入动态代理

  • IdGenerator 用以外漏 id 的生产接口. 方法1:生成全局id generatorGlobalId(String name) tenantId =9527 方法2:生成租户级别全局唯一generatorGlobalId(long tenantId,String name);

  • IdGeneratorImpl 具体方法生成的实现类. 内部持有持有上下文中具体的Sequence name 与 SequenceValue 的映射的读写操作.

核心读写锁分离处理具体流程(方便理解可能与上图部分有重复).

  • InitSequenceConfigWare& SequenceStepContextHolder InitSequenceConfigWare 通过实现 InitializingBean扩展点 完成容器启动后(本质是利用容器启动完成,数据源完成注入)来实现数据库Sequence 读取得到 SequenceConfigModel .通过 SequenceStepContextHolder 维护 sequenceName 与 SequenceValue的映射关系.为了减少 内存占用.将SequenceConfigModel 简化一部分得到 SequenceValue 进而维护到 SequenceStepContextHolder 持有的 ALL_SEQUENCE_CONTEXT 中.

3.3 DB设计

code rule 表(非必选)本期没有实现后续可以持续在这个表进行扩展得到更加灵活的SequenceId表达.

sql 复制代码
CREATE TABLE `code_rule` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `tenant_id` bigint(20) NOT NULL COMMENT '租户id',
  `biz_type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '业务:1-商品编码规则,2-类目编码规则,参见BizTypeEnum',
  `rule_key` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1-流水自增,2-租户自定义,3-类目编号和流水自增,参见RuleKeyEnum',
  `rule_value` varchar(256) NOT NULL COMMENT '编码规则描述json格式',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '数据中心抽数专用字段,无业务含义',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_tid_type` (`tenant_id`,`biz_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='编码规则表';

/**
* **sequence** 表。必选表。本期先实现需要业务方自行创建**ddl**维护到对应数据源中.
*/
CREATE TABLE `sequence` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '当前序列最大值',
  `tenant_id` bigint(20) NOT NULL COMMENT '租户id',
  `name` varchar(200) NOT NULL COMMENT '序列名称',
  `start` bigint(20) NOT NULL COMMENT '开始值',
  `end` bigint(20) NOT NULL COMMENT '结束值',
  `step_size` bigint(20) DEFAULT NULL COMMENT 'id号段数量',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='序列号表';

3.4 no more talking 一键开箱

详见:github github.com/Baixiu-code...

3.5 测试

本着面向TDD的原则,我也开放了多线程版本的自测演示.一键直达吧. github.com/Baixiu-code...

相关推荐
大脑经常闹风暴@小猿1 小时前
1.1 go环境搭建及基本使用
开发语言·后端·golang
尚学教辅学习资料1 小时前
基于SpringBoot的美食分享平台+LW示例参考
spring boot·后端·美食
Vitalia3 小时前
从零开始学 Rust:基本概念——变量、数据类型、函数、控制流
开发语言·后端·rust
猎人everest6 小时前
SpringBoot应用开发入门
java·spring boot·后端
孤雪心殇12 小时前
简单易懂,解析Go语言中的Map
开发语言·数据结构·后端·golang·go
小突突突13 小时前
模拟实现Java中的计时器
java·开发语言·后端·java-ee
web1376560764313 小时前
Scala的宝藏库:探索常用的第三方库及其应用
开发语言·后端·scala
闲猫14 小时前
go 反射 interface{} 判断类型 获取值 设置值 指针才可以设置值
开发语言·后端·golang·反射
LUCIAZZZ14 小时前
EasyExcel快速入门
java·数据库·后端·mysql·spring·spring cloud·easyexcel
Asthenia041215 小时前
依托IOC容器提供的Bean生命周期,我们能在Bean中做些什么?又能测些什么?
后端