多租户框架案例
概念
多租户软件架构就是在同一个系统实例上运行不同用户,能做到应用程序共享,服务自治?,并且还能做到数据互相隔离的软件架构思想。
需求
公司有多个集群需要访问配置中心项目,不同集群访问到的数据可能不同,所以每个租户一个独立的数据库,但是共用一个配置中心项目。
开始
解析租户信息
可以通过域名、URL、Header等方式解析租户信息
/// <summary>
/// 解析租户信息
/// </summary>
public interface ITenantResolver
{
Task<string> GetIdentifierAsync();
}
DefaultHeaderTenantResolver:
public class DefaultHeaderTenantResolver : ITenantResolver
{
private readonly IHttpContextAccessor _accessor;
public DefaultHeaderTenantResolver(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public async Task<string> GetIdentifierAsync()
{
string identifier = _accessor.HttpContext.Request.Headers["Identifier"];
if (string.IsNullOrWhiteSpace(identifier))
{
throw new System.Exception("Identifier不能为空");
}
return await Task.FromResult(identifier);
}
}
租户信息体基类
public class TenantInfoBase
{
/// <summary>
/// 租户标识
/// </summary>
public string Identifier { get; set; }
/// <summary>
/// 数据库类型
/// </summary>
public string DBType { get; set; }
/// <summary>
/// 数据库连接字符串
/// </summary>
public string ConnectionString { get; set; }
}
存储租户信息
/// <summary>
/// 存储租户信息
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ITenantStore<T>
{
/// <summary>
/// 获取多租户信息
/// </summary>
/// <param name="identifier"></param>
/// <returns></returns>
Task<T> GetTenantAsync(string identifier);
/// <summary>
/// 获取全部多租户信息
/// </summary>
/// <returns></returns>
Task<IEnumerable<T>> GetTenantsAsync();
}
LoadBalanceTenantStore:
public class LoadBalanceTenantStore : ITenantStore<LoadBalanceTenant>
{
private List<LoadBalanceTenant> _cache = null;
private IConfiguration _configuration = null;
public LoadBalanceTenantStore(IConfiguration configuration)
{
_configuration = configuration;
}
public async Task<LoadBalanceTenant> GetTenantAsync(string identifier)
{
List<LoadBalanceTenant> tenantInfos = (await GetTenantsAsync()).Cast<LoadBalanceTenant>().ToList();
var tenant = tenantInfos.Find(tenant => tenant.Identifier == identifier) ?? throw new NullReferenceException($"租户不存在,identifier:{identifier}");
return tenant;
}
public async Task<IEnumerable<LoadBalanceTenant>> GetTenantsAsync()
{
if (_cache == null)
{
List<LoadBalanceTenant> tenantInfoList = new List<LoadBalanceTenant>();
ChangeToken.OnChange(() => _configuration.GetReloadToken(), () => { _cache = null; });
var tenantSections = _configuration.GetSection("Tenants").GetChildren();
bool enable = false;
if (tenantSections.Any())
{
foreach (var tenantSection in tenantSections)
{
enable = tenantSection.GetValue<bool>("Enable");
if (enable)
{
bool updateCurrency = tenantSection.GetValue<bool>("UpdateCurrency");
string dbType = tenantSection.GetValue<string>("DBType");
string masterConnectionString = tenantSection.GetValue<string>("MasterConnectionString");
//List<SlaveConnection> slaveConnections = tenantSection.GetValue<SlaveConnection[]>("Slaves")?.ToList() ?? new List<SlaveConnection>(0);
var slaveSections = tenantSection.GetSection("Slaves").GetChildren();
List<SlaveConnection> slaveConnections = new List<SlaveConnection>(slaveSections.Count());
foreach (var slaveSection in slaveSections)
{
slaveConnections.Add(new SlaveConnection
{
ConnectionString = slaveSection["ConnectionString"],
Weight = slaveSection.GetValue<int>("Weight")
});
}
tenantInfoList.Add(new LoadBalanceTenant
{
UpdateCurrency = updateCurrency,
Identifier = tenantSection.Key,
MasterConnectionString = masterConnectionString,
SlaveConnections = slaveConnections,
DBType = dbType
});
_cache = tenantInfoList;
}
}
}
}
return await Task.FromResult(_cache);
}
}
ITenantService
/// <summary>
/// 多租户对外服务(外观模式)
/// </summary>
/// <typeparam name="TTenant"></typeparam>
public interface ITenantService<TTenant> where TTenant : TenantInfoBase
{
/// <summary>
/// 异步获取当前请求租户信息
/// </summary>
/// <returns></returns>
Task<TTenant> GetTenantAsync();
/// <summary>
/// 获取全部多租户信息
/// </summary>
/// <returns></returns>
Task<List<TTenant>> GetTenantsAsync();
}
public class TenantService : ITenantService<TenantInfoBase>
{
private readonly ITenantResolver _tenantResolver;
private readonly ITenantStore<TenantInfoBase> _tenantStore;
public TenantService(ITenantResolver tenantResolver, ITenantStore<TenantInfoBase> tenantStore)
{
_tenantResolver = tenantResolver;
_tenantStore = tenantStore;
}
public async Task<TenantInfoBase> GetTenantAsync()
{
var identifier = _tenantResolver.GetIdentifierAsync().Result;
return await _tenantStore.GetTenantAsync(identifier);
}
public async Task<List<TenantInfoBase>> GetTenantsAsync()
{
return (await _tenantStore.GetTenantsAsync()).ToList();
}
}
public class TenantBuilder<TTenant> where TTenant : TenantInfoBase
{
private readonly IServiceCollection _services;
public TenantBuilder(IServiceCollection services)
{
_services = services;
}
public TenantBuilder<TTenant> WithResolver<TResolver>(ServiceLifetime lifetime = ServiceLifetime.Transient) where TResolver : ITenantResolver
{
_services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
_services.Add(new ServiceDescriptor(typeof(ITenantResolver), typeof(TResolver), lifetime));
return this;
}
public TenantBuilder<TTenant> WithStore<TStore>(ServiceLifetime lifetime = ServiceLifetime.Transient)
{
_services.Add(new ServiceDescriptor(typeof(ITenantStore<TenantInfoBase>), typeof(TStore), lifetime));
return this;
}
}
public static class ServiceCollectionExtensions
{
public static TenantBuilder<TTenant> AddMultiTenant<TTenant>(this IServiceCollection services) where TTenant : TenantInfoBase
{
services.AddTransient(typeof(ITenantService<TenantInfoBase>), typeof(TenantService));
return new TenantBuilder<TTenant>(services);
}
}
参考:https://www.cnblogs.com/ms27946/p/How-To-Implement-Multi-Tenant-System.html