【项目实战】单数据源多数据库实现多租户

文章目录

  • 前言
  • 多租户的四种实现方案
  • 单数据源多数据库
    • 实现思路
    • 代码实现
  • 总结

前言

多租户(Multi-Tenancy)是一种软件架构设计模式,旨在使单个应用程序可以同时为多个租户(如不同组织、用户或客户)提供服务,每个租户都拥有独立的数据和配置,并且彼此之间相互隔离。实现多租户架构可以帮助企业降低成本、简化管理并提高效率。

在设计多租户系统之前,我们需要明确业务需求和预期目标。是否希望租户之间完全隔离,还是允许一些共享资源?我们需要考虑以下几个方面:

数据隔离:每个租户的数据应该彼此隔离,不同租户之间不能直接访问或共享数据。可以通过使用独立的数据库或者使用数据表中的租户ID进行区分来实现数据隔离。

身份认证与授权:多租户系统需要支持各个租户的身份认证和授权,确保只有合法的用户能够访问自己所属的租户资源。

水平扩展:多租户系统可能会面临大量用户和数据增长的挑战,因此需要具备水平扩展的能力,以保持系统的高性能和可靠性。

配置管理:每个租户可能有不同的配置需求,例如与邮件、支付和存储等集成的参数设置。因此,应该提供一种可灵活配置的机制,使租户能够根据自己的需求进行定制。

安全性和隔离:多租户系统需要确保租户之间的安全隔离,以防止潜在的数据泄露和访问冲突。在设计系统时,应考虑到对于敏感数据的保护和各种攻击的预防。

资源利用率:为了提高资源利用率,可以考虑共享某些通用的资源,如计算资源、存储空间和网络带宽。这样可以减少成本,并提供更高的效率。

多租户的四种实现方案

常见的设计方案大致分为4种:

1、所有租户使用同一数据源下同一数据库下共同数据表(单数据源单数据库单数据表)
2、所有租户使用同一数据源下同一数据库下不同数据表(单数据源单数据库多数据表)
3、所有租户使用同一数据源下不同数据库下不同数据表(单数据源多数据库多数据表)
4、所有租户使用不同数据源下不同数据库下不同数据表(多数据源多数据库多数据表)
在这里插入图片描述

第一、二种相对来说比较简单。我们本文主要对第三种展开讲解

单数据源多数据库

多租户的最终目的就是要实现数据隔离,在但数据源多数据库中,也就是当有了一个新的租户要进行注册的时候,要实现去创建一个数据库和对应的数据库表。
现在就有了问题,应该如何去动态的去创建数据库呢?

实现思路

1、创建数据库:可以数用传统的方式,通过sql语句去创建
2、创建数据库表:在创建完数据库之后,使用sql语句去创建表

下文的代码中使用了读取xml文件中的sql去创建

代码实现

在resources下创建一个xml文件:
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<document><statement><name>create-tenant</name><script>DROP DATABASE IF EXISTS `sys_user${tenant_id}`;CREATE DATABASE ${database};USE ${database};CREATE TABLE `sys_user` (`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',`dept_id` bigint(20) DEFAULT NULL COMMENT '部门ID',`user_name` varchar(30) NOT NULL COMMENT '用户账号',`nick_name` varchar(30) NOT NULL COMMENT '用户昵称',`user_type` varchar(2) DEFAULT '00' COMMENT '用户类型(00系统用户)',`email` varchar(50) DEFAULT '' COMMENT '用户邮箱',`phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码',`sex` char(1) DEFAULT '0' COMMENT '用户性别(012未知)',`avatar` varchar(100) DEFAULT '' COMMENT '头像地址',`password` varchar(100) DEFAULT '' COMMENT '密码',`status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',`login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP',`login_date` datetime DEFAULT NULL COMMENT '最后登录时间',`create_by` varchar(64) DEFAULT '' COMMENT '创建者',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`update_by` varchar(64) DEFAULT '' COMMENT '更新者',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`remark` varchar(500) DEFAULT NULL COMMENT '备注',`auth_id` varchar(30) DEFAULT NULL COMMENT '其他项目用户id(积分)',`ding_id` varchar(50) DEFAULT NULL COMMENT '用户dingid',`tenant_id` varchar(100) DEFAULT NULL COMMENT '公司id',`open_id` varchar(50) DEFAULT NULL COMMENT '微信用户唯一标识',PRIMARY KEY (`user_id`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='用户信息表';</script></statement>
</document>

这是对应的创建数据库的语句。

获取xml文件中信息:

@Component
public class ExecSqlConfig {private final Map<String, String> sqlContainer = new HashMap<>();public ExecSqlConfig() throws Exception {// 1.创建Reader对象SAXReader reader = new SAXReader();// 2.加载xmlInputStream inputStream = new ClassPathResource("create-library.xml").getInputStream();Document document = reader.read(inputStream);// 3.获取根节点Element root = document.getRootElement();// 4.遍历每个statementList<Element> statements = root.elements("statement");for (Element statement : statements) {String name = null;String sql = null;List<Element> elements = statement.elements();// 5.拿到name和script加载到内存中管理for (Element element : elements) {if ("name".equals(element.getName())) {name = element.getText();} else if ("script".equals(element.getName())) {sql = element.getText();}}sqlContainer.put(name, sql);}}public String get(String name) {return sqlContainer.get(name);}}

sql执行脚本:
在脚本中去读取了ExecSqlConfig 文件中的信息

@Component
public class ExecSqlUtil {@Autowiredprivate   ExecSqlConfig execSqlConfig ;@Autowiredprivate  DataSource dataSource ;
/*** @description: 把模板中的数据库名称替换为新租户的名称* @author: * @date: 2023/9/27 15:05* @param: [name , replaceMap]* @return: void**/@SneakyThrowspublic  void execSql(String name, Map<String, String> replaceMap) {try {// 获取SQL脚本模板String sql = execSqlConfig.get(name);// 替换模板变量for (Map.Entry<String, String> entity : replaceMap.entrySet()) {sql = sql.replace(entity.getKey(), entity.getValue());}ScriptRunner scriptRunner = new ScriptRunner(dataSource.getConnection());// 执行SQLscriptRunner.runScript(new StringReader(sql));}catch (Exception e){e.printStackTrace();}}}

业务逻辑

public void addTenantLibrary(TanentModel tanentModel) {try {if (!(tanentModel.getDescription().isEmpty()||tanentModel.getTenantContact().isEmpty()||tanentModel.getTel().isEmpty()||tanentModel.getTenantName().isEmpty())){//判断数据源中是否已经存在该数据库int flag= tantentMapper.insertTantentAdmin(tanentModel.getTenantName());if (flag>0){throw new Exception("已存在该数据库");}else {Map<String,String> map=new HashMap<>();map.put("${database}",tanentModel.getTenantName());map.put("${project_name}",tanentModel.getTenantName());map.put("${leader}",tanentModel.getTenantContact());map.put("${phone}",tanentModel.getTel());map.put("${description}",tanentModel.getDescription());execSqlUtil.execSql("create-tenant",map);}}}catch (Exception e){e.printStackTrace();throw new RuntimeException(e.getMessage()); // 将异常信息作为响应的一部分返回给前端}}

在业务逻辑的代码中,map.put(“${database}”,tanentModel.getTenantName());是替换的xml文件中的变量

总结

在多租户架构中,单数据源多数据库是一种常见的实现方案。通过使用不同的数据库实例或者数据库模式,我们可以实现对每个租户独立的数据存储,并保持彼此之间的隔离性。在本文中,我们讨论了单数据源多数据库的实现方案和一些关键考虑因素。

总结而言,单数据源多数据库方案为多租户系统提供了以下优势:

数据隔离:每个租户拥有独立的数据库,数据之间互不干扰,确保了租户数据的隔离性和安全性。

性能优化:通过将租户数据分散到多个数据库中,可以减轻单一数据库的负载压力,提高系统的性能和吞吐量。

扩展性:当租户数量增加时,可以通过新增数据库实例或者数据库服务器来水平扩展系统,以满足不断增长的业务需求。

容错性:单数据源多数据库方案降低了租户之间的相互影响,当某个数据库发生故障时,其他数据库仍然可以正常运行,提高了系统的容错性和可用性。

在实施单数据源多数据库方案时,我们需要注意以下几个关键因素:

数据迁移和同步:对于已有的多租户系统,将现有数据迁移到新的数据库中可能需要一定的工作量。同时,为保持数据的一致性,我们需要确保数据在多个数据库之间的同步。

租户管理和路由策略:需要实现租户的动态管理和路由策略,确保每个请求能够正确地路由到对应的数据库。这可以通过维护租户-数据库映射关系或者使用中间件进行请求路由来实现。

数据库连接和资源管理:在系统设计中,需要考虑数据库连接的管理和资源分配。合理配置数据库连接池以及优化数据库查询等操作,可以提高系统的性能和资源利用率。

安全性和权限控制:在设计数据库架构时,需要考虑安全性和权限控制的需求。确保不同租户之间的数据访问和操作是受限的,并遵循数据隐私与保护的法律规定。

综上所述,单数据源多数据库是实现多租户系统的一种有效方案,它提供了良好的数据隔离、性能优化和扩展性。然而,在实施过程中需要综合考虑数据迁移、租户管理、资源管理和安全性等因素,以确保系统的稳定性和可靠性。通过合理的设计和实施,单数据源多数据库方案可以为多租户系统提供高效、安全且可扩展的数据存储解决方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/92573.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS 相关

CSS 相关 CSS布局如何管理CSS 代码目录&#xff1f;分多个目录的话&#xff0c;会有命名冲突&#xff0c;那如何解决命名冲突&#xff1f; box-sizing:border-boximage的宽度的问题&#xff1a; CSS布局 单列布局&#xff1a;将一个元素作为布局容器。通常设置一个较小的宽度(最…

MATLAB中d2d函数用法

目录 语法 说明 示例 重新采样离散时间模型 重新采样已识别的离散时间模型 d2d函数的功能是重新采样离散时间模型。 语法 sys1 d2d(sys, Ts) sys1 d2d(sys, Ts, method) sys1 d2d(sys, Ts, opts) 说明 sys1 d2d(sys, Ts)将离散时间动态系统模型 sys 重新采样&#…

JAVA 异常分类及处理

1 概念 如果某个方法不能按照正常的途径完成任务&#xff0c;就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时&#xff0c;这个方法会立刻退出同时不返回任何值。另外&#xff0c;调用 这个方法的其他代码也无法继续执行&#xff0c;异常处理…

OCI 发布了容器运行时和镜像规范!

7 月 19 日是开放容器计划Open Container Initiative&#xff08;OCI&#xff09;的一个重要里程碑&#xff0c;OCI 发布了容器运行时和镜像规范的 1.0 版本&#xff0c;而 Docker 在这过去两年中一直充当着推动和引领的核心角色。 我们的目标是为社区、客户以及更广泛的容器行…

重试机制-spring-retry、guava-retry

重试机制是什么&#xff1f; 网络重试机制是用于在网络通信中处理失败的请求。接口重试可以在一定的时间间隔内多次尝试发送相同的请求&#xff0c;直到请求成功或达到最大重试次数为止。 为什么要重试&#xff1f; 1. 提高请求的成功率&#xff1a;网络通信中可能会出现各种…

通过 Azure 日志分析加强云安全

Microsoft Azure 云服务在安全日志存储、访问、可伸缩性、降低成本和易于部署方面提供了巨大的优势&#xff0c;因此在企业中很受欢迎。 Microsoft Azure 日志记录工具&#xff08;如 Log360&#xff09;可帮助管理 Azure 云基础结构中所有设备和应用程序&#xff08;如虚拟机…

一点C知识:数据类型和内存地址。

当你需要存储一份数据到内存里的时候&#xff0c;你需要通过需要存储的方式和精度&#xff0c;向操作系统申请一份内存地址&#xff0c;形容怎么样申请地址的关键字就是数据类型。 例如&#xff0c;32位的处理器就有着32位的地址位宽&#xff0c;定义了一个char类型的数据&…

Flutter笔记:AnimationMean、AnimationMax 和 AnimationMin 三个类的用法

Flutter笔记 AnimationMean、AnimationMax 和 AnimationMin三个类的用法 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/…

Python绘图系统24:添加辅助坐标轴

文章目录 辅助坐标增减坐标轴时间轴**代码优化源代码 Python绘图系统&#xff1a; 前置源码&#xff1a; Python打造动态绘图系统&#x1f4c8;一 三维绘图系统 &#x1f4c8;二 多图绘制系统&#x1f4c8;三 坐 标 轴 定 制&#x1f4c8;四 定制绘图风格 &#x1f4c8;五 数据…

MySQL单表查询与多表查询

目录 一、单表查询 ​编辑 1、显示所有职工的基本信息。 ​编辑2、查询所有职工所属部门的部门号&#xff0c;不显示重复的部门号。 ​编辑3、求出所有职工的人数。 4、列出最高工和最低工资。 ​编辑5、列出职工的平均工资和总工资。 ​编辑6、创建一个只有职…

opencv实现目标跟踪及视频转存

创建跟踪器 def createTypeTracker(trackerType): 读取视频第一帧&#xff0c;选择跟踪的目标 读第一帧。 ok, frame video.read() 选择边界框 bbox cv2.selectROI(frame, False) 初始化跟踪器 tracker_type ‘MIL’ tracker createTypeTracker(tracker_type) 用第一…

上古神器:十六位应用程序 Debug 的基本使用

文章目录 参考环境上古神器 DebugBug 与 DebuggingDebugDebug 应用程序淘汰原因使用限制 DOSBox学习 Debug 的必要性DOSBox-X Debug 的基本使用命令 R查看寄存器的状态修改寄存器的内容 命令 D显示内存中的数据指定起始内存空间地址指定内存空间的范围 命令 A使用命令语法错误查…

ElementPlus· tab切换/标签切换 + 分页

tab切换 ---> <el-tabs><el-tab-pane>... 分页 --------> <el-pagination> tab切换 // tab标签切换 // v-model双向绑定选项中的name&#xff0c;tab-change事件在 activeName改变时触发 <script setup> const tabChange (tab, event)>{…

28391-2012 建筑施工机械与设备 人力移动式液压动力站

声明 本文是学习GB-T 28391-2012 建筑施工机械与设备 人力移动式液压动力站. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了人力移动式液压动力站(以下简称动力站)的范围、分类、要求、试验方法和检验规则。 本标准适用于以中小…

2023年9月文章一览

2023年9月编程人总共更新了4篇文章&#xff1a; 1.2023年8月文章一览 2.Programming abstractions in C阅读笔记&#xff1a;p144-p160 3.Programming abstractions in C阅读笔记&#xff1a;p161-p165 4.我为什么选择这样一份经常出差的工作 9月份大部分时间在出差&#…

信息安全:网络安全漏洞防护技术原理与应用.

信息安全&#xff1a;网络安全漏洞防护技术原理与应用. 网络安全漏洞又称为脆弱性&#xff0c;简称漏洞。漏洞一般是致使网络信息系统安全策略相冲突的缺陷&#xff0c;这种缺陷通常称为安全隐患。 安全漏洞的影响主要有机密性受损、完整性破坏、可用性降低、抗抵赖性缺失、可…

yolov8 opencv模型部署(C++版)

yolov8 opencv模型部署&#xff08;C 版&#xff09; 使用opencv推理yolov8模型&#xff0c;仅依赖opencv&#xff0c;无需其他库&#xff0c;以yolov8s为例子&#xff0c;注意&#xff1a; 使用opencv4.8.0 &#xff01;使用opencv4.8.0 &#xff01;使用opencv4.8.0 &#…

第8章 Spring(二)

8.11 Spring 中哪些情况下,不能解决循环依赖问题 难度:★★ 重点:★★ 白话解析 有一下几种情况,循环依赖是不能解决的: 1、原型模式下的循环依赖没办法解决; 假设Girl中依赖了Boy,Boy中依赖了Girl;在实例化Girl的时候要注入Boy,此时没有Boy,因为是原型模式,每次都…

28270-2012 智能型阀门电动装置 学习笔记

声明 本文是学习GB-T 28270-2012 智能型阀门电动装置. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了智能型阀门电动装置(以下简称智能电装)的术语、技术要求、试验方法、检验规则、标 志、包装、运输和贮存条件等。 本标准适…

destoon根据查询标题小于5个字符的数据进行删除

最近客户有个需求&#xff0c;就是他采集的时候&#xff0c;标题有些小于5字符的短标题的垃圾数据&#xff0c;进行清空处理&#xff0c;让我进行批量删除。废话不多说&#xff0c;接着干。 首先在dt根目录新建delmysql.php文件&#xff0c;代码如下&#xff1a; <?php r…