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

news2024/12/24 22:15:14

文章目录

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

前言

多租户(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.加载xml
        InputStream inputStream = new ClassPathResource("create-library.xml").getInputStream();
        Document document = reader.read(inputStream);
        // 3.获取根节点
        Element root = document.getRootElement();

        // 4.遍历每个statement
        List<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 {
    @Autowired
    private   ExecSqlConfig execSqlConfig ;

    @Autowired
    private  DataSource dataSource ;
/**
 * @description: 把模板中的数据库名称替换为新租户的名称
 * @author: 
 * @date: 2023/9/27 15:05
 * @param: [name , replaceMap]
 * @return: void
 **/
    @SneakyThrows
    public  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());

            // 执行SQL
           scriptRunner.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.coloradmin.cn/o/1050201.html

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

相关文章

Python 小爬虫入门 -- 爬取专栏文章标题保存到 CSV 文件中

爬取专栏文章标题保存到 CSV 文件中目标分析网页代码及理解代码段一代码段二成果展示爬取专栏文章标题保存到 CSV 文件中 目标 从一个网页上抓取数据,并保存到一个 CSV 文件中。 具体是爬取 微机系统与接口上机实验_TD PITE型 专栏里的所有 文章标题 并 保存到 csv 文件 中…

估计、偏差和方差

一、介绍 统计领域为我们提供了很多工具来实现机器学习目标&#xff0c;不仅可以解决训练集上的任务&#xff0c;还可以泛化。基本的概念&#xff0c;例如参数估计、偏差和方差&#xff0c;对于正式地刻画泛化、欠拟合和过拟合都非常有帮助。 二、参数估计 参数估计 是统计学…

35 LRU缓存

LRU缓存 题解1 双map&#xff08;差2个testcases&#xff09;题解2 哈希表双向链表&#xff08;参考&#xff09;题解3 STL:listunordered_map 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正…

通讯网关软件016——利用CommGate X2Access实现OPC数据转储Access

本文介绍利用CommGate X2ACCESS实现从OPC Server读取数据并转储至ACCESS数据库。CommGate X2ACCESS是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;实现从OPC Server读取数据并转储至ACCESS…

【前段基础入门之】=>CSS 常用的字体文本属性

导读&#xff1a; 这一章&#xff0c;主要分享一些 CSS 中的一些&#xff0c;常用的 字体和文本方面的属性。 文章目录 字体属性字体大小字体族字体风格字体粗细字体复合写法 文本属性文本间距文本修饰文本缩进文本水平对齐行高vertical-align 字体属性 字体大小 属性名&…

进入Linux的世界

了解Linux的历史 一、Linux发展史二、企业应用现状三、Linux操作系统的各种版本 一、Linux发展史 了解一下硅谷模式: 1945年——1991年是美苏冷战的时间&#xff0c;在这个环境背景下&#xff0c;计算机诞生了。 Linux的发展史&#xff1a; 查看Linux纯源代码 二、企业…

No144.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

完整的 pixel 6a 刷入 AOSP 源码过程记录

基础环境 虚拟机&#xff1a;VMware Workstation 16 Pro 16.0.0 build-16894299 Linux版本&#xff1a;ubuntu-16.04.7-desktop-amd64 设备&#xff1a;pixel 6a&#xff1b;代号&#xff1a;bluejay&#xff1b; 基础软件安装 安装 Git 命令&#xff1a;sudo apt install git …

ATA-M系列功率放大器——应用场景介绍

ATA-M系列是一款理想的单通道功率放大器。最大输出690Vrms电压&#xff0c;800VA功率&#xff0c;可驱动0~100%的阻性或非阻性负载。输出阻抗匹配多个档位可选&#xff0c;客户可根据测试需求调节。 图&#xff1a;ATA-M系列功率放大器 国产品牌安泰电子自主研发的ATA-M系列功率…

MySQL MMM高可用架构

MySQL MMM高可用架构一、MMM概述1、MMM简介2、MMM高可用架构3、MMM故障切换流程 二、MMM高可用双主双从架构部署1、配置主主复制&#xff08;master&#xff09;&#xff0c;主从复制&#xff08;slave&#xff09;1&#xff09;修改 Master1的MySQL配置文件2&#xff09;把配置…

Linux 压缩和解压

1、tar命令&#xff08;复杂&#xff09; 使用tar命令均可以进行压缩和解压缩的操作 语法&#xff1a;tar [-c -v -x -f -z -C] 参数1 参数2 ... 参数N -c&#xff0c;创建压缩文件&#xff0c;用于压缩模式 -v&#xff0c;显示压缩、解压过程&#xff0c;用于查看进度 -x&am…

redis查看耗时久的命令

redis查看耗时久的命令主要有两招&#xff1a;latency和slow log 【latency】 在Redis中&#xff0c;latency命令用于监视和测量Redis实例的延迟。 先进入redis: redis-cli -h 127.0.0.1 -p 24000[查看延迟监视器阈值] CONFIG GET latency-monitor-threshold这个值返回0&…

N9917A|是德科技keysight N9917A微波分析仪

181/2461/8938毫米波频率测量需要精确和谨慎。幸运的是&#xff0c;随着更多的毫米测试设备问世&#xff0c;工程挑战的难度略有下降。信号分析仪现已将同轴器件的直接覆盖范围扩大到110 GHz。这提供了低噪声、高精度和宽带宽的优势&#xff0c;使工程师能够专注于他们的设计和…

ESP32IDF出现Syntax Warning in cmake code at column 47报错

前言 &#xff08;1&#xff09;ESP32的资料还是挺难找的&#xff0c;遇到bug处理起来挺折磨人的。今天分享一个我遇到的bug&#xff0c;以及处理思路。 报错日志 &#xff08;1&#xff09;前天在些博客的时候&#xff0c;做测试发现了一个奇怪的bug&#xff0c;报错日志如下。…

Linux 本地 Docker Registry本地镜像仓库远程连接

目录 Linux 本地 Docker Registry本地镜像仓库远程连接 1. 部署Docker Registry 2. 本地测试推送镜像 3. Linux 安装cpolar 4. 配置Docker Registry公网访问地址 5. 公网远程推送Docker Registry 6. 固定Docker Registry公网地址 Linux 本地 Docker Registry本地镜像仓库…

Linux Kernel 之十 虚拟化、VirtIO 架构及规范、VirtQueue VRing

VirtIO 是一种 IO 半虚拟化解决方案&#xff0c;它提供 Guest OS 与 Hypervisor 虚拟化设备之间的通信框架和编程接口。其主要的优势是能提高性能且减少跨平台带来的兼容性问题。本文重点结合 VirtIO 规范 1.1 版以及 Linux 中的源码来分析 VirtIO 框架。 本文是我自己学习虚拟…

【Vue.js】使用Element入门搭建登入注册界面axios中GET请求与POST请求跨域问题

一&#xff0c;ElementUI是什么&#xff1f; Element UI 是一个基于 Vue.js 的桌面端组件库&#xff0c;它提供了一套丰富的 UI 组件&#xff0c;用于构建用户界面。Element UI 的目标是提供简洁、易用、美观的组件&#xff0c;同时保持灵活性和可定制性 二&#xff0c;Element…

idea2023根据表自动生成+springboot跑起来

idea安装插件 idea中显示数据库连接 就可以看到如下界面 选中你想生成的表&#xff0c;右键如下操作 如上就有了所有需要的后端代码 生成后&#xff0c;要查看一下mapper.xml中的文件是否 正确&#xff0c;若有误请先去修改&#xff0c;例如我的版本下生成了xml文件中缺乏…

Firecamp2.7.1exe安装与工具调试向后端发送SocketIO请求

背景&#xff1a; 笔者在python使用socket-io包时需要一个测试工具&#xff0c;选择了firecamp这个测试工具来发送请求。 参考视频与exe资源包&#xff1a; Firecamp2.7.1exe安装包以及基本使用说明文档&#xff08;以SocketIO为例&#xff09;.zip资源-CSDN文库 15_send方法…

如何注册一个 DA 为 10 的高价值老域名

众所周知&#xff0c;由于域名存在唯一性&#xff0c;随着人们注册的越多&#xff0c;好域名也变得越来越少&#xff0c;渐渐成为稀缺的网络资源。这个时候要想拥有好的域名&#xff0c;抢注优质老域名就成了广大米友的路径之一。 优质的高价值域名都有一个特点&#xff0c;那…