「軟件多租戶」指的是一種軟件架構,一個軟件實例在一個服務器上運行,但爲多個租戶服務。租戶們對軟件實例有通用的訪問入口,可是每一個租戶都有特定的權限。web
在多租戶體系架構中,用程序旨在爲每一個租戶提供一個專用的實例共享,包括其數據*、配置、用戶管理、租戶我的功能和非功能屬性。數據庫
多租戶與多實例體系結構造成對比,在多實例體系結構中,獨立的軟件實例表明不一樣的租戶操做」(維基百科)。緩存
簡而言之,多租戶是一種用於建立SaaS(軟件即服務)應用程序的技術。服務器
有幾種不一樣的多租戶數據庫和部署方法:架構
這實際上不是多租戶,可是若是咱們爲每一個客戶(租戶)運行一個應用程序實例,並使用一個獨立的數據庫,咱們能夠在一個服務器上爲多個租戶服務。咱們只須要確保應用程序的多個實例在同一個服務器環境中不會相互衝突。框架
對於已存在的但沒有被設計爲多租戶的應用程序也是可能的。建立這樣的應用程序可能更容易,由於不須要考慮多租戶,可是會有安裝,使用以及維護等各類問題。dom
在這種方法中,咱們在服務器上運行應用程序的單個實例。咱們有一個主(主機)數據庫來存儲租戶元數據(好比租戶名稱和子域),每一個租戶有一個單獨的數據庫。一旦咱們肯定了當前的租戶(入從子域或者用戶登陸提交的form),咱們就能夠切換到該租戶的數據庫來執行操做。ide
在這種方法中,應用程序應該在某種程度上被設計爲多租戶,可是應用程序大部分能夠獨立於它。性能
咱們爲每一個租戶建立和維護一個單獨的數據庫,包括數據庫遷移。若是咱們有許多擁有專用數據庫的客戶,那麼在應用程序更新期間遷移數據庫模式可能須要很長時間。因爲每一個租戶都有一個單獨的數據庫,所以能夠將其數據庫與其餘租戶分開備份。若是租戶須要,咱們還能夠將租戶數據庫移動到更強大的服務器。大數據
這是最理想的多租戶體系結構:咱們只將應用程序的一個實例和一個數據庫部署到一個服務器上。咱們在每一個表(對於RDBMS)中都有一個TenantId(或相似的)字段,用於將租戶的數據與其餘數據隔離開來。
這種類型的應用程序易於安裝和維護,但建立起來比較困難。這是由於咱們必須防止租戶讀取或寫入其餘租戶數據。咱們能夠爲每一個數據庫讀取(選擇)操做添加一個TenantId過濾器。咱們也能夠在每次寫的時候檢查看看這個實體是否與當前租戶相關。這既乏味又容易出錯。然而,ASP.NET Boilerplate幫助咱們在這裏使用自動數據過濾。
若是咱們有許多具備大數據集的租戶,這種方法可能存在性能問題。咱們可使用表分區或其餘數據庫特性來克服這個問題。
一般,咱們可能但願將租戶存儲在單個數據庫中,但可能但願爲所需的租戶建立單獨的數據庫。例如,咱們能夠將具備大數據的租戶存儲在本身的數據庫中,但將全部其餘租戶存儲在一個數據庫中。
最後,爲了得到更好的應用程序性能、高可用性和/或可伸縮性,咱們可能但願將應用程序部署到多個服務器(好比web farm)。這與數據庫方法無關。
ASP.NET Boilerplate可用於上面描述的全部場景。
框架默認是禁用多租戶的,咱們能夠在模塊的預初始(PreInitialize)方法中啓用它,以下圖所示:
Configuration.MultiTenancy.IsEnabled = true;
注意:在ASP.NET Core 和 ASP.NET MVC 5.x 啓動模板中都支持多租戶。
咱們定義了在多租戶系統中使用的兩個術語:
租戶:客戶擁有本身的用戶、角色、權限、設置……並使用與其餘租戶徹底隔離的應用程序。多租戶應用程序將有一個或多個租戶。若是這是一個CRM應用程序,不一樣的租戶都有本身的賬戶、聯繫人、產品和訂單。因此當咱們說「租戶用戶」時,咱們指的是租戶擁有的用戶。
主機:主機是單例的(只有一個主機)。主機負責建立和管理租戶。「主機用戶」處於更高級別,獨立於全部租戶,能夠控制它們。
ASP.NET Boilerplate定義了IAbpSession接口來獲取當前用戶和租戶id,此接口用於多租戶在默認狀況下獲取當前租戶的id。所以,它能夠根據當前租戶的id過濾數據。規則以下:
因爲全部租戶都使用相同的應用程序,咱們應該有一種方法來區分當前請求的租戶。默認會話實現(ClaimsAbpSession)使用不一樣的方法查找與當前請求相關的租戶,順序以下:
若是這些嘗試都不能解析TenantId,那麼當前請求者被認爲是主機。租戶解析器是可擴展的。您能夠向Configuration.MultiTenancy.Resolvers collection中添加解析器,或刪除現有的解析器。
出於性能緣由,在同一請求期間緩存已解析的租戶id。解析器在請求中執行一次,且僅在當前用戶還沒有登陸時執行。
DomainTenantResolveContributer使用ITenantStore根據租戶名稱查找租戶id。ITenantStore的默認實現是NullTenantStore,它不包含任何租戶,對於查詢返回null。您能夠實現並替換它來查詢來自任何數據源的租戶。
對於多租戶單數據庫方法,咱們必須添加一個TenantId過濾器,以便在從數據庫檢索實體時只獲取當前租戶的實體。若是你的實體實現IMustHaveTenant 和 IMayHaveTenant任何一個接口,ASP.NET Boilerplate會自動的幫你作這個。
這個接口經過定義TenantId屬性來區分不一樣租戶的實體。實現IMustHaveTenant的示例實體:
public class Product : Entity, IMustHaveTenant { public int TenantId { get; set; } public string Name { get; set; } //...other properties }
這樣,ASP.NET Boilerplate知道這是一個特定租戶的實體,並自動將租戶的實體與其餘租戶隔離開來。
咱們可能須要在主機和租戶之間共享一個實體類型。所以,實體可能由租戶或主機擁有。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接口,由於維護主機和租戶共享的代碼比較困難。
當您將實體類型定義爲IMustHaveTenant或IMayHaveTenant時,老是在建立新實體時設置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確保咱們正在處理給定租戶的數據,獨立於數據庫體系結構:
若是咱們不使用SetTenantId,它會從會話中獲取tenantId。這裏有一些指導方針和最佳實踐: