using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Options;
|
using WIDESEAWCS_Core.Caches;
|
using WIDESEAWCS_RedisService.Connection;
|
using WIDESEAWCS_RedisService.Options;
|
using WIDESEAWCS_RedisService.Serialization;
|
|
namespace WIDESEAWCS_RedisService.Cache
|
{
|
public class HybridCacheService : ICacheService
|
{
|
private readonly IMemoryCache _memoryCache;
|
private readonly IRedisConnectionManager _connectionManager;
|
private readonly IRedisSerializer _serializer;
|
private readonly RedisOptions _options;
|
private readonly ILogger<HybridCacheService> _logger;
|
private bool _disposed;
|
|
private bool RedisAvailable => _connectionManager.IsConnected;
|
|
public HybridCacheService(
|
IMemoryCache memoryCache,
|
IRedisConnectionManager connectionManager,
|
IRedisSerializer serializer,
|
IOptions<RedisOptions> options,
|
ILogger<HybridCacheService> logger)
|
{
|
_memoryCache = memoryCache;
|
_connectionManager = connectionManager;
|
_serializer = serializer;
|
_options = options.Value;
|
_logger = logger;
|
}
|
|
private string BuildKey(string key) => $"{_options.KeyPrefix}{key}";
|
|
public bool Exists(string key)
|
{
|
if (_memoryCache.TryGetValue(BuildKey(key), out _)) return true;
|
if (!RedisAvailable) return false;
|
try
|
{
|
return _connectionManager.GetDatabase().KeyExists(BuildKey(key));
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis Exists失败, key={Key}", key);
|
return false;
|
}
|
}
|
|
public bool Add(string key, string value, int expireSeconds = -1, bool isSliding = false)
|
{
|
var fullKey = BuildKey(key);
|
|
// 只有启用L1缓存时才写入内存缓存
|
if (_options.EnableL1Cache)
|
{
|
SetMemoryCache(fullKey, value, expireSeconds, isSliding);
|
}
|
|
if (!RedisAvailable)
|
{
|
if (!_options.EnableL1Cache)
|
{
|
_logger.LogWarning("Redis不可用且L1缓存已禁用, key={Key}", key);
|
return false;
|
}
|
_logger.LogWarning("Redis不可用,仅使用内存缓存, key={Key}", key);
|
return true;
|
}
|
try
|
{
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
var result = _connectionManager.GetDatabase().StringSet(fullKey, value, expiry);
|
_logger.LogInformation("Redis写入成功: key={Key}, result={Result}", fullKey, result);
|
return result;
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis Add失败, key={Key}", key);
|
return _options.FallbackToMemory && _options.EnableL1Cache;
|
}
|
}
|
|
public bool AddObject(string key, object value, int expireSeconds = -1, bool isSliding = false)
|
{
|
return Add(key, _serializer.Serialize(value), expireSeconds, isSliding);
|
}
|
|
public void AddOrUpdate(string key, string value, int expireSeconds = -1, bool isSliding = false)
|
{
|
Add(key, value, expireSeconds, isSliding);
|
}
|
|
public void AddOrUpdate(string key, object value, int expireSeconds = -1, bool isSliding = false)
|
{
|
AddObject(key, value, expireSeconds, isSliding);
|
}
|
|
public bool Remove(string key)
|
{
|
var fullKey = BuildKey(key);
|
|
// 只有启用L1缓存时才从内存缓存中移除
|
if (_options.EnableL1Cache)
|
{
|
_memoryCache.Remove(fullKey);
|
}
|
|
if (!RedisAvailable) return _options.EnableL1Cache;
|
try
|
{
|
return _connectionManager.GetDatabase().KeyDelete(fullKey);
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis Remove失败, key={Key}", key);
|
return true;
|
}
|
}
|
|
public void Remove(IEnumerable<string> keys)
|
{
|
foreach (var key in keys) Remove(key);
|
}
|
|
#region 删除扩展方法
|
|
public string? RemoveAndGet(string key)
|
{
|
var value = Get(key);
|
if (value != null) Remove(key);
|
return value;
|
}
|
|
public T? RemoveAndGet<T>(string key) where T : class
|
{
|
var value = Get<T>(key);
|
if (value != null) Remove(key);
|
return value;
|
}
|
|
public int RemoveByPrefix(string prefix)
|
{
|
if (string.IsNullOrEmpty(prefix)) return 0;
|
var fullPrefix = BuildKey(prefix);
|
int count = 0;
|
|
// 删除内存缓存中的匹配项
|
// MemoryCache无法枚举,跳过
|
|
if (RedisAvailable)
|
{
|
try
|
{
|
var server = _connectionManager.GetServer();
|
var keys = server.Keys(pattern: $"{fullPrefix}*").ToArray();
|
if (keys.Length > 0)
|
{
|
count = (int)_connectionManager.GetDatabase().KeyDelete(keys);
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis RemoveByPrefix失败, prefix={Prefix}", prefix);
|
}
|
}
|
|
return count;
|
}
|
|
public int RemoveByPattern(string pattern)
|
{
|
if (string.IsNullOrEmpty(pattern)) return 0;
|
int count = 0;
|
|
if (RedisAvailable)
|
{
|
try
|
{
|
var server = _connectionManager.GetServer();
|
var keys = server.Keys(pattern: $"{_options.KeyPrefix}{pattern}").ToArray();
|
if (keys.Length > 0)
|
{
|
count = (int)_connectionManager.GetDatabase().KeyDelete(keys);
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis RemoveByPattern失败, pattern={Pattern}", pattern);
|
}
|
}
|
|
return count;
|
}
|
|
public int RemoveAll(IEnumerable<string> keys)
|
{
|
if (keys == null) return 0;
|
int count = 0;
|
foreach (var key in keys)
|
{
|
if (Remove(key)) count++;
|
}
|
return count;
|
}
|
|
public int RemoveWhere(Func<string, bool> predicate)
|
{
|
if (predicate == null) return 0;
|
int count = 0;
|
|
if (RedisAvailable)
|
{
|
try
|
{
|
var server = _connectionManager.GetServer();
|
var keys = server.Keys(pattern: $"{_options.KeyPrefix}*").ToArray();
|
var keysToDelete = keys.Where(k =>
|
{
|
var originalKey = k.ToString().Replace(_options.KeyPrefix, "");
|
return predicate(originalKey);
|
}).ToArray();
|
|
if (keysToDelete.Length > 0)
|
{
|
count = (int)_connectionManager.GetDatabase().KeyDelete(keysToDelete);
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis RemoveWhere失败");
|
}
|
}
|
|
return count;
|
}
|
|
#endregion
|
|
#region 添加和修改扩展方法
|
|
public void AddAll(IDictionary<string, string> items, int expireSeconds = -1)
|
{
|
if (items == null) return;
|
foreach (var item in items)
|
{
|
Add(item.Key, item.Value, expireSeconds);
|
}
|
}
|
|
public void AddAllObjects(IDictionary<string, object> items, int expireSeconds = -1)
|
{
|
if (items == null) return;
|
foreach (var item in items)
|
{
|
AddObject(item.Key, item.Value, expireSeconds);
|
}
|
}
|
|
public bool Replace(string key, string newValue, int expireSeconds = -1)
|
{
|
return TryUpdate(key, newValue, expireSeconds);
|
}
|
|
public bool Replace<T>(string key, T newValue, int expireSeconds = -1) where T : class
|
{
|
if (!Exists(key)) return false;
|
AddObject(key, newValue, expireSeconds);
|
return true;
|
}
|
|
public string? GetAndRefresh(string key, int expireSeconds)
|
{
|
var fullKey = BuildKey(key);
|
string? value = null;
|
|
// 从Redis获取值
|
if (RedisAvailable)
|
{
|
try
|
{
|
var redisValue = _connectionManager.GetDatabase().StringGet(fullKey);
|
if (!redisValue.IsNullOrEmpty)
|
{
|
value = redisValue.ToString();
|
// 刷新Redis过期时间
|
_connectionManager.GetDatabase().KeyExpire(fullKey, TimeSpan.FromSeconds(expireSeconds));
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis GetAndRefresh失败, key={Key}", key);
|
}
|
}
|
|
// 如果Redis不可用,尝试从内存缓存获取
|
if (value == null && _options.EnableL1Cache)
|
{
|
if (_memoryCache.TryGetValue(fullKey, out string? cached))
|
value = cached;
|
}
|
|
// 更新内存缓存(如果有值)
|
if (value != null && _options.EnableL1Cache)
|
{
|
SetMemoryCache(fullKey, value, expireSeconds, false);
|
}
|
|
return value;
|
}
|
|
public T? GetAndRefresh<T>(string key, int expireSeconds) where T : class
|
{
|
var fullKey = BuildKey(key);
|
T? value = default;
|
|
// 从Redis获取值
|
if (RedisAvailable)
|
{
|
try
|
{
|
var redisValue = _connectionManager.GetDatabase().StringGet(fullKey);
|
if (!redisValue.IsNullOrEmpty)
|
{
|
var json = redisValue.ToString();
|
value = _serializer.Deserialize<T>(json);
|
// 刷新Redis过期时间
|
_connectionManager.GetDatabase().KeyExpire(fullKey, TimeSpan.FromSeconds(expireSeconds));
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis GetAndRefresh<T>失败, key={Key}", key);
|
}
|
}
|
|
// 如果Redis不可用,尝试从内存缓存获取
|
if (value == null && _options.EnableL1Cache)
|
{
|
if (_memoryCache.TryGetValue(fullKey, out string? cached) && cached != null)
|
value = _serializer.Deserialize<T>(cached);
|
}
|
|
// 更新内存缓存(如果有值)
|
if (value != null && _options.EnableL1Cache)
|
{
|
SetMemoryCache(fullKey, _serializer.Serialize(value), expireSeconds, false);
|
}
|
|
return value;
|
}
|
|
public bool RefreshExpire(string key, int expireSeconds)
|
{
|
var fullKey = BuildKey(key);
|
bool result = false;
|
|
// 刷新Redis过期时间
|
if (RedisAvailable)
|
{
|
try
|
{
|
result = _connectionManager.GetDatabase().KeyExpire(fullKey, TimeSpan.FromSeconds(expireSeconds));
|
}
|
catch { }
|
}
|
|
// 更新内存缓存过期时间(需要重新设置值来刷新过期时间)
|
if (_options.EnableL1Cache && _memoryCache.TryGetValue(fullKey, out string? cached) && cached != null)
|
{
|
SetMemoryCache(fullKey, cached, expireSeconds, false);
|
return true;
|
}
|
|
return result;
|
}
|
|
public bool ExpireIn(string key, int seconds)
|
{
|
return RefreshExpire(key, seconds);
|
}
|
|
public bool ExpireAt(string key, DateTime expireTime)
|
{
|
var seconds = (long)(expireTime - DateTime.Now).TotalSeconds;
|
if (seconds <= 0) return Remove(key);
|
return RefreshExpire(key, (int)seconds);
|
}
|
|
public long? GetExpire(string key)
|
{
|
if (RedisAvailable)
|
{
|
try
|
{
|
var ttl = _connectionManager.GetDatabase().KeyTimeToLive(BuildKey(key));
|
return ttl.HasValue ? (long)ttl.Value.TotalSeconds : null;
|
}
|
catch { }
|
}
|
return null; // MemoryCache不支持TTL查询
|
}
|
|
public bool AddIfNotExists(string key, string value, int expireSeconds = -1)
|
{
|
return TryAdd(key, value, expireSeconds);
|
}
|
|
public bool AddIfNotExists<T>(string key, T value, int expireSeconds = -1) where T : class
|
{
|
return TryAdd(key, value, expireSeconds);
|
}
|
|
public string? GetAndSet(string key, string newValue, int expireSeconds = -1)
|
{
|
var fullKey = BuildKey(key);
|
string? oldValue = null;
|
|
// 从Redis获取旧值
|
if (RedisAvailable)
|
{
|
try
|
{
|
var value = _connectionManager.GetDatabase().StringGet(fullKey);
|
oldValue = value.IsNullOrEmpty ? null : value.ToString();
|
}
|
catch { }
|
}
|
|
// 如果Redis不可用,从内存缓存获取
|
if (oldValue == null && _options.EnableL1Cache)
|
{
|
_memoryCache.TryGetValue(fullKey, out oldValue);
|
}
|
|
// 写入Redis
|
if (RedisAvailable)
|
{
|
try
|
{
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
_connectionManager.GetDatabase().StringSet(fullKey, newValue, expiry);
|
}
|
catch { }
|
}
|
|
// 更新内存缓存
|
if (_options.EnableL1Cache)
|
{
|
SetMemoryCache(fullKey, newValue, expireSeconds, false);
|
}
|
|
return oldValue;
|
}
|
|
public T? GetAndSet<T>(string key, T newValue, int expireSeconds = -1) where T : class
|
{
|
var fullKey = BuildKey(key);
|
T? oldValue = default;
|
string? oldJson = null;
|
|
// 从Redis获取旧值
|
if (RedisAvailable)
|
{
|
try
|
{
|
var value = _connectionManager.GetDatabase().StringGet(fullKey);
|
if (!value.IsNullOrEmpty)
|
{
|
oldJson = value.ToString();
|
oldValue = _serializer.Deserialize<T>(oldJson);
|
}
|
}
|
catch { }
|
}
|
|
var newJson = _serializer.Serialize(newValue);
|
|
// 写入Redis
|
if (RedisAvailable)
|
{
|
try
|
{
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
_connectionManager.GetDatabase().StringSet(fullKey, newJson, expiry);
|
}
|
catch { }
|
}
|
|
// 更新内存缓存
|
if (_options.EnableL1Cache)
|
{
|
SetMemoryCache(fullKey, newJson, expireSeconds, false);
|
}
|
|
return oldValue;
|
}
|
|
public long Increment(string key, long value = 1)
|
{
|
if (RedisAvailable)
|
{
|
try
|
{
|
return _connectionManager.GetDatabase().StringIncrement(BuildKey(key), value);
|
}
|
catch { }
|
}
|
|
// Fallback to memory
|
var current = long.TryParse(Get(key), out var v) ? v : 0;
|
var newValue = current + value;
|
Add(key, newValue.ToString());
|
return newValue;
|
}
|
|
public long Decrement(string key, long value = 1)
|
{
|
return Increment(key, -value);
|
}
|
|
public long Append(string key, string value)
|
{
|
var current = Get(key) ?? "";
|
var newValue = current + value;
|
Add(key, newValue);
|
return newValue.Length;
|
}
|
|
#endregion
|
|
public T? Get<T>(string key) where T : class
|
{
|
var fullKey = BuildKey(key);
|
|
// 如果禁用了L1缓存,直接查Redis
|
if (!_options.EnableL1Cache)
|
{
|
if (!RedisAvailable) return default;
|
try
|
{
|
var value = _connectionManager.GetDatabase().StringGet(fullKey);
|
if (value.IsNullOrEmpty) return default;
|
return _serializer.Deserialize<T>(value!);
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis Get<T>失败, key={Key}", key);
|
return default;
|
}
|
}
|
|
// 正常的L1+L2逻辑
|
if (_memoryCache.TryGetValue(fullKey, out string? cached) && cached != null)
|
return _serializer.Deserialize<T>(cached);
|
|
if (!RedisAvailable) return default;
|
try
|
{
|
var value = _connectionManager.GetDatabase().StringGet(fullKey);
|
if (value.IsNullOrEmpty) return default;
|
var str = value.ToString();
|
SetMemoryCache(fullKey, str, 300, false);
|
return _serializer.Deserialize<T>(str);
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis Get<T>失败, key={Key}", key);
|
return default;
|
}
|
}
|
|
/// <summary>
|
/// 安全更新:仅当内存缓存中的值与expectedVersion匹配时才更新
|
/// 防止并发写入时旧值覆盖新值
|
/// </summary>
|
/// <typeparam name="T">值类型</typeparam>
|
/// <param name="key">缓存键</param>
|
/// <param name="newValue">新值</param>
|
/// <param name="expectedVersion">期望的版本(通常是旧对象的哈希值或时间戳)</param>
|
/// <param name="versionExtractor">从对象提取版本号的函数</param>
|
/// <param name="expireSeconds">过期时间</param>
|
/// <returns>是否更新成功</returns>
|
public bool TrySafeUpdate<T>(
|
string key,
|
T newValue,
|
object? expectedVersion,
|
Func<T, object?> versionExtractor,
|
int expireSeconds = -1) where T : class
|
{
|
var fullKey = BuildKey(key);
|
|
// 从Redis获取当前值
|
string? existingJson = null;
|
T? existingValue = default;
|
if (RedisAvailable)
|
{
|
try
|
{
|
var value = _connectionManager.GetDatabase().StringGet(fullKey);
|
if (!value.IsNullOrEmpty)
|
{
|
existingJson = value.ToString();
|
existingValue = _serializer.Deserialize<T>(existingJson);
|
}
|
}
|
catch { }
|
}
|
|
// 如果Redis不可用,从内存缓存获取
|
if (existingValue == null && _options.EnableL1Cache)
|
{
|
if (_memoryCache.TryGetValue(fullKey, out string? cached) && cached != null)
|
{
|
existingValue = _serializer.Deserialize<T>(cached);
|
existingJson = cached;
|
}
|
else
|
{
|
return false;
|
}
|
}
|
|
// 检查版本是否匹配
|
if (existingValue != null)
|
{
|
var currentVersion = versionExtractor(existingValue);
|
if (!Equals(currentVersion, expectedVersion))
|
{
|
_logger.LogWarning("TrySafeUpdate版本不匹配, key={Key}, expected={Expected}, current={Current}",
|
key, expectedVersion, currentVersion);
|
return false; // 版本不匹配,拒绝更新
|
}
|
}
|
|
// 版本匹配,执行更新
|
var newJson = _serializer.Serialize(newValue);
|
|
// 先写入Redis
|
if (RedisAvailable)
|
{
|
try
|
{
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
if (!_connectionManager.GetDatabase().StringSet(fullKey, newJson, expiry))
|
{
|
_logger.LogWarning("Redis TrySafeUpdate写入失败, key={Key}", key);
|
return _options.FallbackToMemory && _options.EnableL1Cache;
|
}
|
else
|
{
|
_logger.LogInformation("Redis TrySafeUpdate写入成功, key={Key}", key);
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis TrySafeUpdate写入失败, key={Key}", key);
|
return _options.FallbackToMemory && _options.EnableL1Cache;
|
}
|
}
|
|
// 更新内存缓存
|
if (_options.EnableL1Cache)
|
{
|
SetMemoryCache(fullKey, newJson, expireSeconds, false);
|
}
|
|
return true;
|
}
|
|
public object? Get(Type type, string key)
|
{
|
var fullKey = BuildKey(key);
|
|
// 如果禁用了L1缓存,直接查Redis
|
if (!_options.EnableL1Cache)
|
{
|
if (!RedisAvailable) return null;
|
try
|
{
|
var value = _connectionManager.GetDatabase().StringGet(fullKey);
|
if (value.IsNullOrEmpty) return null;
|
return _serializer.Deserialize(value!, type);
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis Get(Type)失败, key={Key}", key);
|
return null;
|
}
|
}
|
|
// 正常的L1+L2逻辑
|
if (_memoryCache.TryGetValue(fullKey, out string? cached) && cached != null)
|
return _serializer.Deserialize(cached, type);
|
|
if (!RedisAvailable) return null;
|
try
|
{
|
var value = _connectionManager.GetDatabase().StringGet(fullKey);
|
if (value.IsNullOrEmpty) return null;
|
var str = value.ToString();
|
SetMemoryCache(fullKey, str, 300, false);
|
return _serializer.Deserialize(str, type);
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis Get(Type)失败, key={Key}", key);
|
return null;
|
}
|
}
|
|
public string? Get(string key)
|
{
|
var fullKey = BuildKey(key);
|
|
// 如果禁用了L1缓存,直接查Redis
|
if (!_options.EnableL1Cache)
|
{
|
if (!RedisAvailable) return null;
|
try
|
{
|
var value = _connectionManager.GetDatabase().StringGet(fullKey);
|
return value.IsNullOrEmpty ? null : value.ToString();
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis Get失败, key={Key}", key);
|
return null;
|
}
|
}
|
|
// 正常的L1+L2逻辑
|
if (_memoryCache.TryGetValue(fullKey, out string? cached))
|
return cached;
|
|
if (!RedisAvailable) return null;
|
try
|
{
|
var value = _connectionManager.GetDatabase().StringGet(fullKey);
|
if (value.IsNullOrEmpty) return null;
|
var str = value.ToString();
|
SetMemoryCache(fullKey, str, 300, false);
|
return str;
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis Get失败, key={Key}", key);
|
return null;
|
}
|
}
|
|
private void SetMemoryCache(string fullKey, string value, int expireSeconds, bool isSliding)
|
{
|
var entryOptions = new MemoryCacheEntryOptions();
|
if (expireSeconds > 0)
|
{
|
if (isSliding)
|
entryOptions.SetSlidingExpiration(TimeSpan.FromSeconds(expireSeconds));
|
else
|
entryOptions.SetAbsoluteExpiration(TimeSpan.FromSeconds(expireSeconds));
|
}
|
_memoryCache.Set(fullKey, value, entryOptions);
|
}
|
|
#region ConcurrentDictionary风格方法
|
|
public bool TryAdd(string key, string value, int expireSeconds = -1)
|
{
|
if (Exists(key)) return false;
|
return Add(key, value, expireSeconds);
|
}
|
|
public bool TryAdd<T>(string key, T value, int expireSeconds = -1) where T : class
|
{
|
if (Exists(key)) return false;
|
return AddObject(key, value, expireSeconds);
|
}
|
|
public bool TryGetValue(string key, out string? value)
|
{
|
value = Get(key);
|
return value != null;
|
}
|
|
public bool TryGetValue<T>(string key, out T? value) where T : class
|
{
|
value = Get<T>(key);
|
return value != null;
|
}
|
|
public bool TryRemove(string key, out string? value)
|
{
|
value = Get(key);
|
if (value == null) return false;
|
Remove(key);
|
return true;
|
}
|
|
public bool TryUpdate(string key, string newValue, int expireSeconds = -1)
|
{
|
if (!Exists(key)) return false;
|
Add(key, newValue, expireSeconds);
|
return true;
|
}
|
|
public bool TryUpdateIfChanged(string key, string newValue, int expireSeconds = -1)
|
{
|
var existing = Get(key);
|
if (existing == null) return false;
|
if (existing == newValue) return false; // 值相同,不更新
|
Add(key, newValue, expireSeconds);
|
return true;
|
}
|
|
public bool TryUpdateIfChanged<T>(string key, T newValue, int expireSeconds = -1) where T : class
|
{
|
var fullKey = BuildKey(key);
|
|
// 总是从Redis获取当前实际值进行比较,确保数据一致性
|
string? existingJson = null;
|
if (RedisAvailable)
|
{
|
try
|
{
|
var value = _connectionManager.GetDatabase().StringGet(fullKey);
|
if (!value.IsNullOrEmpty)
|
existingJson = value.ToString();
|
}
|
catch { }
|
}
|
|
if (existingJson == null)
|
{
|
// Redis不可用,检查内存缓存
|
if (_options.EnableL1Cache && _memoryCache.TryGetValue(fullKey, out string? cached) && cached != null)
|
existingJson = cached;
|
else
|
return false;
|
}
|
|
var newJson = _serializer.Serialize(newValue);
|
if (existingJson == newJson) return false; // JSON字符串相同,不更新
|
|
// 先写入Redis,成功后再更新内存缓存
|
if (RedisAvailable)
|
{
|
try
|
{
|
var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
|
if (!_connectionManager.GetDatabase().StringSet(fullKey, newJson, expiry))
|
{
|
// Redis写入失败
|
_logger.LogWarning("Redis TryUpdateIfChanged写入失败, key={Key}", key);
|
return _options.FallbackToMemory && _options.EnableL1Cache;
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning(ex, "Redis TryUpdateIfChanged写入失败, key={Key}", key);
|
return _options.FallbackToMemory && _options.EnableL1Cache;
|
}
|
}
|
|
// Redis写入成功(或Redis不可用时),更新内存缓存
|
if (_options.EnableL1Cache)
|
{
|
SetMemoryCache(fullKey, newJson, expireSeconds, false);
|
}
|
|
return true;
|
}
|
|
public string GetOrAdd(string key, string value, int expireSeconds = -1)
|
{
|
var existing = Get(key);
|
if (existing != null) return existing;
|
Add(key, value, expireSeconds);
|
return value;
|
}
|
|
public string GetOrAdd(string key, Func<string, string> valueFactory, int expireSeconds = -1)
|
{
|
var existing = Get(key);
|
if (existing != null) return existing;
|
var value = valueFactory(key);
|
Add(key, value, expireSeconds);
|
return value;
|
}
|
|
public T GetOrAdd<T>(string key, Func<string, T> valueFactory, int expireSeconds = -1) where T : class
|
{
|
var existing = Get<T>(key);
|
if (existing != null) return existing;
|
var value = valueFactory(key);
|
AddObject(key, value, expireSeconds);
|
return value;
|
}
|
|
#endregion
|
|
public void Dispose()
|
{
|
if (_disposed) return;
|
_disposed = true;
|
}
|
}
|
}
|