using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StackExchange.Redis;
using System.Collections.Concurrent;
using WIDESEAWCS_RedisService.Connection;
using WIDESEAWCS_RedisService.Options;
using WIDESEAWCS_RedisService.Serialization;
namespace WIDESEAWCS_RedisService.Cache
{
///
/// Redis到内存缓存的自动同步后台服务
/// 定期从Redis提取数据并覆盖内存缓存,确保数据一致性
///
public class CacheSyncBackgroundService : BackgroundService
{
private readonly IRedisConnectionManager _connectionManager;
private readonly IMemoryCache _memoryCache;
private readonly IRedisSerializer _serializer;
private readonly RedisOptions _options;
private readonly ILogger _logger;
private readonly ConcurrentDictionary _trackedKeys;
public CacheSyncBackgroundService(
IRedisConnectionManager connectionManager,
IMemoryCache memoryCache,
IRedisSerializer serializer,
IOptions options,
ILogger logger)
{
_connectionManager = connectionManager;
_memoryCache = memoryCache;
_serializer = serializer;
_options = options.Value;
_logger = logger;
_trackedKeys = new ConcurrentDictionary();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// 如果未启用L1缓存,则无需同步
if (!_options.EnableL1Cache)
{
_logger.LogInformation("L1缓存未启用,后台同步服务不会运行");
return;
}
// 如果未启用自动同步,则不运行
if (!_options.EnableAutoSync)
{
_logger.LogInformation("Redis自动同步未启用,后台同步服务不会运行");
return;
}
_logger.LogInformation("Redis缓存同步服务已启动,同步间隔: {Interval}秒", _options.SyncIntervalSeconds);
// 等待Redis连接就绪
while (!_connectionManager.IsConnected && !stoppingToken.IsCancellationRequested)
{
_logger.LogWarning("等待Redis连接就绪...");
await Task.Delay(5000, stoppingToken);
}
if (stoppingToken.IsCancellationRequested)
return;
// 启动时先进行一次全量同步
await SyncCacheAsync(stoppingToken);
// 定期同步
while (!stoppingToken.IsCancellationRequested)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(_options.SyncIntervalSeconds), stoppingToken);
if (_connectionManager.IsConnected)
{
await SyncCacheAsync(stoppingToken);
}
else
{
_logger.LogWarning("Redis未连接,跳过本次同步");
}
}
catch (OperationCanceledException)
{
// 正常退出
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "缓存同步过程中发生错误");
}
}
_logger.LogInformation("Redis缓存同步服务已停止");
}
///
/// 执行缓存同步
///
private async Task SyncCacheAsync(CancellationToken stoppingToken)
{
try
{
var db = _connectionManager.GetDatabase();
var server = _connectionManager.GetServer();
var keyPrefix = _options.KeyPrefix;
// 获取所有匹配前缀的Redis key
var redisKeys = server.Keys(pattern: $"{keyPrefix}*", pageSize: _options.SyncBatchSize).ToList();
if (redisKeys.Count == 0)
{
_logger.LogDebug("Redis中没有找到匹配前缀 {Prefix} 的key", keyPrefix);
return;
}
int syncedCount = 0;
int skippedCount = 0;
int removedCount = 0;
// 1. 同步Redis中的数据到内存缓存
foreach (var redisKey in redisKeys)
{
if (stoppingToken.IsCancellationRequested)
break;
try
{
var keyStr = redisKey.ToString();
_trackedKeys.AddOrUpdate(keyStr, true, (_, _) => true);
// 获取Redis中的值
var value = db.StringGet(redisKey);
if (!value.IsNullOrEmpty)
{
var valueStr = value.ToString();
var ttl = db.KeyTimeToLive(redisKey);
// 获取过期时间,如果没有设置TTL则使用默认5分钟
var expireSeconds = ttl.HasValue && ttl.Value.TotalSeconds > 0
? (int)ttl.Value.TotalSeconds
: 300;
// 更新内存缓存
var entryOptions = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(expireSeconds)
};
_memoryCache.Set(keyStr, valueStr, entryOptions);
syncedCount++;
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "同步key {Key} 时发生错误", redisKey);
}
}
// 2. 清理内存缓存中不存在于Redis的key
// 注意:IMemoryCache不支持枚举,这里只能清理已跟踪的key
var keysToRemove = _trackedKeys.Where(k => !redisKeys.Contains(k.Key)).Select(k => k.Key).ToList();
foreach (var keyToRemove in keysToRemove)
{
_memoryCache.Remove(keyToRemove);
_trackedKeys.TryRemove(keyToRemove, out _);
removedCount++;
}
_logger.LogInformation(
"缓存同步完成: 同步 {SyncedCount} 个, 跳过 {SkippedCount} 个, 清理 {RemovedCount} 个, 总计 {TotalCount} 个key",
syncedCount, skippedCount, removedCount, redisKeys.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "缓存同步时发生错误");
}
}
///
/// 获取同步统计信息
///
public CacheSyncStatistics GetStatistics()
{
return new CacheSyncStatistics
{
IsEnabled = _options.EnableAutoSync && _options.EnableL1Cache,
SyncIntervalSeconds = _options.SyncIntervalSeconds,
TrackedKeysCount = _trackedKeys.Count,
IsRedisConnected = _connectionManager.IsConnected
};
}
}
///
/// 缓存同步统计信息
///
public class CacheSyncStatistics
{
public bool IsEnabled { get; set; }
public int SyncIntervalSeconds { get; set; }
public int TrackedKeysCount { get; set; }
public bool IsRedisConnected { get; set; }
}
}