介紹
多租戶是指一種軟件架構,其中軟件的單個實例在服務器上運行并為多個租戶提供服務。
在基于 SAAS 的平臺中,租戶是指使用該平臺開展業務運營的客戶。每個租戶都擁有獨立的數據、用戶帳戶和配置設置,并且與其他租戶隔離。
多租戶允許有效利用資源,因為同一個軟件實例可以為多個客戶提供服務,從而減少了為每個客戶提供單獨服務器和基礎設施的需求。
它還可以更輕松地進行維護和更新,因為可以對軟件的單個實例進行更改,而不必為每個客戶更新多個實例。
不同的實施方案
我們可以將多租戶實現分為三種不同的方法。
單一數據庫
所有租戶共享一個數據庫實例和架構。這種方法最節省資源,跨租戶的數據分析也變得更加簡單。這是一種經濟高效的方法,并簡化了管理。
但這種方法的安全性成為一個關鍵問題;隨著租戶數量的增加,出現性能瓶頸的可能性也會增大。數據法規的合規性,尤其是當租戶在不同地區運營且數據保護法規各異時,管理起來會變得復雜且具有挑戰性。
具有單獨模式的單一數據庫
所有租戶將共享單個數據庫,但每個租戶將擁有各自的架構。這種方法比單獨的數據庫更節省資源,同時仍然提供高級別的隔離和安全性。管理單個數據庫可以降低運營成本,例如備份和更新等管理任務。
然而,在資源爭用程度較高的場景中,例如跨不同模式的頻繁讀寫操作,可能會出現數據爭用和性能下降。
獨立數據庫
每個租戶都將擁有自己的數據庫,并且應用程序將有一個通用數據庫來存儲特定于租戶的信息。多個數據庫可提供更強的安全性和隔離性,從而降低租戶之間數據泄露和未經授權訪問的風險。
可擴展性得到增強,因為每個租戶的數據庫都可以根據各自的需求和使用模式獨立擴展。
然而,管理多個數據庫會增加管理任務的復雜性,可能會出現同步挑戰,導致不同數據庫之間數據不一致。此外,存儲、處理和基礎設施成本方面的資源消耗也會更高。
今天,在本博客中,我們將學習如何在 .Net Core 應用程序中實現多租戶的單獨數據庫方法以及如何向 DbContext 提供動態連接字符串。
我將給出一個應用程序的演示,其中我將采用兩個 DbContext 類。
? ? ? ? 1、一個用于主數據庫的 DB Context,它將存儲與租戶相關的內容,例如租戶詳細信息、租戶用戶登錄等。
? ? ? ? 2、每個租戶數據庫的第二個數據庫上下文,我們將根據 TenantId 連接該數據庫
我們將添加一個名為 x-tenant-id 的請求標頭,其中將包含我們想要獲取數據的租戶 ID。
步驟 1
創建租戶 Pod 表。我們可以使用此表存儲數據庫的租戶連接字符串。如果我們不想將連接字符串直接存儲在數據庫中,可以使用 Azure Key Vault 或 AWS Secret Manager,并在該數據庫中存儲 Secret 名稱。
// Tenant.cs
[Table("tenants")]
public class Tenant
{
[Key]
public Guid Id { get; protected set; }
public string Name { get; protected set; }
public Tenant(Guid id, string name)
{
Id = id;
Name = name;
}
}
// TenantPod.cs
[Table("tenant_pods")]
public class TenantPod
{
[Key]
public Guid Id { get; protected set; }
public Guid TenantId { get; protected set; }
public string ConnectionString { get; protected set; }
[ForeignKey(nameof(TenantId))]
public virtual Tenant Tenant { get; protected set; }
public TenantPod(Guid id, Guid tenantId, string connectionString)
{
Id = id;
TenantId = tenantId;
ConnectionString = connectionString;
}
}
步驟2
創建一個方法,根據請求標頭獲取連接字符串。
// TenantsService.cs
public interface ITenantsService
{
string GetConnectionByTenant();
}
public class TenantsService: ITenantsService
{
private readonly HttpContext? _httpContext;
public TenantsService(IHttpContextAccessor httpContextAccessor)
{
_httpContext = httpContextAccessor.HttpContext;
}
public string GetConnectionByTenant()
{
var tenantId = Guid.Empty;
try
{
if (_httpContext == null)
throw new Exception("Tenant Not Found");
? ? ? ? ? ? var clientId = _httpContext.Request.Headers["x-tenant_id"];
if (string.IsNullOrEmpty(clientId))
{
throw new Exception("Tenant Request Header is missing.");
}
Guid.TryParse(clientId.ToString(), out tenantId);
// Logic to get DB connection based on tenant
// Dummy Connection string Her you should have your logic to get connection string and return it
return "Server=127.0.0.1;Database=multiDemoMain;Port=5432;User Id=postgres;Password=123456";
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
}
}
步驟3
在Db Context中,添加邏輯以使用上述方法,覆蓋DBContext類的OnConfiguring方法。
// MultiTenantDbContext.cs
public class MultiTenantDbContext : DbContext
{
private readonly ITenantsService _tenantsService;
public MultiTenantDbContext(DbContextOptions<MultiTenantDbContext> options, ITenantsService tenantsService) :
base(options)
{
_tenantsService = tenantsService;
}
public DbSet<Tenant> Tenants { get; set; }
public DbSet<Document> Documents { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var connectionString = _tenantsService.GetConnectionByTenant();
if (string.IsNullOrEmpty(connectionString))
{
throw new Exception("Tenant Connection Not found");
}
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.UseNpgsql(connectionString, b =>
{
b.SetPostgresVersion(new Version(9, 6));
b.EnableRetryOnFailure(10, TimeSpan.FromSeconds(30), null);
b.CommandTimeout(300);
});
optionsBuilder.UseNpgsql(connectionString).UseSnakeCaseNamingConvention();
}
}
就是這樣。現在,您可以在存儲庫或服務層中使用此數據庫上下文,并且此 DbContext 將根據傳遞的 TenantId 獲取連接字符串。我們需要確保每個 API 的請求標頭中都有一個租戶。
注意:在本例中,我有一個在兩個數據庫中都重復的租戶表,但要使用約束,我們需要復制該表,并且應該有邏輯來同步數據庫中的數據。此外,我們可以實現 Redis 緩存或內存緩存服務來從內存中獲取連接字符串,從而加快速度。在我的下一篇文章中,我們將學習如何在 .Net Core 中使用內存緩存和 Redis 緩存。
結論
在本文中,我們了解了多租戶及其不同的實現方式。此外,我們還探索了如何基于 TenantId 動態連接數據庫。
如果您喜歡此文章,請收藏、點贊、評論,謝謝,祝您快樂每一天。?