diff --git a/src/BuildingBlocks/Caching/Masa.BuildingBlocks.Caching/Internal/DistributedCacheClientCache.cs b/src/BuildingBlocks/Caching/Masa.BuildingBlocks.Caching/Internal/DistributedCacheClientCache.cs new file mode 100644 index 000000000..1aad2183c --- /dev/null +++ b/src/BuildingBlocks/Caching/Masa.BuildingBlocks.Caching/Internal/DistributedCacheClientCache.cs @@ -0,0 +1,57 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.BuildingBlocks.Caching; + +internal class DistributedCacheClientCache +{ + private static ConcurrentDictionary _cacheClients = new(); + + public IManualDistributedCacheClient GetCacheClient(IServiceProvider serviceProvider) + { + var environment = GetCurrentEnvironment(serviceProvider); + var tenantId = GetCurrentTenantId(serviceProvider); + + var key = GenerateKey(environment, tenantId); + + return _cacheClients.GetOrAdd(key, _ => CreateCacheClient(serviceProvider)); + } + + private static string GetCurrentEnvironment(IServiceProvider serviceProvider) + { + var multiEnvironmentContext = serviceProvider.GetService(); + return multiEnvironmentContext?.CurrentEnvironment ?? string.Empty; + } + + private static string GetCurrentTenantId(IServiceProvider serviceProvider) + { + var multiTenantContext = serviceProvider.GetService(); + return multiTenantContext?.CurrentTenant?.Id ?? string.Empty; + } + + private static string GenerateKey(string environment, string tenantId) + { + if (string.IsNullOrEmpty(environment)) + { + return tenantId; + } + if (string.IsNullOrEmpty(tenantId)) + { + return environment; + } + return $"{environment}:{tenantId}"; + } + + private static IManualDistributedCacheClient CreateCacheClient(IServiceProvider serviceProvider) + { + try + { + var scopedService = serviceProvider.GetRequiredService>(); + return scopedService.Service; + } + catch (Exception ex) + { + throw new InvalidOperationException("Failed to create cache client", ex); + } + } +} diff --git a/src/BuildingBlocks/Caching/Masa.BuildingBlocks.Caching/Internal/ServiceCollectionExtensions.Core.cs b/src/BuildingBlocks/Caching/Masa.BuildingBlocks.Caching/Internal/ServiceCollectionExtensions.Core.cs index 67ed3f85b..25c475013 100644 --- a/src/BuildingBlocks/Caching/Masa.BuildingBlocks.Caching/Internal/ServiceCollectionExtensions.Core.cs +++ b/src/BuildingBlocks/Caching/Masa.BuildingBlocks.Caching/Internal/ServiceCollectionExtensions.Core.cs @@ -23,8 +23,9 @@ public static void TryAddDistributedCache( services.TryAddTransient(serviceProvider => { var cacheClient = serviceProvider.EnableIsolation() ? - serviceProvider.GetRequiredService>().Service : + serviceProvider.GetRequiredService().GetCacheClient(serviceProvider) : serviceProvider.GetRequiredService>().Service; + return new DefaultDistributedCacheClient(cacheClient); }); services.TryAddTransient(serviceProvider @@ -65,6 +66,8 @@ private static void AddTypeAlias( private static void AddCaching(this IServiceCollection services) { + services.TryAddSingleton(); + services.TryAddSingleton>(serviceProvider => new SingletonService(serviceProvider.GetRequiredService() .Create())); diff --git a/src/Contrib/Caching/Distributed/Masa.Contrib.Caching.Distributed.StackExchangeRedis/RedisCacheClientBase.cs b/src/Contrib/Caching/Distributed/Masa.Contrib.Caching.Distributed.StackExchangeRedis/RedisCacheClientBase.cs index 20cf755e2..c0b6c97f9 100644 --- a/src/Contrib/Caching/Distributed/Masa.Contrib.Caching.Distributed.StackExchangeRedis/RedisCacheClientBase.cs +++ b/src/Contrib/Caching/Distributed/Masa.Contrib.Caching.Distributed.StackExchangeRedis/RedisCacheClientBase.cs @@ -7,29 +7,31 @@ public abstract class RedisCacheClientBase : DistributedCacheClientBase { protected readonly string? InstanceId; protected static readonly Guid UniquelyIdentifies = Guid.NewGuid(); - protected readonly ISubscriber Subscriber; + protected ISubscriber Subscriber; protected IDatabase Db { get { - if (_connection.IsConnected || _connection.IsConnecting) - return _connection.GetDatabase(); + EnsureDbConnection(); - throw new NotSupportedException("Redis service has been disconnected, please wait for reconnection and try again"); + return _connection.GetDatabase(); } } - private readonly IConnectionMultiplexer _connection; + private IConnectionMultiplexer _connection; protected readonly JsonSerializerOptions GlobalJsonSerializerOptions; private readonly CacheEntryOptions _globalCacheEntryOptions; private readonly CacheOptions _globalCacheOptions; + private readonly RedisConfigurationOptions _redisConfigurationOptions; + protected RedisCacheClientBase( RedisConfigurationOptions redisConfigurationOptions, JsonSerializerOptions? jsonSerializerOptions) : this(redisConfigurationOptions.GlobalCacheOptions, redisConfigurationOptions, jsonSerializerOptions) { + _redisConfigurationOptions = redisConfigurationOptions; var redisConfiguration = redisConfigurationOptions.GetAvailableRedisOptions(); _connection = ConnectionMultiplexer.Connect(redisConfiguration); Subscriber = _connection.GetSubscriber(); @@ -51,6 +53,22 @@ private RedisCacheClientBase( GlobalJsonSerializerOptions = jsonSerializerOptions ?? new JsonSerializerOptions().EnableDynamicTypes(); } + protected void EnsureDbConnection() + { + if (!_connection.IsConnected && !_connection.IsConnecting) + { + // Attempt to reconnect + var redisConfiguration = _redisConfigurationOptions.GetAvailableRedisOptions(); + _connection = ConnectionMultiplexer.Connect(redisConfiguration); + Subscriber = _connection.GetSubscriber(); + } + + if (!_connection.IsConnected && !_connection.IsConnecting) + { + throw new NotSupportedException("Unable to reconnect to Redis, please check the connection settings and try again."); + } + } + protected T? ConvertToValue(RedisValue value, out bool isExist) { if (value is { HasValue: true, IsNullOrEmpty: false })