多租戶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,如以前所說。這有一些建議和最佳實踐: