| | |
| | | public bool Add(string key, string value, int expireSeconds = -1, bool isSliding = false) |
| | | { |
| | | var fullKey = BuildKey(key); |
| | | SetMemoryCache(fullKey, value, expireSeconds, isSliding); |
| | | |
| | | // 只有启用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; |
| | | } |
| | |
| | | catch (Exception ex) |
| | | { |
| | | _logger.LogWarning(ex, "Redis Add失败, key={Key}", key); |
| | | return _options.FallbackToMemory; |
| | | return _options.FallbackToMemory && _options.EnableL1Cache; |
| | | } |
| | | } |
| | | |
| | |
| | | public bool Remove(string key) |
| | | { |
| | | var fullKey = BuildKey(key); |
| | | _memoryCache.Remove(fullKey); |
| | | if (!RedisAvailable) return true; |
| | | |
| | | // 只有启用L1缓存时才从内存缓存中移除 |
| | | if (_options.EnableL1Cache) |
| | | { |
| | | _memoryCache.Remove(fullKey); |
| | | } |
| | | |
| | | if (!RedisAvailable) return _options.EnableL1Cache; |
| | | try |
| | | { |
| | | return _connectionManager.GetDatabase().KeyDelete(fullKey); |
| | |
| | | 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); |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | /// <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); |
| | | |
| | |
| | | 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; |
| | | |
| | |
| | | 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); |