ABP官方文档翻译 1.5 多租户

多租户html

什么是多租户?web

  “软件多租户技术指的是一种软件架构,这种架构可使用软件的单实例运行并为多个租户提供服务。租户是经过软件实例的特定权限共享通用访问的一组用户。使用多租户架构,软件应用为每一个租户提供实例的专用共享,包括实例的数据、配置、用户管理、租户的私有功能和非功能属性。多租户与多实例架构造成对比,将软件实例的行为根据不一样的租户分割开来。”(维基百科)数据库

   简单来讲,多租户是一种建立SaaS(软件即服务)应用的技术。缓存

数据库和部署架构服务器

  有许多不一样的多租户数据库和部署方法:cookie

多个部署,多个数据库session

  这种实际上不是多租户的。可是,若是咱们为每一个客户使用分离的数据库运行应用的一个实例,能够在一个单独的服务器上为多租户提供服务。咱们只要保证在一样的服务器环境下,应用的多个实例之间不会相互冲突。架构

  这种状况一样适用于已经存在并无设计为多租户的应用。建立这种对多租户没有感知的应用是容易的。可是使用这种方法须要安装、使用和维护众多的问题。dom

单个部署,多个数据库ide

  在这种方法里,在服务器上运行应用的单独实例。使用一个主数据库存储租户的元数据(如租户名称和子域),每一个租户使用隔离的数据库。一旦咱们呢识别出当前的租户(例如从一个子域或登陆表单),而后就切换到当前租户的数据库执行操做。

  在这种方法里,应用程序须要在必定程度上设计成多租户的。可是应用的大部分仍然与多租户是不相干的。

  咱们须要为每一个租户建立和维护一个分离的数据库,包括数据库迁移。若是咱们有许多专用数据库的客户,在应用升级时将会花费咱们很长的时间迁移数据库模式。由于咱们为租户分离了数据库,咱们能够备份租户的数据库而不受其余租户的影响。若是租户须要的话,咱们也能够把租户的数据库引动到一个更强劲的服务器上。

单个部署,单个数据库

  这种是最真实的多租户架构:咱们只须要部署在一个单独服务器上部署应用的一个实例。咱们在每一个表里都有一个TenantId(或者类似的)字段用来区分租户间的数据。

  这种是最易安装和维护的。可是这种应用很难建立。由于,咱们必须禁止一个租户读写另外一个租户的数据。咱们能够天剑TenantId字段为每一个数据库的读操做。若是这个实体和当前租户相关,咱们能够每次写都检查。这样是冗长乏味且容易犯错误的。ABP使用自动的数据过滤来帮助咱们实现。

  若是有许多租户且数据庞大的话会形成性能问题。咱们可使用表分区或其余数据库特征克服这个问题。

单个部署,混合数据库

  咱们想正常的在单独数据库中存储租户数据,可是也想为但愿使用单独数据库的租户建立单独数据库。例如,咱们把有大数据的租户存储到他们本身的数据库中,其余的租户存储在单独的一个数据库中。

多个部署,单个/多个/混合数据库

  最后,咱们想讲应用部署在多个服务器(如web farms)上以得到更好的性能、高可用和可扩展性。这是独立于数据库方式的。

ABP的多租户

  ABP能够以上面描述的任何场景方式工做

设置多租户可用

  多租户默认是不可用的。咱们能够在模块的PreInitialize方法按以下的方式使其可用:

Configuration.MultiTenancy.IsEnabled = true; 

租主和租户

  首先,须要定义在多租户系统中的两个术语:

  • 租户:拥有本身的用户,角色,权限,设置......彻底独立于其余租户使用应用程序。多租户应用有一个或多个租户。若是是一个CRM应用,不一样的租户拥有他们本身的帐户,联系人,产品和订单。因此当咱们说一个“租户用户”的时候,指的是租户拥有的一个用户。
  • 租主:租主是单例的(there is a single host)。租主负责建立和管理租户。因此,一个“租主用户”是高等级的和独立于其余全部的租户而且能够控制他们。

会话

  ABP定义了IabpSession接口获取当前用户和租户的ids。在多租户系统中,默认使用这个接口来获取当前租户的id。所以,它能够基于当前租户的id来过滤数据。咱们有如下规则:

  • 若是UserId和TenantId都是null,当前用户没有登陆到系统。因此,咱们不知道当前用户是一个租主用户仍是一个租户用户。在这种状况下,用户不能方位受权内容。
  • 若是UserId不为null,TenantId为null,而后咱们能够知道当前用户是一个租主用户。
  • 若是UserId不为null,TenantId也不为null,咱们能够知道当前用户是一个租户用户。
  • 若是UserId为null,TenantId不为null,意味着咱们能够知道当前为租户,但当前请求没有受权(用户没有登陆)。参见下一部分了解如何决定当前租户。

  参见session documention章节了解更多关于session的信息。

决定当前租户

  既然全部的租户用户都使用一样的应用,咱们应该有区分当前请求是哪一个租户的方法。默认的会话实现按照下面给定的顺序使用不一样的方法查找和当前请求相关的租户:

  1. 若是用户已经登陆则从当前的声明中获取TenantId。声明名称为http://www.aspnetboilerplate.com/identity/claims/tenantId 应该会包含一个整型值。若是没在声明中找到则假定当前用户是主人用户。
  2. 若是用户没有登陆,ABP会尝试从租户解决贡献者中解决TenantId的问题你。有三个预约义的租户贡献者,以规定的顺序运行(第一个成功的解决者将会胜利):
    1. DomainTenantResolveContributer:尝试从URL中获取租户名称,一般是从域名或子域名。能够再模块的PreInitialize方法中配置域名形式(如 Configuration.Modules.AbpWebCommon().MultiTenancy.DomainFormat = "{0}.mydomain.com")。若是域名形式是“{0}.mydomain.com”且请求的当前主机是acme.mydomain.com,name租户名称就是“acme”。而后下一步就是根据租户名查询ItenantStore找到TenantId。若是找到了租户,就做为当前的TenantId。
    2. 2.      HttpHeaderTenantResolveContributer:尝试从”Abp.TenantId”数据头中解决,若是存在的话(这是定义在Abp.MultiTenancy.MultiTenancyConsts.TenantIdResolveKey中的一个常数)。
    3. HttpCookieTenantResolveContributer:尝试从”Abp.TenantId“cookie值中解决,若是存在的话(使用上面解释的相同的常量)。

  若是以上全部的尝试都没有解决TenantId,当前的请求者会被认为一个主人。租户解决者是能够扩展的。能够向 Configuration.MultiTenancy.Resolvers 集合中添加解决者,也能够从中移除解决者。

  最后一个关于解决者的事情是:为了提高性能,在相同的请求中解决的租户id是被缓存的。因此,解决这在请求中只会被执行一次(且只有当前用户没有登陆的时候)。

租户仓库

  DomainTenantResolveContributer使用ItenantStore经过租户名称查找租户idItenantStore默认实现是NullTenantStore,它不包含任何的租户,查询的时候返回null。能够从新实现这个接口以便从任何数据源查询租户。Module Zero实现方式是从他的租户管理中获取租户。

数据过滤器

  对于多租户单数据库的方法,必须添加一个TenantId过滤器,这样从数据库里提取数据的时候只获取当前租户的实体。当实体实现了IMustHaveTenant和IMayHaveTenant两个接口中的任意一个接口时,ABP会自动过滤。

IMustHaveTenant 接口

  这个接口使用定义的TenantId属性区分不一样租户的实体。实现IMustHaveTenant接口的实例以下:

复制代码
public class Product : Entity, IMustHaveTenant
{
    public int TenantId { get; set; }

    public string Name { get; set; }

    //...other properties
}
复制代码

  这样,ABP知道这是一个租户特定的实体,自动和其余租户的实体隔离开。

IMayHaveTenant 接口

  咱们可能须要在租户和主人之间共享一个实体类型。因此,一个实体可能属于一个租户或主人。ImayHaveTenant接口也定义了TenantId
(和ImustHaveTenant类似),可是子在这种状况下它是nullable。实现了此接口的示例以下:
复制代码
public class Role : Entity, IMayHaveTenant
{
    public int? TenantId { get; set; }

    public string RoleName { get; set; }

    //...other properties
}
复制代码
  咱们可能会使用相同的角色类存储主人角色和租户角色。在这种状况下,TenantId属性会断定当前实体是主人实体仍是租户实体。Null意味
着是租主实体,non-null值意味着这个实体属于租户,当前值即为TenantId。

附加说明

  ImayHaveTenant并不如ImustHaveTenant通用。例如,产品类不能继承ImayHaveTenant,由于产品与实际应用功能关联,不予管理租户关联。因此,当心使用ImayHaveTenant接口,由于很难维护被主人和租户共享的代码。

  当定义ImustHaveTenant或ImayHaveTenant的实体类型时,当建立一个新实体时(当ABP尝试从当前TenantId设置它时,在某些状况下是不可能的,尤为是对于ImayHaveTenant实体)最好老是设置TenantId。大多数的时候,这是处理TenantId属性时惟一关注的点。当写LINQ时,不须要显示的在Where条件中写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。这样,ABP会自动在using块结束时恢复tenantid,GetProducts方法能够和之前同样工做。
  • 若是须要的话能够在嵌套块中使用SetTenantId。
  • 既然_unitOfWorkManager.Current只能在工做单元中使用,确保代码在UOW中运行。

返回主目录

相关文章
相关标签/搜索