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 _logger; private bool _disposed; private bool RedisAvailable => _connectionManager.IsConnected; public HybridCacheService( IMemoryCache memoryCache, IRedisConnectionManager connectionManager, IRedisSerializer serializer, IOptions options, ILogger 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 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(string key) where T : class { var value = Get(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 keys) { if (keys == null) return 0; int count = 0; foreach (var key in keys) { if (Remove(key)) count++; } return count; } public int RemoveWhere(Func 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 items, int expireSeconds = -1) { if (items == null) return; foreach (var item in items) { Add(item.Key, item.Value, expireSeconds); } } public void AddAllObjects(IDictionary 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(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(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(json); // 刷新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) && cached != null) value = _serializer.Deserialize(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(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(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(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(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(value!); } catch (Exception ex) { _logger.LogWarning(ex, "Redis Get失败, key={Key}", key); return default; } } // 正常的L1+L2逻辑 if (_memoryCache.TryGetValue(fullKey, out string? cached) && cached != null) return _serializer.Deserialize(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(str); } catch (Exception ex) { _logger.LogWarning(ex, "Redis Get失败, key={Key}", key); return default; } } /// /// 安全更新:仅当内存缓存中的值与expectedVersion匹配时才更新 /// 防止并发写入时旧值覆盖新值 /// /// 值类型 /// 缓存键 /// 新值 /// 期望的版本(通常是旧对象的哈希值或时间戳) /// 从对象提取版本号的函数 /// 过期时间 /// 是否更新成功 public bool TrySafeUpdate( string key, T newValue, object? expectedVersion, Func 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(existingJson); } } catch { } } // 如果Redis不可用,从内存缓存获取 if (existingValue == null && _options.EnableL1Cache) { if (_memoryCache.TryGetValue(fullKey, out string? cached) && cached != null) { existingValue = _serializer.Deserialize(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(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(string key, out T? value) where T : class { value = Get(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(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 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(string key, Func valueFactory, int expireSeconds = -1) where T : class { var existing = Get(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; } } }