介绍
多租户是指一种软件架构,其中软件的单个实例在服务器上运行并为多个租户提供服务。
在基于 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 动态连接数据库。
如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。