SaaS 电商设计 (六) 实现 id 生成器本地化生产 (附源码)

专栏系列

-SaaS 电商设计 (一) 如何设计一套适应多规格的商品服务
-SaaS 电商设计 (二) 私有化部署-缓存中间件适配
-SaaS 电商设计 (三) 电商黄金流程(商详,购物车,提单)梳理,持续更新(建议收藏)
-SaaS 电商设计 (四) 谈一谈电商系统高并发多耦合上下游的系统压测怎么做
-SaaS 电商设计 (五) 私有化部署-实现 binlog 中间件适配(附源码)
-SaaS 电商设计 (六) 实现 id 生成器本地化生产 (附源码)
-SaaS 电商设计 (七) 利用 Spring 扩展点 ImportBeanDefinitionRegistrar 实现 toB 系统对接(附源码)

一.背景

1.1 背景

  • 业务背景:

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

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

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

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

场景具体逻辑示例
SPUIdSPUId生产支持固定位数(可配),起始值(可配)的自增流水Id
类目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 结合现有业务的最终选择

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

- 支持分布式全局
- 支持私有化部署独立部署
- 支持自定义规则 **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表达.

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 https://github.com/Baixiu-code/id-generate-starter

3.5 多线程并发测试验证

本着面向TDD的原则,也开放了多线程版本的自测演示.

场景:
启动1000个线程同时并发请求获取生产Id

脚本准备:

insert into sequence (tenant_id,name,start,end,step_size,create_time,update_time) values ('9527','product','0','0','2',now(),now());

这里的 stepSize 设为2 ,尽量去触发临界值得到更新数据库的条件,来模拟现实场景的情况.实际场景中可能stepSize 可能非常大.大多数都是内存级别的读取行为,很少触动数据库的更新.

验证方案:

验证获取的Id是否重复,本方案通过 put IdConcurrentHashMap , map 能够自动去重. 最后通过countDownLatch阻塞主线程,获取 ConcurrentHashMapsize 是否为1000,来得到是否满足并发场景下的生产Id诉求.

在这里插入图片描述

https://github.com/Baixiu-code/id-generate-starter-test

赠人玫瑰 手有余香 我是柏修 一名持续更新的晚熟程序员
期待您的点赞,关注加收藏,加个关注不迷路,感谢
您的鼓励是我更新的最大动力
↓↓↓↓↓↓

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柏修

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值