288 lines
9.1 KiB
C#
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);
|
|
}
|
|
}
|
|
} |