多租户html
“软件多租户技术指的是一种软件架构,这种架构可使用软件的单实例运行并为多个租户提供服务。租户是经过软件实例的特定权限共享通用访问的一组用户。使用多租户架构,软件应用为每一个租户提供实例的专用共享,包括实例的数据、配置、用户管理、租户的私有功能和非功能属性。多租户与多实例架构造成对比,将软件实例的行为根据不一样的租户分割开来。”(维基百科)数据库
简单来讲,多租户是一种建立SaaS(软件即服务)应用的技术。缓存
有许多不一样的多租户数据库和部署方法:cookie
这种实际上不是多租户的。可是,若是咱们为每一个客户使用分离的数据库运行应用的一个实例,能够在一个单独的服务器上为多租户提供服务。咱们只要保证在一样的服务器环境下,应用的多个实例之间不会相互冲突。架构
这种状况一样适用于已经存在并无设计为多租户的应用。建立这种对多租户没有感知的应用是容易的。可是使用这种方法须要安装、使用和维护众多的问题。dom
在这种方法里,在服务器上运行应用的单独实例。使用一个主数据库存储租户的元数据(如租户名称和子域),每一个租户使用隔离的数据库。一旦咱们呢识别出当前的租户(例如从一个子域或登陆表单),而后就切换到当前租户的数据库执行操做。
在这种方法里,应用程序须要在必定程度上设计成多租户的。可是应用的大部分仍然与多租户是不相干的。
咱们须要为每一个租户建立和维护一个分离的数据库,包括数据库迁移。若是咱们有许多专用数据库的客户,在应用升级时将会花费咱们很长的时间迁移数据库模式。由于咱们为租户分离了数据库,咱们能够备份租户的数据库而不受其余租户的影响。若是租户须要的话,咱们也能够把租户的数据库引动到一个更强劲的服务器上。
这种是最真实的多租户架构:咱们只须要部署在一个单独服务器上部署应用的一个实例。咱们在每一个表里都有一个TenantId(或者类似的)字段用来区分租户间的数据。
这种是最易安装和维护的。可是这种应用很难建立。由于,咱们必须禁止一个租户读写另外一个租户的数据。咱们能够天剑TenantId字段为每一个数据库的读操做。若是这个实体和当前租户相关,咱们能够每次写都检查。这样是冗长乏味且容易犯错误的。ABP使用自动的数据过滤来帮助咱们实现。
若是有许多租户且数据庞大的话会形成性能问题。咱们可使用表分区或其余数据库特征克服这个问题。
咱们想正常的在单独数据库中存储租户数据,可是也想为但愿使用单独数据库的租户建立单独数据库。例如,咱们把有大数据的租户存储到他们本身的数据库中,其余的租户存储在单独的一个数据库中。
最后,咱们想讲应用部署在多个服务器(如web farms)上以得到更好的性能、高可用和可扩展性。这是独立于数据库方式的。
ABP能够以上面描述的任何场景方式工做
多租户默认是不可用的。咱们能够在模块的PreInitialize方法按以下的方式使其可用:
ABP定义了IabpSession接口获取当前用户和租户的ids。在多租户系统中,默认使用这个接口来获取当前租户的id。所以,它能够基于当前租户的id来过滤数据。咱们有如下规则:
参见session documention章节了解更多关于session的信息。
既然全部的租户用户都使用一样的应用,咱们应该有区分当前请求是哪一个租户的方法。默认的会话实现按照下面给定的顺序使用不一样的方法查找和当前请求相关的租户:
若是以上全部的尝试都没有解决TenantId,当前的请求者会被认为一个主人。租户解决者是能够扩展的。能够向 Configuration.MultiTenancy.Resolvers 集合中添加解决者,也能够从中移除解决者。
最后一个关于解决者的事情是:为了提高性能,在相同的请求中解决的租户id是被缓存的。因此,解决这在请求中只会被执行一次(且只有当前用户没有登陆的时候)。
DomainTenantResolveContributer使用ItenantStore经过租户名称查找租户id。ItenantStore默认实现是NullTenantStore,它不包含任何的租户,查询的时候返回null。能够从新实现这个接口以便从任何数据源查询租户。Module Zero实现方式是从他的租户管理中获取租户。
对于多租户单数据库的方法,必须添加一个TenantId过滤器,这样从数据库里提取数据的时候只获取当前租户的实体。当实体实现了IMustHaveTenant和IMayHaveTenant两个接口中的任意一个接口时,ABP会自动过滤。
这个接口使用定义的TenantId属性区分不一样租户的实体。实现IMustHaveTenant接口的实例以下:
public class Product : Entity, IMustHaveTenant { public int TenantId { get; set; } public string Name { get; set; } //...other properties }
这样,ABP知道这是一个租户特定的实体,自动和其余租户的实体隔离开。
咱们可能须要在租户和主人之间共享一个实体类型。因此,一个实体可能属于一个租户或主人。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方法确保咱们在给定的租户数据上工做,独立于数据库架构:
若是咱们不使用SetTenantId,它会从会话中获取tenantid,如以前所说。这有一些建议和最佳实践: