“软件多租户”指的是一种软件架构,一个软件实例在一个服务器上运行,但为多个租户服务。租户们对软件实例有通用的访问入口,但是每个租户都有特定的权限。

在多租户体系架构中,用程序旨在为每个租户提供一个专用的实例共享,包括其数据*、配置、用户管理、租户个人功能和非功能属性。

多租户与多实例体系结构形成对比,在多实例体系结构中,独立的软件实例代表不同的租户操作”(维基百科)。

简而言之,多租户是一种用于创建SaaS(软件即服务)应用程序的技术。

数据库和部署架构

有几种不同的多租户数据库和部署方法:

多个部署-多个数据库(Multiple Deployment - Multiple Database)

这实际上不是多租户,但是如果我们为每个客户(租户)运行一个应用程序实例,并使用一个独立的数据库,我们可以在一个服务器上为多个租户服务。我们只需要确保应用程序的多个实例在同一个服务器环境中不会相互冲突。

对于已存在的但没有被设计为多租户的应用程序也是可能的。创建这样的应用程序可能更容易,因为不需要考虑多租户,但是会有安装,使用以及维护等各种问题。

单一部署-多个数据库(Single Deployment - Multiple Database)

在这种方法中,我们在服务器上运行应用程序的单个实例。我们有一个主(主机)数据库来存储租户元数据(比如租户名称和子域),每个租户有一个单独的数据库。一旦我们确定了当前的租户(入从子域或者用户登录提交的form),我们就可以切换到该租户的数据库来执行操作。

在这种方法中,应用程序应该在某种程度上被设计为多租户,但是应用程序大部分可以独立于它。

我们为每个租户创建和维护一个单独的数据库,包括数据库迁移。如果我们有许多拥有专用数据库的客户,那么在应用程序更新期间迁移数据库模式可能需要很长时间。由于每个租户都有一个单独的数据库,因此可以将其数据库与其他租户分开备份。如果租户需要,我们还可以将租户数据库移动到更强大的服务器。

单一部署-单个数据库(Single Deployment - Single Database)

这是最理想的多租户体系结构:我们只将应用程序的一个实例和一个数据库部署到一个服务器上。我们在每个表(对于RDBMS)中都有一个TenantId(或类似的)字段,用于将租户的数据与其他数据隔离开来。

这种类型的应用程序易于安装和维护,但创建起来比较困难。这是因为我们必须防止租户读取或写入其他租户数据。我们可以为每个数据库读取(选择)操作添加一个TenantId过滤器。我们也可以在每次写的时候检查看看这个实体是否与当前租户相关。这既乏味又容易出错。然而,ASP.NET Boilerplate帮助我们在这里使用自动数据过滤。

如果我们有许多具有大数据集的租户,这种方法可能存在性能问题。我们可以使用表分区或其他数据库特性来克服这个问题。

单一部署-混合数据库(Single Deployment - Hybrid Databases)

通常,我们可能希望将租户存储在单个数据库中,但可能希望为所需的租户创建单独的数据库。例如,我们可以将具有大数据的租户存储在自己的数据库中,但将所有其他租户存储在一个数据库中。

多个部署——单个/多个/混合数据库(Multiple Deployment - Single/Multiple/Hybrid Database)

最后,为了获得更好的应用程序性能、高可用性和/或可伸缩性,我们可能希望将应用程序部署到多个服务器(比如web farm)。这与数据库方法无关。

ASP.NET Boilerplate中的多租户

ASP.NET Boilerplate可用于上面描述的所有场景。

启用多租户

框架默认是禁用多租户的,我们可以在模块的预初始(PreInitialize)方法中启用它,如下图所示:

Configuration.MultiTenancy.IsEnabled = true; 

注意:在ASP.NET Core 和 ASP.NET MVC 5.x 启动模板中都支持多租户。

主机vs租户(Host vs Tenant)

我们定义了在多租户系统中使用的两个术语:

租户:客户拥有自己的用户、角色、权限、设置……并使用与其他租户完全隔离的应用程序。多租户应用程序将有一个或多个租户。如果这是一个CRM应用程序,不同的租户都有自己的帐户、联系人、产品和订单。所以当我们说“租户用户”时,我们指的是租户拥有的用户。

主机:主机是单例的(只有一个主机)。主机负责创建和管理租户。“主机用户”处于更高级别,独立于所有租户,可以控制它们。

会话(Session)

ASP.NET Boilerplate定义了IAbpSession接口来获取当前用户和租户id,此接口用于多租户在默认情况下获取当前租户的id。因此,它可以根据当前租户的id过滤数据。规则如下:

  • 如果UserId 和 TenantId都是null,则当前用户不会登录到系统。我们无法知道它是主机用户还是租户用户。在这种情况下,用户无法访问授权内容。
  • 如果UserId不是null,而TenantId是null,则当前用户是主机用户。
  • 如果UserId不是null,而TenantId也不是null,则当前用户是租户用户。
  • 如果UserId为null,而TenantId不是null,这意味着我们知道当前的租户,但是当前的请求没有被授权(用户没有登录)。

确定当前租户

由于所有租户都使用相同的应用程序,我们应该有一种方法来区分当前请求的租户。默认会话实现(ClaimsAbpSession)使用不同的方法查找与当前请求相关的租户,顺序如下:

  1. 如果用户已经登录,它会从当前的claims获得TenantId。Claim名称是 http://www.aspnetboilerplate.com/identity/claims/tenantId(tenantId一般是一个整数值),如果在claims中没有找到,则认为是主机用户。
  2. 如果用户尚未登录,那么它将尝试从租户解析贡献者解析TenantId。有3个预定义的租户贡献者,并且按照给定的顺序运行(第一个成功的解析器'wins'): 
    1. DomainTenantResolveContributer:尝试从url解析租赁名称,通常是从域或子域解析。您可以在模块的预初始化(PreInitialize)方法中配置域格式(像Configuration.Modules.AbpWebCommon().MultiTenancy.DomainFormat = "{0}.mydomain.com";)。如果域格式是“{0}.mydomain.com”,比如请求的当前主机是acme.mydomain.com,则租户名称解析为“acme”。下一步是查询ITenantStore以根据给定的租户名称找到TenantId。如果找到了租户,则将其解析为当前的TenantId。
    2. HttpHeaderTenantResolveContributer:尝试从“Abp.TenantId”标头值(如果存在)解析TenantId。这是在Abp.MultiTenancy.MultiTenancyConsts.TenantIdResolveKey中定义的常数。
    3. HttpCookieTenantResolveContributer:尝试从“Abp.TenantId”Cookie值(如果存在)解析TenantId。这是在Abp.MultiTenancy.MultiTenancyConsts.TenantIdResolveKey中定义的常数。

如果这些尝试都不能解析TenantId,那么当前请求者被认为是主机。租户解析器是可扩展的。您可以向Configuration.MultiTenancy.Resolvers collection中添加解析器,或删除现有的解析器。

出于性能原因,在同一请求期间缓存已解析的租户id。解析器在请求中执行一次,且仅在当前用户尚未登录时执行。

租户存储

DomainTenantResolveContributer使用ITenantStore根据租户名称查找租户id。ITenantStore的默认实现是NullTenantStore,它不包含任何租户,对于查询返回null。您可以实现并替换它来查询来自任何数据源的租户。

数据过滤

对于多租户单数据库方法,我们必须添加一个TenantId过滤器,以便在从数据库检索实体时只获取当前租户的实体。如果你的实体实现IMustHaveTenant IMayHaveTenant任何一个接口,ASP.NET Boilerplate会自动的帮你做这个。

IMustHaveTenant

这个接口通过定义TenantId属性来区分不同租户的实体。实现IMustHaveTenant的示例实体:

public class Product : Entity, IMustHaveTenant
{
public int TenantId { get; set; } public string Name { get; set; } //...other properties
}

这样,ASP.NET Boilerplate知道这是一个特定租户的实体,并自动将租户的实体与其他租户隔离开来。

IMayHaveTenant

我们可能需要在主机和租户之间共享一个实体类型。因此,实体可能由租户或主机拥有。IMayHaveTenant接口也定义了TenantId(类似于IMustHaveTenant),但在本例中它是可空的。实现IMayHaveTenant的示例实体:

public class Role : Entity, IMayHaveTenant
{
public int? TenantId { get; set; } public string RoleName { get; set; } //...other properties
}

我们可以使用相同的角色类来存储主机角色和租户角色。在本例中,TenantId属性表示这是主机实体还是租户实体。空值表示这是一个主机实体,非空值表示该实体由租户所有,其中Id是TenantId。

还有,

IMayHaveTenant并不像IMustHaveTenant那样常用。例如,产品类不能是IMayHaveTenant,因为产品与实际应用程序功能相关,而与管理租户无关。所以要小心使用IMayHaveTenant接口,因为维护主机和租户共享的代码比较困难。

当您将实体类型定义为IMustHaveTenantIMayHaveTenant时,总是在创建新实体时设置TenantId(ASP.NET Boilerplate 试图从当前TenantId设置它,在某些情况下可能不可能,特别是对于IMayHaveTenant实体)。大多数情况下,这是处理TenantId属性的惟一一点。在编写LINQ时,不需要显式地编写TenantId过滤器,因为它是自动过滤的。

在主机和租户之间切换

在处理多租户应用程序数据库时,我们可以获得当前的租户。默认情况下,它是从IAbpSession(如前所述)获得的。我们可以更改此行为并切换到另一个租户的数据库。例如:

public class ProductService : ITransientDependency
{
private readonly IRepository<Product> _productRepository;
private readonly IUnitOfWorkManager _unitOfWorkManager; public ProductService(IRepository<Product> productRepository, IUnitOfWorkManager unitOfWorkManager)
{
_productRepository = productRepository;
_unitOfWorkManager = unitOfWorkManager;
} [UnitOfWork]
public virtual List<Product> GetProducts(int tenantId)
{
using (_unitOfWorkManager.Current.SetTenantId(tenantId))
{
return _productRepository.GetAllList();
}
}
}

SetTenantId确保我们正在处理给定租户的数据,独立于数据库体系结构:

  • 如果给定的租户有一个专用数据库,它将切换到该数据库并从中获取产品。
  • 如果给定的租户没有专用的数据库(例如,单一数据库方法),它将添加自动TenantId过滤器,只查询该租户的产品。

如果我们不使用SetTenantId,它会从会话中获取tenantId。这里有一些指导方针和最佳实践:

  • 使用SetTenantId(null)切换到主机。
  • 如果没有特殊情况,在using块中使用SetTenantId(如示例中所示)。通过这种方式,它在using块的末尾自动恢复tenantId,调用GetProducts方法的代码与前面一样工作。
  • 如果需要,可以在嵌套块中使用SetTenantId。
  • 因为_unitOfWorkManager.Current只能在工作单元中可用,请确保您的代码在UOW中运行。

ABP之多租户的更多相关文章

  1. X-Admin&ABP框架开发-租户管理

    软件即服务概念的推动,定制化到通用化的发展,用一套代码完成适应不同企业的需求,利用多租户技术可以去做到这一点.ABP里提供了多租户这一概念并且也在Zero模块中实现了这一概念. 一.多租户的概念 单部 ...

  2. ABP框架 - 多租户

    文档目录 本节内容: 什么是多租户 多部署 - 多数据库 单部署 - 多数据库 单部署 - 单数据库 单部署 - 混数据库 多部署 - 单/多/混 数据库 ABP中的多租户 启用多租户 宿主与租户 会 ...

  3. ABP Zero 多租户管理

    ABPZero - 多租户管理 启用多租户 ASP.NET Boilerplate和module-zero可以运行多租户或单租户模式.多租户默认为禁用.我们可以在我们的模块PreInitialize方 ...

  4. ABP vNext 实现租户Id自动赋值插入

    背景 在使用ABP vNext过程中,因为我们的用户体系庞大,所以一直与其他业务同时开发,在开发其他业务模块时,我们一直存在着误区:认为ABP vNext 自动处理了数据新增时的租户Id(Tenant ...

  5. ABP与多租户

    ABP简介 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程 ...

  6. 详解ABP框架的多租户

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:ABP框架对多租户场景提供了很好的支持,内建了多租户的处理机制,今天我们来深入解析一下 ...

  7. ABP官方文档翻译 1.5 多租户

    多租户 什么是多租户? 数据库和部署架构 多部署-多数据库 单部署-多数据库 单部署-单数据库 单部署-混合数据库 多部署-单/多/混合数据库 ABP的多租户 启用多租户 租主和租户 会话 决定当前租 ...

  8. [Abp vNext 源码分析] - 19. 多租户

    一.简介 ABP vNext 原生支持多租户体系,可以让开发人员快速地基于框架开发 SaaS 系统.ABP vNext 实现多租户的思路也非常简单,通过一个 TenantId 来分割各个租户的数据,并 ...

  9. 吐槽一下Abp的用户和租户管理模块

    1. 背景 ASP.NET Core 基于声明的访问控制到底是什么鬼? 聊到基于声明的身份认证将 身份和签发机构分离,应用程序信任签发机构,故认可签发的身份信息. -- --- --- --- Cla ...

随机推荐

  1. Hadoop系列003-Hadoop运行环境搭建

    本人微信公众号,欢迎扫码关注! Hadoop运行环境搭建 1.虚拟机网络模式设置为NAT 2.克隆虚拟机 3.修改为静态ip 4. 修改主机名 5.关闭防火墙 1)查看防火墙开机启动状态 chkcon ...

  2. 关于pip安装时提示"pkg_resources.DistributionNotFound"错误

    使用pip install --upgrade pip升级pip中途失败,再次安装pip,完成后出现如下错误: 尝试重新安装pip也不行,同样会出现上述问题. 此时我们查看/usr/bin/pip文件 ...

  3. springboot~rabbitmq自己通过UI手动发布队列需要注意的地方

    springboot里发布队列消息 为了兼容性和可读性更好,我们一般使用json字符串做为数据载体. public void decreaseCallMonitor(CallMonitorInfo c ...

  4. Scrum Mastery:有效利用组织的5个步骤

    组织以什么样的方式能最大限度的发挥Scrum的优势?组织在哪些方面阻碍了个人的发展?Scrum是一种能使业务变得敏捷的框架.而组织恰恰需要变得敏捷.只是,组织本身有时候并没有足够的能力来帮助Scrum ...

  5. Asp.Net Core 轻松学-在.Net Core 使用缓存和配置依赖策略

    前言     几乎在所有的应用程序中,缓存都是一个永恒的话题,恰当的使用缓存可以有效提高应用程序的性能:在某些业务场景下,使用缓存依赖会有很好的体验:在 Asp.Net Core 中,支持了多种缓存组 ...

  6. RDIFramework.NET V3.3 WinForm版角色授权管理新增角色对操作权限项、模块起止生效日期的设置

    在实际应用在我们可能会有这样的需求,某个操作权限项(按钮)或菜单在某个时间范围内可以让指定角色访问.此时通过我们的角色权限扩展设置就可以办到. 在我们框架V3.3 WinForm版全新增加了角色权限扩 ...

  7. 原子操作&普通锁&读写锁

    一:原子操作CAS(compare-and-swap) 原子操作分三步:读取addr的值,和old进行比较,如果相等,则将new赋值给*addr,他能保证这三步一起执行完成,叫原子操作也就是说它不能再 ...

  8. HTTP协议及其相关

    URI.URL.URN URL,统一资源定位符,用来标识某一处资源的地址,必须包含协议和域名,协议指的是HTTP.FTP.WS.file等协议,域名就是常说的网址,比如www.baidu.com ,其 ...

  9. C#工具:加密解密帮助类

    using System; using System.IO; using System.Security.Cryptography; using System.Text; //加密字符串,注意strE ...

  10. javascript小记一则:今天在写VS2005——.NET程序时,写的一个JS图片示例案例

    源码如下,如遇调试问题,可以找我解决: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" &quo ...