项目背景
注:因涉及公司保密协议,会隐藏代码,只说明设计思路。
本人所在公司是做saas软件服务的,在做一个大客户专项时遇到集团企业需要管控子公司,希望可以夸租户管理。
也就是说,有一个集团租户,可以管理多个子租户。配置数据都由集团租户下发,子租户只能使用。子租户还要定时上报业务数据,集团要统计查看各子租户数据。
按现有架构是无法支持该模式的,所以初步打算保持原有租户(之后简称子租户)不变的情况,再将现有系统复制出一套,用于开发集团租户(之后简称主租户),这样业务只要核心开发主租户方向的新需求,子租户增加权限控制就可实现该需求。
遇到的问题
1. 业务模块多,每天都在增加新的模块与表结构(现有表400多张)
2. 主租户下发的配置数据主键如何在多个子租户唯一,并保持与主租户配置数据的关系
3. 如何确保子租户上报的数据主键唯一
4. 大数据量不同表,如果确保数据一致性
5. 时间
因为项目有时间压力,在开发时分多个阶段执行。
验证方案 | 直接修改部分表数据,验证子租户功能是否有问题 |
SQL版 Demo | 根据验证的方案,出一个直接生成新的子租户所有表SQL快速验证版,测试大部分功能是否可用 |
主租户开通流程 | 传输工具完成新的主租户开通流程 |
完整全流程 | 主租户按模块下发、子租户数据上报 |
性能优化 | 优化性能与稳定性 |
在验证方案可行之后,需要快速实现,给业务开发提供接口,保证业务开发不阻塞,所以初始方案与最终方案会有一些差异,但核心思路相同。
核心思路
1. 使用功能模块 + SQL实现各功能模块的数据下发或上报。
调用方指定模块,业务无需关注具体SQL,一次配置好,如后续有修改也只需要负责该模块的开发调整SQL就可以,无需调整调用逻辑。
2. 更改主键ID规则,使用《租户ID + "特殊字符" + 源数据ID》形式,并替换引用字段ID,如staff表引用部门ID,也会将部门ID转换为新ID规则
3. 定义主子任务机制,兼容多业务场景,如任务1完成后,执行任务2修改某些数据等复杂场景。
4. 使用任意载体可将数据做双向传输。
主租户与子租户数据存储方案
前置条件说明
1. 所有表的ID主键都是字符串
2. 每张表都有一个字段存放租户ID
3. 因内部技术架构原因,所有数据结构都是以mysql 的json结构存放。
所以一个表一般只有主键ID与数据一个大json存放,索引都是使用json中的虚拟列实现。
方案不同的地方
引用ID替换问题 | 传输前查出所有主键ID,使用DFA算法替换数据中的引用ID | 更改ID生成规则,可识别数据中哪些数据是ID | 性能原因 |
数据传输方式 | 使用CSV文件作为一个任务数据的载体,先传输到自建minio,接收方下载后解析入库执行 | 使用RESTful接口一个任务分批多次传输,传输完成后一起执行 | 减少历史无用文件,传输时压缩数据大小 |
程序设计
以上基本把本次数据传输的业务需求与方案做了说明。 接下来具体到程序设计阶段。
技术需求
1. 为了可以更好的维护与部署,最好在一个程序中,只通过修改配置实现双向不同地址的传输。
2. 用什么方式获取数据库所有表,避免因业务新增表缺少同步数据。
3. 如何确保之后的扩展性?如初期使用minio做为数据载体,之后需要扩展为RESTful接口形式。
4. 如何解决数据库事务与单表大事物问题。
5. 整个数据变更与传输尽量减少业务开发参与,业务只专注在业务开发上。
整个项目,选用了以下相关工具包:
spring cloud | 2.2.2 | 基础服务 |
hutool | 5.4.3 | 使用DB操作 |
druid | 1.1.22 | DB连接池 |
lombok | 1.18.12 | |
okhttp | 3.14.4 | RESTful传输数据使用(数据压缩gzip) |
用主企业开通的业务流程来做例子说明:
上图简易描述了一个新的租户,如何从一个普通租户转换为一个集团主租户的流程。可以看到无论在主租户还是子租户中,都是有很多步骤,再具体到代码层还会有更多。
数据传输是可以双向传输,所以按数据发送方、接收方定义。一个完整的执行流程大致与下图:
双方分别使用发送方与接收方责任链来实现整体流程。因有特殊业务所以在责任链中增加了特殊业务处理。
下边接着说明数据传输所用到的数据模型:
transfer_application | 应用配置表 | 该表通过只允许一个active的数据,确保当前应用的配置如URL、AK、SK等 |
transfer_model | 业务模块 | 抽象业务模块,为业务开发聚合分类(预定义) |
transfer_modelItem | 业务模块SQL | 对应业务模块SQL语句(预定义) |
transfer_modelItemCondition | 业务模块SQL条件 | 为sql语句定义动态添加的条件 |
transfer_taskInfo | 数据传输主任务 | 一次调用请求生成一次主任务(包含子任务总数量) |
transfer_subtask | 数据传输子任务 | 一个主任务可以支持多个子任务,可支持子任务按顺序执行(包含子任务传输数据总数量) |
transfer_data | 临时数据存储 | 因数据分批传入,整个任务全部传输后再做数据最终落库。 |
在配置业务模块SQL时,大部分核心配置都是查询语句,只有特殊业务对会需要配置其他类型SQL。主要是因为数据发起的逻辑都是以发起方已修改好的最终数据进行传输,在到达接收方时需要根据实际情况进行insert or update的不同进行操作。
项目总结
以上就是该项目整体的核心设计思路,项目主要麻烦的地方就是在前边提到的ID转换,因为数据是增量的,在用DFA搜索替换会导致使用时间越久导致传输效率越差。
旧方案单任务传输110W并转换数据大概需要1小时20分左右,修改替换数据方案后110W数据大概需要10分左右,极大的提升了性能。