云平台-多租户技术设计

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 云平台-多租户技术设计

文章目录


“多租户技术或称多重租赁技术” 是一种软件架构技术,是实现 如何在多组织环境下共用相同的系统或程序组件,并且可确保各租户间数据的隔离性。在当下云计算时代,多租户技术在共用的数据中心以单一系统架构与服务提供多数客户端相同甚至可定制化的服务,并且仍可以保障客户的数据隔离。目前各种各样的云计算服务就是这类技术范畴。


实现方案

多租户的数据隔离方案,不外乎以下三种方案(来自网络转载)分别是:

1. 独立数据库database-based multitenancy

也称per-database-per-tenant,即一个租户一个数据库实例。

这是第一种方案,即一个租户一个数据库,这种方案的用户数据隔离级别最高,安全性最好,但成本较高。

优点: 为不同的租户提供独立的数据库,有助于简化数据模型的扩展设计,满足不同租户的独特需求;如果出现故障,恢复数据比较简单。

缺点: 增多了数据库的安装数量,随之带来维护成本和购置成本的增加。这种方案与传统的一个客户、一套数据、一套部署类似,差别只在于软件统一部署在运营商那里。如果面对的是银行、医院等需要非常高数据隔离级别的租户,可以选择这种模式,提高租用的定价。如果定价较低,产品走低价路线,这种方案一般对运营商来说是无法承受的。


2. 共享数据库,隔离数据架构(schema-based multitenancy

也称per-schema-per-tenant,即一个租户一个schema,但都共享同一个数据库实例。

这是第二种方案,即多个或所有租户共享 DataBase,但是每个租户一个 Schema(也可叫做一个user)。

在MySQL中,schema和database是同义词.

CREATE SCHEMA和CREATE DATABASE是等效的.

但是其他的数据库产品(几乎所有数据库)有所不同.在oracle数据库产品中,schema是database的一部分.

表示the tables and other objects owned by a single user.

优点: 为安全性要求较高的租户提供了一定程度的逻辑数据隔离,并不是完全隔离;

缺点: 如果出现故障,数据恢复比较困难,因为恢复数据库将牵涉到其他租户的数据;如果需要跨租户统计数据,存在一定困难。


3. 共享数据库,共享数据架构table-based multitenancy

即所有租户都使用一个表,然后通过在所有表中增加一个字段(通常就是租户id)来区分不同租户。数据库实例和表都是共享的。

这是第三种方案,即租户共享同一个 DataBase、同一个 Schema,但在表中增加了租户ID的多租户的数据字段。这是共享程度最高、隔离级别最低、维护成本和购置最低的模式。也是一种逻辑隔离方案。

优点: 三种方案比较,第三种方案的维护和购置成本最低,跨租户统计方便,允许每个数据库支持的租户数量多。

缺点: 隔离级别最低,需要在设计开发时加大对安全的开发量;对单个租户的数据备份和恢复困难。

如果希望以最少的服务器为最多的租户提供服务,并且租户接受牺牲隔离级别换取降低成本,这种方案最适合。


4. 方案对比

审视一下三种设计方案,从1到3隔离程度越来越低,共享程度越来越高。我们从以下一些维度对比一下它们各自的优劣(注意:对比主要是从数据库角度看的,而不是整个SaaS):

  • 可扩展性:隔离度越高,扩展性越差。数据库实例在数据库中是一个比较重的资源,虽然RDBMS中一般没有对database的个数做限制,但一个数据库服务器上面创建成千上万个数据库实例的场景应该是很少见的吧。所以从1到3,扩展性依次变差。
  • 隔离性:主要是数据的隔离、负载的隔离。这个很明显,隔离性1最好,3最差,2适中。
  • 成本/资源利用率:这里的成本主要指数据库的成本,或者说硬件的资源利用率。隔离程度越高,利用率越差。比如很多业务其实都有业务高峰和低峰,如果能把高峰不在同一时间段的业务部署在一起,自然是能够提升资源的利用率。
  • 开发复杂度:主要体现在查询、过滤、database/schema/table切换等。1和2适中,3难度高一些。
  • 运维复杂度:性能监控、管理;database/schema/table的管理;租户数据恢复;容灾等。扩展其实也算运维的一部分,第一个已经讨论过了,这里就不包含扩展了。从监控、管理、租户数据恢复、容灾等考虑,隔离度越高,越简单。
  • 可定制性:根据不同租户的需求进行定制的难度,这个自然也是隔离度越高,定制化越好做。


技术方案实现

共享数据库,共享数据架构

应用管理员, 也就是平台管理员


设计原则

多租户SaaS系统怎么设计,下面是我总结的几点原则,供大家参考:

1、租户间是资源隔离的。相互无法访问对方的数据。

我们目前做到逻辑隔离,通过表里面增加租户ID的方式来实现多租户的支持。当然我们自然想做到物理隔离,相应的成本也会多很多。这块大家必须有租户间资源是隔离的概念。为了能更好的理解整个SaaS系统的设计初衷,我们可以认为租户间资源是物理隔离的。

2、组织也是一种资源,各租户都有自己的组织。

每个租户是有各自的资源信息的,这些资源是租户私有的。比如:角色信息、用户信息、组织信息等。

3、租户可以通过组织进行资源划分。

租户和组织这块有很多相近的地方,这块需要深刻的理解下。

我个人是这么理解的:租户是对全部资源物理层面的隔离,而组织是对租户私有资源逻辑上的隔离。

4、租户不支持多层级,租户有不同的类型。

为了降低系统的复杂性,我们建议租户不支持多层级,只能建一级,租户是有类型的,通过类型区分不同的业务场景,租户间是平等的。

比如:XXX运营方也是独立的租户,与其它用户无本质区别。

5、通过组织的层级结构,来实现用户的数据权限。

这块也是我们做的最大改动:轻租户,重组织。发挥组织的天然业务隔离的特性,通过组织树来实现资源数据权限

6、运营侧只管理到租户级别,不应该涉及到租户的私有资源。

组织是租户的私有资源,运营管理侧自然不应该去管理他,也不方便管理。

思路设计

首先,我们为了租户能否方便的访问,以及平台能自动识别访问是哪个租户,我们在接入层采用通过url来识别租户。即系统在初始化租户信息时,会随机生成一个租户编码(租户编码允许修改一次),用于saas平台的三级域名监听,通过在业务系统的处理和绑定,当接收到请求时,拦截器会自动识别对应的租户编码,并加载对应的租户信息。

其次,在业务处理时,租户标识编号作为必须条件带入,进行数据操作。

后期,随着租户数量增多, 数据量必然指数级上涨,可以采用分库分表的处理策略。

首页:

  • 区分平台端(/platform/login), 和租户端入口(/saas/login)

平台管理端:

  1. 新增租户管理, 包含租户基本信息, 租户角色, 租户权限管理,
  1. 1 – 租户基本信息, 要有租户编号,建议有个开放时间限制
  1. 新增运营管理:重视租户登录日志和操作日志的收集
  2. 业务修改:原来的业务逻辑中增加租户ID的字段

平台租户端:

  • 支持有各自的资源信息


技术实现-Mybatis-plus

Mybatis-plus在第3层隔离级别上,提供了基于分页插件的多租户的解决方案,我们对此来进行介绍。

参考: https://baomidou.com/guide/interceptor-tenant-line.html#tenantlineinnerinterceptor

第一步:租户管理

在应用添加维护一张sys_tenant(租户管理表),在需要进行隔离的数据表上新增租户id

第二步:实现TenantHandler接口并实现它的方法:

public interface TenantHandler {
    /**
     * 获取租户 ID 值表达式,支持多个 ID 条件查询
     * <p>
     * 支持自定义表达式,比如:tenant_id in (1,2) @since 2019-8-2
     *
     * @param where 参数 true 表示为 where 条件 false 表示为 insert 或者 select 条件
     * @return 租户 ID 值表达式
     */
    Expression getTenantId(boolean where);
    /**
     * 获取租户字段名
     *
     * @return 租户字段名
     */
    String getTenantIdColumn();
    /**
     * 根据表名判断是否进行过滤
     *
     * @param tableName 表名
     * @return 是否进行过滤, true:表示忽略,false:需要解析多租户字段
     */
    boolean doTableFilter(String tableName);
}


PreTenantHandler 实现 TenantHandler

@Slf4j
@Component
public class PreTenantHandler implements TenantHandler {
    @Autowired
    private PreTenantConfigProperties configProperties;
    /**
     * 租户Id
     *
     * @return
     */
    @Override
    public Expression getTenantId(boolean where) {
        //可以通过过滤器从请求中获取对应租户id 
        Long tenantId = PreTenantContextHolder.getCurrentTenantId();
        log.debug("当前租户为{}", tenantId);
        if (tenantId == null) {
            return new NullValue();
        }
        return new LongValue(tenantId);
    }
    /**
     * 租户字段名
     *
     * @return
     */
    @Override
    public String getTenantIdColumn() {
        return configProperties.getTenantIdColumn();
    }
    /**
     * 根据表名判断是否进行过滤
     * 忽略掉一些表:如租户表(sys_tenant)本身不需要执行这样的处理
     *
     * @param tableName
     * @return
     */
    @Override
    public boolean doTableFilter(String tableName) {
        return configProperties.getIgnoreTenantTables().stream().anyMatch((e) -> e.equalsIgnoreCase(tableName));
    }
}


第三步:配置mybatisPlus的分页插件配置

这里主要实现的功能:

  • 创建SQL解析器集合
  • 创建租户SQL解析器
  • 设置租户处理器,具体处理租户逻辑
@EnableTransactionManagement
@Configuration
@MapperScan({"com.xd.pre.**.mapper"})
public class MyBatisPlusConfig {
     // 租户处理器
    @Autowired
    private PreTenantHandler preTenantHandler;
    /**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        List<ISqlParser> sqlParserList = new ArrayList<>();
        // 攻击 SQL 阻断解析器、加入解析链
        sqlParserList.add(new BlockAttackSqlParser());
        // 多租户拦截
        TenantSqlParser tenantSqlParser = new TenantSqlParser();
        tenantSqlParser.setTenantHandler(preTenantHandler);
        sqlParserList.add(tenantSqlParser);
        paginationInterceptor.setSqlParserList(sqlParserList);
        return paginationInterceptor;
    }
}


配置好之后,不管是查询、新增、修改删除方法,MP都会自动加上租户ID的标识,测试如下:

    @Test
    public void select(){
        List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery().eq(User::getAge, 18));
        users.forEach(System.out::println);
    }

运行sql实例:

DEBUG==>  Preparing: SELECT id, login_name, name, password, 
           email, salt, sex, age, phone, user_type, status,
          organization_id, create_time, update_time, version,
          tenant_id FROM sys_user 
     WHERE sys_user.tenant_id = '001' AND is_delete = '0' AND age = ? 

特定SQL过滤

如果在程序中,有部分SQL不需要加上租户ID的表示,需要过滤特定的sql,可以通过如下两种方式:

方式一:

在配置分页插件中加上配置ISqlParserFilter解析器,如果配置SQL很多,比较麻烦,不建议。

        //有部分SQL不需要加上租户ID的表示,需要过滤特定的sql。如果比较多不建议这里配置。
        /*paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
            @Override
            public boolean doFilter(MetaObject metaObject) {
                MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);
                // 对应Mapper或者dao中的方法
                if("com.erbadagang.mybatis.plus.tenant.mapper.UserMapper.selectList".equals(ms.getId())){
                    return true;
                }
                return false;
            }
        });*/


方式二:

通过租户注解的形式,目前只能作用于Mapper的方法上。特定sql过滤 过滤特定的方法 也可以在userMapper需要排除的方法上加入注解SqlParser(filter=true) 排除 SQL 解析。

package com.erbadagang.mybatis.plus.tenant.mapper;
import com.baomidou.mybatisplus.annotation.SqlParser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.erbadagang.mybatis.plus.tenant.entity.Tenant;
import org.apache.ibatis.annotations.Select;
/**
 * <p>
 * Mapper 接口
 * </p>
 *
 */
public interface TenantMapper extends BaseMapper<Tenant> {
    /**
     * 自定Wrapper, @SqlParser(filter = true)注解代表不进行SQL解析也就没有租户的附加条件。
     *
     * @return
     */
    @SqlParser(filter = true)
    @Select("SELECT count(5) FROM t_tenant ")
    public Integer myCount();
}


参考链接:

https://gitee.com/jinzheyi/yubb-saas

https://gitee.com/xiaoqiangBUG/hello-ruoyi-saas

https://gitee.com/Spring-Pig/RY-SAAS

https://baomidou.com/guide/interceptor-tenant-line.html#tenantlineinnerinterceptor

https://www.jianshu.com/p/1e2cef81bce8

https://docs.microsoft.com/zh-cn/azure/azure-sql/database/saas-tenancy-app-design-patterns

https://baomidou.com/guide/interceptor-tenant-line.html#tenantlineinnerinterceptor

https://gitee.com/baomidou/mybatis-plus-samples/tree/master/mybatis-plus-sample-tenant



目录
相关文章
|
4月前
|
人工智能 运维 监控
现代云平台技术及其应用
在当今数字化时代,云平台技术正日益成为企业转型和创新的关键。本文将探讨现代云平台的定义、架构特点及其在不同行业中的应用案例,旨在帮助读者深入了解并有效应用这一技术。 【7月更文挑战第9天】
164 2
|
1月前
|
Cloud Native 持续交付 云计算
云端新纪元:探索云原生技术的奥秘在当今数字化时代,云计算已成为推动企业创新和增长的关键动力。随着云平台的不断成熟,云原生技术应运而生,以其独特的优势引领着一场新的技术革命。本文将深入探讨云原生的核心概念、主要特点以及它如何改变现代软件开发和部署的方式,为您揭开云原生这一神秘面纱。
云原生是一种构建和运行应用程序的方法,充分利用了云平台的弹性、分布式本质以及声明式基础设施。本文将解析云原生的十二要素,微服务架构的优势,以及容器化、持续集成与持续部署(CI/CD)等核心技术的实践应用。通过深入浅出的方式,让读者理解云原生不仅是一种技术,更是一种文化和方法论,它正在重塑软件开发流程,提高资源利用率和应用系统的可扩展性与容错性。
|
6月前
|
机器学习/深度学习 传感器 自动驾驶
基于深度学习的图像识别技术在自动驾驶系统中的应用构建高效云原生应用:云平台的选择与实践
【5月更文挑战第31天】 随着人工智能技术的飞速发展,深度学习已经成为推动计算机视觉进步的关键力量。特别是在图像识别领域,通过模仿人脑处理信息的方式,深度学习模型能够从大量数据中学习并识别复杂的图像模式。本文将探讨深度学习技术在自动驾驶系统中图像识别方面的应用,重点分析卷积神经网络(CNN)的结构与优化策略,以及如何通过这些技术提高自动驾驶车辆的环境感知能力。此外,文章还将讨论目前所面临的挑战和未来的研究方向。
|
6月前
|
Cloud Native Devops 持续交付
构建未来:以云原生技术打造灵活可靠的云平台
【4月更文挑战第28天】 随着企业数字化转型的不断深入,传统的IT架构已难以满足市场快速变化的需求。云原生技术的兴起为构建高效、可扩展且自动化的云平台提供了新的解决方案。本文将探讨如何利用云原生的核心组件如容器化、微服务、持续集成/持续部署(CI/CD)和DevOps文化来搭建一个现代化的云平台,旨在为企业提供一个灵活、可靠并且能够快速响应市场变化的IT环境。
|
6月前
|
运维 Cloud Native 持续交付
云原生技术:构建灵活高效的云平台
随着云计算技术的快速发展,云原生技术作为一种全新的应用架构范式,正在逐渐成为企业数字化转型的关键。本文将介绍云原生技术的核心概念及其在构建灵活高效的云平台中的重要作用,以及云原生技术对企业业务的影响和意义。
|
存储 XML 缓存
关于云平台虚拟机核心组件 libvirt 热迁移流程及关键参数介绍 | 龙蜥技术
一键了解libvirt虚拟机热迁移整体流程,迁移方式及关键迁移参数作用及影响。
|
传感器 人工智能 小程序
电子班牌是什么?电子班牌云平台源码开发技术?
电子班牌是一种智能交互终端,电子班牌可以解决“走班教学”考勤管理问题,将大数据、物联网和人工智能等新兴技术和教学管理工作融合,提升学校管理水平和管理效率。
|
移动开发 运维 安全
蚂蚁集团联合牵头的行业标准发布,规范移动应用开发云平台技术应用
工信部发布首个移动应用开发云平台行业标准,蚂蚁集团牵头联合行业制定
199 0
|
存储 运维 容灾
《医保行业容灾演练云上技术白皮书》——第三章 医保云容灾建设方案——3.4 云平台建设保障与运维要求
《医保行业容灾演练云上技术白皮书》——第三章 医保云容灾建设方案——3.4 云平台建设保障与运维要求
200 0
|
容灾 网络协议 NoSQL
《医保行业容灾演练云上技术白皮书》——第四章 医保云容灾演练方案——4.2 容灾演练改造——4.2.1 云平台侧容灾改造
《医保行业容灾演练云上技术白皮书》——第四章 医保云容灾演练方案——4.2 容灾演练改造——4.2.1 云平台侧容灾改造

热门文章

最新文章