返回總目錄
本篇目錄
什么是多租戶
維基百科:“軟件多租戶是指一種軟件架構,在這種軟件架構中,軟件的一個實例運行在服務器上并且為多個租戶服務”。一個租戶是一組共享該軟件實例特定權限的用戶。有了多租戶架構,軟件應用被設計成為每個租戶提供一個 專用的實例包括該實例的數據的共享,還可以共享配置,用戶管理,租戶自己的功能和非功能屬性。多租戶和多實例架構相比,多租戶分離了代表不同的租戶操作的多個實例。
多租戶用于創建Saas(Software as-a service)應用(云處理)。有幾種類型的多租戶:
多部署-多數據庫
這實際上不是多租戶。但是,如果我們為每個具有分開數據庫的客戶(租戶)運行該應用的一個實例,那么我們可以在單個服務器上為多個租戶提供服務。我們可以確定該應用的多個實例在相同的服務器環境不會相互沖突。
這個對于一個不是為多租戶設計的已存在應用也是可能的。創建這么一個應用更容易,因為該應用不需要了解多租戶。但這種方式存在安裝,使用和維護問題。
單部署-多數據庫
在這種情況下,我們可以在一個服務器上運行應用的單個實例。對于每個登錄用戶,我們從master database中檢測該用戶的租戶,并獲得該租戶的數據庫信息(連接字符串)。然后我們可以將連接字符串存儲到像session一樣的變量中,同時,使用這個租戶特定的連接字符串執行所有的數據庫操作。
某種程度上,這樣的應用應該設計成多租戶。但是大多數的應用都獨立于多租戶。這種方式也存在一些安裝,使用和維護問題。我們應該為每個租戶創建并維護一個分離的數據庫。
單部署-單數據庫
這是最真實的多租戶架構:我們只將具有單個數據庫應用的單個實例部署到單個服務器上。在(RDBMS)每個表中,都存在一個TenantId(或相似)字段,該字段用于分離每個租戶之間的數據。
這種方法安裝和維護都很簡單,但唯獨創建這么一個應用很難,因為我們必須要阻止一個租戶讀取或寫入其他租戶的數據。我們可以為每個數據庫的讀取(select)操作添加一個TenantId過濾器。而且,我們可以在每次寫入的時候檢查一下該實體是否和當前的租戶相關。這是乏味而易于出錯的,但ABP通過使用自動的數據過濾幫助我們處理這個事情。
如果我們有很多具有大量數據的租戶,那么這種方法可能會有性能問題。我們可以使用關系型數據庫的表分割特征或者將租戶按組分到不同的服務器上。
ABP中的多租戶
ABP提供了創建單部署,單數據庫,多租戶架構的基礎設施。
開啟多租戶
多租戶默認是關閉的。我們可以在模塊的PreInitialize方法中開啟,如下所示:
Configuration.MultiTenancy.IsEnabled = true;
租主vs租戶
首先,我們應該定義多租戶系統中的兩個條目:
- 租主(Host):租主是單例的(只有一個租主)。租主會對創建和管理租戶負責。因此,一個“租主用戶”比所有的租戶等級更高,并獨立于所有租戶,同時還能控制他們。
- 租戶(Tenant):租主的一個客戶,具有自己的用戶角色,權限,設置等。每個租戶都可以完全獨立于其他租戶使用應用。一個多租戶應用會有一個或多個租戶。如果是一個CRM應用,那么不同的租戶也有它們自己的賬戶,契約,產品和訂單。因此,當我們說“**租戶用戶”的時候,意思就是一個租戶擁有的用戶。
Session
ABP定義了一個獲取當前用戶和租戶id的IAbpSession接口。該接口用于多租戶獲取當前的租戶id。因此,它可以基于當前的租戶id過濾數據。ABP中有以下規則:
- 如果UserId和TenantId都是null,那么當前的用戶沒有登錄到系統。因此,我們可以不知道當前用戶是否是一個租主用戶還是一個租戶用戶。在這種情況下,用戶不能訪問授權的內容。
- 如果UserId不是null,TenantId是null,那么當前用戶是一個租主用戶。
- 如果UserId不是null,TenantId也不是null,那么當前用戶是租戶用戶。
更多關于session的信息請看后面的Session一節。
數據過濾器
當從數據庫中檢索實體時,我們必須添加一個TenantId過濾器來只獲得當前的租戶實體。當你為實體實現了IMustHaveTenant和IMayHaveTenant兩個接口之一時,ABP會自動地完成數據過濾。
IMustHaveTenant接口
該接口通過定義TenantId屬性來區分不同租戶的實體。一個實現了IMustHaveTenant的實體例子如下:
public class Product : Entity, IMustHaveTenant
{
public int TenantId { get; set; }
public string Name { get; set; }
//...其他屬性
}
這樣,ABP知道這是一個特定租戶的實體,并且會自動地將一個租戶的實體從其他實體中分離出來。
IMayHaveTenant接口
我們可能需要在租戶和租戶之間共享一個實體類型。因此,一個實體可能會被一個租戶或租主擁有。IMayHaveTenant接口也定義了TenantId(類似于IMustHaveTenant),但在這種情況下是nullable。實現了IMayHaveTenant的一個實體例子:
public class Role : Entity, IMayHaveTenant
{
public int? TenantId { get; set; }
public string RoleName { get; set; }
//...其他屬性
}
我們可能會使用相同的Role類來存儲租主角色和租戶角色。這種情況下,TenantId表明這是一個租戶實體還是一個租主實體。null值表示這是一個租主實體,非null值表示這被一個租戶擁有,該租戶的Id是TenantId。
IMayHaveTenant不像IMustHaveTenant一樣常用。比如,一個Product類可以不實現IMayHaveTenant接口,因為Product和實際的應用功能相關,和管理租戶不相干。因此,要小心使用IMayHaveTenant接口,因為它更難維護租戶和租主共享的代碼。
保存實體
一個租戶用戶不應該創建或編輯其他租戶的實體。如果相關的數據過濾器開啟了,那么ABP會檢查該實體相對于數據庫的改變。
想要獲得更多關于數據過濾器的信息,請看后面關于數據過濾器的博客。
|