dramaling-vocab-learning/backend/DramaLing.Api/Services/Infrastructure/Caching/HybridCacheService.cs

288 lines
9.1 KiB
C#

using DramaLing.Api.Services.Infrastructure.Caching;
namespace DramaLing.Api.Services.Caching;
/// <summary>
/// 混合快取服務,使用組合模式結合記憶體和分散式快取
/// </summary>
public class HybridCacheService : ICacheService
{
private readonly ICacheProvider _memoryProvider;
private readonly ICacheProvider? _distributedProvider;
private readonly IDatabaseCacheManager _databaseCacheManager;
private readonly ICacheStrategyManager _strategyManager;
private readonly ILogger<HybridCacheService> _logger;
private readonly CacheStats _stats;
public HybridCacheService(
ICacheProvider memoryProvider,
ICacheProvider? distributedProvider,
IDatabaseCacheManager databaseCacheManager,
ICacheStrategyManager strategyManager,
ILogger<HybridCacheService> logger)
{
_memoryProvider = memoryProvider ?? throw new ArgumentNullException(nameof(memoryProvider));
_distributedProvider = distributedProvider;
_databaseCacheManager = databaseCacheManager ?? throw new ArgumentNullException(nameof(databaseCacheManager));
_strategyManager = strategyManager ?? throw new ArgumentNullException(nameof(strategyManager));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_stats = new CacheStats { LastUpdated = DateTime.UtcNow };
_logger.LogInformation("HybridCacheService initialized with {MemoryProvider} and {DistributedProvider}",
_memoryProvider.ProviderName, _distributedProvider?.ProviderName ?? "No Distributed Cache");
}
public async Task<T?> GetAsync<T>(string key) where T : class
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));
try
{
// L1: 記憶體快取
var memoryResult = await _memoryProvider.GetAsync<T>(key);
if (memoryResult != null)
{
_stats.HitCount++;
return memoryResult;
}
// L2: 分散式快取
if (_distributedProvider != null)
{
var distributedResult = await _distributedProvider.GetAsync<T>(key);
if (distributedResult != null)
{
// 回填到記憶體快取
var memoryExpiry = _strategyManager.CalculateMemoryExpiry(key);
await _memoryProvider.SetAsync(key, distributedResult, memoryExpiry);
_stats.HitCount++;
return distributedResult;
}
}
// L3: 資料庫快取 (僅適用於分析結果)
if (key.StartsWith("analysis:"))
{
var dbResult = await _databaseCacheManager.GetFromDatabaseCacheAsync<T>(key);
if (dbResult != null)
{
// 回填到上層快取
await SetMultiLevelCacheAsync(key, dbResult);
_stats.HitCount++;
return dbResult;
}
}
_stats.MissCount++;
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting cache for key: {Key}", key);
_stats.MissCount++;
return null;
}
}
public async Task<bool> SetAsync<T>(string key, T value, TimeSpan? expiry = null) where T : class
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));
if (value == null)
throw new ArgumentNullException(nameof(value));
try
{
var smartExpiry = expiry ?? _strategyManager.CalculateSmartExpiry(key, value);
var tasks = new List<Task<bool>>();
// L1: 記憶體快取
var memoryExpiry = TimeSpan.FromMinutes(Math.Min(smartExpiry.TotalMinutes, 30));
tasks.Add(_memoryProvider.SetAsync(key, value, memoryExpiry));
// L2: 分散式快取
if (_distributedProvider != null)
{
tasks.Add(_distributedProvider.SetAsync(key, value, smartExpiry));
}
var results = await Task.WhenAll(tasks);
var success = results.Any(r => r);
if (success)
{
_stats.TotalKeys++;
_logger.LogDebug("Cache set for key: {Key}, expiry: {Expiry}", key, smartExpiry);
}
return success;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error setting cache for key: {Key}", key);
return false;
}
}
public async Task<bool> RemoveAsync(string key)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));
try
{
var tasks = new List<Task<bool>>
{
_memoryProvider.RemoveAsync(key)
};
if (_distributedProvider != null)
{
tasks.Add(_distributedProvider.RemoveAsync(key));
}
var results = await Task.WhenAll(tasks);
return results.Any(r => r);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error removing cache for key: {Key}", key);
return false;
}
}
public async Task<bool> ExistsAsync(string key)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));
try
{
if (await _memoryProvider.ExistsAsync(key))
return true;
if (_distributedProvider != null)
return await _distributedProvider.ExistsAsync(key);
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking cache existence for key: {Key}", key);
return false;
}
}
public async Task<bool> ExpireAsync(string key, TimeSpan expiry)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));
try
{
var value = await GetAsync<object>(key);
if (value != null)
{
return await SetAsync(key, value, expiry);
}
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error setting expiry for key: {Key}", key);
return false;
}
}
public async Task<bool> ClearAsync()
{
try
{
var tasks = new List<Task<bool>>
{
_memoryProvider.ClearAsync()
};
if (_distributedProvider != null)
{
tasks.Add(_distributedProvider.ClearAsync());
}
await Task.WhenAll(tasks);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error clearing cache");
return false;
}
}
public async Task<Dictionary<string, T?>> GetManyAsync<T>(IEnumerable<string> keys) where T : class
{
var keyList = keys.ToList();
var result = new Dictionary<string, T?>();
if (!keyList.Any())
return result;
try
{
var tasks = keyList.Select(async key =>
{
var value = await GetAsync<T>(key);
return new KeyValuePair<string, T?>(key, value);
});
var results = await Task.WhenAll(tasks);
return results.ToDictionary(r => r.Key, r => r.Value);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting multiple cache values");
return result;
}
}
public async Task<bool> SetManyAsync<T>(Dictionary<string, T> keyValuePairs, TimeSpan? expiry = null) where T : class
{
if (!keyValuePairs.Any())
return true;
try
{
var tasks = keyValuePairs.Select(async kvp =>
await SetAsync(kvp.Key, kvp.Value, expiry));
var results = await Task.WhenAll(tasks);
return results.All(r => r);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error setting multiple cache values");
return false;
}
}
public Task<CacheStats> GetStatsAsync()
{
_stats.LastUpdated = DateTime.UtcNow;
return Task.FromResult(_stats);
}
private async Task SetMultiLevelCacheAsync<T>(string key, T value) where T : class
{
var expiry = _strategyManager.CalculateSmartExpiry(key, value);
var memoryExpiry = _strategyManager.CalculateMemoryExpiry(key);
await _memoryProvider.SetAsync(key, value, memoryExpiry);
if (_distributedProvider != null)
{
await _distributedProvider.SetAsync(key, value, expiry);
}
}
}