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中運行。

返回主目錄

相關文章
相關標籤/搜索