ASP.NET Core 整合 Redis 应用
大约 13 分钟约 4023 字
ASP.NET Core 整合 Redis 应用
简介
Redis 在 ASP.NET Core 项目里最常见的角色是缓存层,但它并不只适合做简单的 Key-Value 缓存。真实项目中,Redis 往往同时承担:热点数据缓存、分布式锁、会话存储、排行榜、限流计数器、发布订阅与轻量消息中转等职责。真正落地时,关键不是“连上 Redis”,而是设计好 Key 规则、过期策略、失效机制、并发行为和故障兜底。
特点
实现
基础接入:IDistributedCache + ConnectionMultiplexer
{
"ConnectionStrings": {
"Redis": "localhost:6379,password=123456,defaultDatabase=0"
},
"Redis": {
"InstanceName": "SunnyFan:"
}
}using StackExchange.Redis;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("Redis");
options.InstanceName = builder.Configuration["Redis:InstanceName"];
});
builder.Services.AddSingleton<IConnectionMultiplexer>(_ =>
{
return ConnectionMultiplexer.Connect(builder.Configuration.GetConnectionString("Redis")!);
});public class CacheService
{
private readonly IDistributedCache _cache;
public CacheService(IDistributedCache cache)
{
_cache = cache;
}
public async Task SetAsync<T>(string key, T value, TimeSpan ttl)
{
var json = JsonSerializer.Serialize(value);
await _cache.SetStringAsync(key, json, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = ttl
});
}
public async Task<T?> GetAsync<T>(string key)
{
var json = await _cache.GetStringAsync(key);
return json is null ? default : JsonSerializer.Deserialize<T>(json);
}
public Task RemoveAsync(string key) => _cache.RemoveAsync(key);
}适合 IDistributedCache 的场景:
- 简单缓存
- 会话缓存
- 统一抽象调用
如果需要直接使用 Redis 丰富数据结构,通常要配合 ConnectionMultiplexer。Cache Aside 模式与业务接入
public interface ICacheFacade
{
Task<T?> GetOrSetAsync<T>(string key, Func<Task<T?>> factory, TimeSpan ttl);
Task RemoveAsync(string key);
}
public class RedisCacheFacade : ICacheFacade
{
private readonly IDistributedCache _cache;
private readonly ILogger<RedisCacheFacade> _logger;
public RedisCacheFacade(IDistributedCache cache, ILogger<RedisCacheFacade> logger)
{
_cache = cache;
_logger = logger;
}
public async Task<T?> GetOrSetAsync<T>(string key, Func<Task<T?>> factory, TimeSpan ttl)
{
var cached = await _cache.GetStringAsync(key);
if (cached is not null)
{
_logger.LogDebug("Redis hit: {Key}", key);
return JsonSerializer.Deserialize<T>(cached);
}
_logger.LogDebug("Redis miss: {Key}", key);
var value = await factory();
if (value is null) return value;
await _cache.SetStringAsync(key, JsonSerializer.Serialize(value), new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = ttl
});
return value;
}
public Task RemoveAsync(string key) => _cache.RemoveAsync(key);
}public class ProductService
{
private readonly ICacheFacade _cache;
private readonly IProductRepository _repository;
public ProductService(ICacheFacade cache, IProductRepository repository)
{
_cache = cache;
_repository = repository;
}
public Task<ProductDto?> GetByIdAsync(long id)
{
return _cache.GetOrSetAsync(
key: $"product:{id}",
factory: () => _repository.GetByIdAsync(id),
ttl: TimeSpan.FromMinutes(10));
}
public async Task UpdateAsync(ProductDto dto)
{
await _repository.UpdateAsync(dto);
await _cache.RemoveAsync($"product:{dto.Id}");
}
}Cache Aside 最常见流程:
1. 先查 Redis
2. 未命中再查数据库
3. 查到后写回缓存
4. 更新数据后主动删缓存直接使用 Redis 数据结构
public class RankingService
{
private readonly IConnectionMultiplexer _redis;
public RankingService(IConnectionMultiplexer redis)
{
_redis = redis;
}
public async Task UpdateScoreAsync(string board, string userId, double score)
{
var db = _redis.GetDatabase();
await db.SortedSetAddAsync($"rank:{board}", userId, score);
}
public async Task<string[]> GetTop10Async(string board)
{
var db = _redis.GetDatabase();
return (await db.SortedSetRangeByRankAsync($"rank:{board}", 0, 9, Order.Descending))
.Select(x => x.ToString())
.ToArray();
}
}public class CounterService
{
private readonly IConnectionMultiplexer _redis;
public CounterService(IConnectionMultiplexer redis)
{
_redis = redis;
}
public async Task<long> IncrementPageViewAsync(string pageKey)
{
var db = _redis.GetDatabase();
return await db.StringIncrementAsync($"pv:{pageKey}");
}
}public class PubSubService
{
private readonly IConnectionMultiplexer _redis;
public PubSubService(IConnectionMultiplexer redis)
{
_redis = redis;
}
public async Task PublishAsync(string channel, string message)
{
await _redis.GetSubscriber().PublishAsync(channel, message);
}
public Task SubscribeAsync(string channel, Action<RedisChannel, RedisValue> handler)
{
return _redis.GetSubscriber().SubscribeAsync(channel, handler);
}
}锁、限流与缓存治理思路
public class SimpleDistributedLock
{
private readonly IConnectionMultiplexer _redis;
public SimpleDistributedLock(IConnectionMultiplexer redis)
{
_redis = redis;
}
public async Task<bool> TryLockAsync(string key, string value, TimeSpan ttl)
{
var db = _redis.GetDatabase();
return await db.StringSetAsync(key, value, ttl, when: When.NotExists);
}
}public class RateLimitCounterService
{
private readonly IConnectionMultiplexer _redis;
public RateLimitCounterService(IConnectionMultiplexer redis)
{
_redis = redis;
}
public async Task<long> CountAsync(string key, TimeSpan window)
{
var db = _redis.GetDatabase();
var count = await db.StringIncrementAsync(key);
if (count == 1)
await db.KeyExpireAsync(key, window);
return count;
}
}真实项目里要重点治理:
- Key 命名规范
- TTL 统一策略
- 热点 Key
- 缓存穿透 / 击穿 / 雪崩
- 更新后失效方式缓存穿透、击穿、雪崩治理
缓存穿透(查询不存在的数据)
// 缓存穿透:恶意请求不存在的 Key,绕过缓存直接打到数据库
// 解决方案 1:缓存空值
public class AntiPenetrationCacheService : ICacheFacade
{
private readonly IDistributedCache _cache;
private readonly ILogger<AntiPenetrationCacheService> _logger;
private static readonly TimeSpan NullCacheTtl = TimeSpan.FromMinutes(5);
public AntiPenetrationCacheService(
IDistributedCache cache, ILogger<AntiPenetrationCacheService> logger)
{
_cache = cache;
_logger = logger;
}
public async Task<T?> GetOrSetAsync<T>(string key, Func<Task<T?>> factory, TimeSpan ttl)
{
var cached = await _cache.GetStringAsync(key);
if (cached is not null)
{
if (cached == "NULL") // 空值标记
{
_logger.LogDebug("穿透防护命中(空值缓存): {Key}", key);
return default;
}
return JsonSerializer.Deserialize<T>(cached);
}
var value = await factory();
if (value is null)
{
// 缓存空值,短时间过期,防止反复穿透
await _cache.SetStringAsync(key, "NULL", new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = NullCacheTtl
});
return default;
}
await _cache.SetStringAsync(key, JsonSerializer.Serialize(value),
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = ttl });
return value;
}
public Task RemoveAsync(string key) => _cache.RemoveAsync(key);
}
// 解决方案 2:布隆过滤器(大量 Key 场景)
public class BloomFilterCacheService : ICacheFacade
{
private readonly IDistributedCache _cache;
private readonly IBloomFilter _bloomFilter;
private readonly ILogger<BloomFilterCacheService> _logger;
public BloomFilterCacheService(
IDistributedCache cache,
IBloomFilter bloomFilter,
ILogger<BloomFilterCacheService> logger)
{
_cache = cache;
_bloomFilter = bloomFilter;
_logger = logger;
}
public async Task<T?> GetOrSetAsync<T>(string key, Func<Task<T?>> factory, TimeSpan ttl)
{
// 先检查布隆过滤器,一定不存在则直接返回
if (!await _bloomFilter.MightContainAsync(key))
{
_logger.LogDebug("布隆过滤器拦截: {Key}", key);
return default;
}
var cached = await _cache.GetStringAsync(key);
if (cached is not null)
{
return JsonSerializer.Deserialize<T>(cached);
}
var value = await factory();
if (value is not null)
{
await _bloomFilter.AddAsync(key);
await _cache.SetStringAsync(key, JsonSerializer.Serialize(value),
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = ttl });
}
return value;
}
public Task RemoveAsync(string key) => _cache.RemoveAsync(key);
}
// 布隆过滤器接口(可使用 RedisBloom 或本地实现)
public interface IBloomFilter
{
Task<bool> MightContainAsync(string key);
Task AddAsync(string key);
}缓存击穿(热点 Key 失效)
// 缓存击穿:某个热点 Key 在同一时刻过期,大量请求同时穿透到数据库
// 解决方案 1:互斥锁(只允许一个线程回源)
public class AntiStampedeCacheService : ICacheFacade
{
private readonly IDistributedCache _cache;
private readonly IConnectionMultiplexer _redis;
private readonly ILogger<AntiStampedeCacheService> _logger;
public AntiStampedeCacheService(
IDistributedCache cache,
IConnectionMultiplexer redis,
ILogger<AntiStampedeCacheService> logger)
{
_cache = cache;
_redis = redis;
_logger = logger;
}
public async Task<T?> GetOrSetAsync<T>(string key, Func<Task<T?>> factory, TimeSpan ttl)
{
var cached = await _cache.GetStringAsync(key);
if (cached is not null)
{
return JsonSerializer.Deserialize<T>(cached);
}
// 尝试获取互斥锁
var lockKey = $"lock:{key}";
var lockValue = Guid.NewGuid().ToString("N");
var db = _redis.GetDatabase();
var locked = await db.StringSetAsync(lockKey, lockValue, TimeSpan.FromSeconds(10),
when: When.NotExists);
if (locked)
{
try
{
_logger.LogDebug("获取回源锁成功: {Key}", key);
// 双重检查(可能其他线程已经回源)
cached = await _cache.GetStringAsync(key);
if (cached is not null)
return JsonSerializer.Deserialize<T>(cached);
var value = await factory();
if (value is not null)
{
await _cache.SetStringAsync(key, JsonSerializer.Serialize(value),
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = ttl });
}
return value;
}
finally
{
// 安全释放锁(Lua 脚本保证原子性)
var script = @"
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end";
await db.ScriptEvaluateAsync(script,
new RedisKey[] { lockKey },
new RedisValue[] { lockValue });
}
}
// 未获取锁,短暂等待后重试
_logger.LogDebug("未获取回源锁,等待重试: {Key}", key);
await Task.Delay(100);
return await GetOrSetAsync(key, factory, ttl);
}
public Task RemoveAsync(string key) => _cache.RemoveAsync(key);
}
// 解决方案 2:逻辑过期(永不过期 + 异步刷新)
// 缓存不设过期时间,而是在值中嵌入逻辑过期时间
// 读取时如果发现逻辑过期,返回旧值 + 异步刷新
public record CacheWrapper<T>(T Data, DateTimeOffset ExpireAt);
public class LogicalExpireCacheService : ICacheFacade
{
private readonly IConnectionMultiplexer _redis;
private readonly ILogger<LogicalExpireCacheService> _logger;
public LogicalExpireCacheService(
IConnectionMultiplexer redis,
ILogger<LogicalExpireCacheService> logger)
{
_redis = redis;
_logger = logger;
}
public async Task<T?> GetOrSetAsync<T>(string key, Func<Task<T?>> factory, TimeSpan ttl)
{
var db = _redis.GetDatabase();
var json = await db.StringGetAsync(key);
if (json.HasValue)
{
var wrapper = JsonSerializer.Deserialize<CacheWrapper<T>>(json!);
if (wrapper is not null)
{
if (wrapper.ExpireAt > DateTimeOffset.UtcNow)
{
return wrapper.Data; // 未过期
}
// 逻辑过期,返回旧值 + 异步刷新
_ = RefreshAsync(key, factory, ttl);
return wrapper.Data;
}
}
// 首次加载
var value = await factory();
if (value is not null)
{
var wrapper = new CacheWrapper<T>(value, DateTimeOffset.UtcNow.Add(ttl));
await db.StringSetAsync(key, JsonSerializer.Serialize(wrapper));
}
return value;
}
private async Task RefreshAsync<T>(string key, Func<Task<T?>> factory, TimeSpan ttl)
{
var lockKey = $"refresh:{key}";
var db = _redis.GetDatabase();
var locked = await db.StringSetAsync(lockKey, "1", TimeSpan.FromSeconds(30),
when: When.NotExists);
if (!locked) return; // 已有其他线程在刷新
try
{
_logger.LogInformation("异步刷新缓存: {Key}", key);
var value = await factory();
if (value is not null)
{
var wrapper = new CacheWrapper<T>(value, DateTimeOffset.UtcNow.Add(ttl));
await db.StringSetAsync(key, JsonSerializer.Serialize(wrapper));
}
}
catch (Exception ex)
{
_logger.LogError(ex, "异步刷新缓存失败: {Key}", key);
}
finally
{
await db.KeyDeleteAsync(lockKey);
}
}
public Task RemoveAsync(string key)
{
return _redis.GetDatabase().KeyDeleteAsync(key);
}
}缓存雪崩(大量 Key 同时过期)
// 缓存雪崩:大量 Key 在同一时间过期,请求全部打到数据库
// 解决方案:TTL 加随机偏移
public class AntiAvalancheCacheService
{
private readonly IDistributedCache _cache;
private readonly Random _random = new();
public AntiAvalancheCacheService(IDistributedCache cache)
{
_cache = cache;
}
public async Task SetAsync<T>(string key, T value, TimeSpan baseTtl)
{
// 在基础 TTL 上加 0% ~ 20% 的随机偏移
var jitter = TimeSpan.FromMilliseconds(
_random.NextDouble() * baseTtl.TotalMilliseconds * 0.2);
var actualTtl = baseTtl + jitter;
var json = JsonSerializer.Serialize(value);
await _cache.SetStringAsync(key, json, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = actualTtl
});
}
public async Task<T?> GetAsync<T>(string key)
{
var json = await _cache.GetStringAsync(key);
return json is null ? default : JsonSerializer.Deserialize<T>(json);
}
}分布式锁进阶
RedLock 算法实现
// RedLock:多节点分布式锁(比单节点更可靠)
public class RedLock : IDisposable
{
private readonly IDatabase[] _databases;
private readonly string _key;
private readonly string _value;
private readonly TimeSpan _ttl;
private bool _locked;
public RedLock(
IConnectionMultiplexer[] connections,
string key, TimeSpan ttl)
{
_databases = connections.Select(c => c.GetDatabase()).ToArray();
_key = $"lock:{key}";
_value = Guid.NewGuid().ToString("N");
_ttl = ttl;
}
public async Task<bool> TryLockAsync()
{
int successCount = 0;
var tasks = _databases.Select(db =>
db.StringSetAsync(_key, _value, _ttl, when: When.NotExists));
var results = await Task.WhenAll(tasks);
successCount = results.Count(r => r);
// 多数节点加锁成功才算成功
_locked = successCount > _databases.Length / 2;
return _locked;
}
public void Dispose()
{
if (!_locked) return;
foreach (var db in _databases)
{
var script = @"
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end";
db.ScriptEvaluate(script,
new RedisKey[] { _key },
new RedisValue[] { _value });
}
}
}
// 使用示例
public class OrderService
{
private readonly IConnectionMultiplexer[] _redisConnections;
public async Task<bool> CreateOrderAsync(Order order)
{
await using var redLock = new RedLock(_redisConnections,
$"order:create:{order.UserId}", TimeSpan.FromSeconds(10));
if (!await redLock.TryLockAsync())
{
throw new DomainException("order_duplicate",
"您有正在处理的订单,请稍后再试");
}
// 执行订单创建逻辑
// ...
return true;
}
}高级数据结构应用
消息队列(Stream)
// Redis Stream 作为轻量消息队列
public class RedisStreamProducer
{
private readonly IConnectionMultiplexer _redis;
public RedisStreamProducer(IConnectionMultiplexer redis) => _redis = redis;
public async Task<string> PublishAsync(string stream, string type, object data)
{
var db = _redis.GetDatabase();
var values = new NameValueEntry[]
{
new("type", type),
new("data", JsonSerializer.Serialize(data)),
new("timestamp", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds())
};
var id = await db.StreamAddAsync(stream, values, maxLength: 10000);
return id.ToString()!;
}
}
public class RedisStreamConsumer : BackgroundService
{
private readonly IConnectionMultiplexer _redis;
private readonly ILogger<RedisStreamConsumer> _logger;
private const string GroupName = "order-processor-group";
private const string ConsumerName = "worker-1";
public RedisStreamConsumer(
IConnectionMultiplexer redis,
ILogger<RedisStreamConsumer> logger)
{
_redis = redis;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var db = _redis.GetDatabase();
// 创建消费者组(如果不存在)
try
{
await db.StreamCreateConsumerGroupAsync("order-events", GroupName, "$");
}
catch (RedisException)
{
// 组已存在,忽略
}
while (!stoppingToken.IsCancellationRequested)
{
try
{
var entries = await db.StreamReadGroupAsync(
stream: "order-events",
group: GroupName,
consumerName: ConsumerName,
count: 10,
block: TimeSpan.FromMilliseconds(5000));
foreach (var entry in entries)
{
var type = entry["type"].ToString();
var data = entry["data"].ToString();
_logger.LogInformation("消费消息 {Id}: {Type}", entry.Id, type);
// 处理消息
await ProcessMessageAsync(type, data);
// 确认消息
await db.StreamAcknowledgeAsync("order-events", GroupName, entry.Id);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "消费消息失败");
await Task.Delay(1000, stoppingToken);
}
}
}
private Task ProcessMessageAsync(string type, string data)
{
// 根据 type 分发处理
return Task.CompletedTask;
}
}用户会话与购物车
// Hash 存储用户购物车
public class CartService
{
private readonly IConnectionMultiplexer _redis;
public CartService(IConnectionMultiplexer redis) => _redis = redis;
// 添加商品到购物车
public async Task AddItemAsync(string userId, long productId, int quantity, decimal price)
{
var db = _redis.GetDatabase();
var cartKey = $"cart:{userId}";
await db.HashSetAsync(cartKey, new HashEntry[]
{
new($"item:{productId}:qty", quantity),
new($"item:{productId}:price", price)
});
// 设置购物车过期时间(7 天无操作自动清理)
await db.KeyExpireAsync(cartKey, TimeSpan.FromDays(7));
}
// 获取购物车所有商品
public async Task<Dictionary<long, (int Quantity, decimal Price)>> GetCartAsync(string userId)
{
var db = _redis.GetDatabase();
var entries = await db.HashGetAllAsync($"cart:{userId}");
var cart = new Dictionary<long, (int Quantity, decimal Price)>();
foreach (var entry in entries)
{
var parts = entry.Name.ToString().Split(':');
if (parts.Length == 2 && long.TryParse(parts[1], out var productId))
{
if (cart.ContainsKey(productId))
{
var existing = cart[productId];
cart[productId] = parts[0] == "qty"
? ((int)entry.Value, existing.Price)
: (existing.Quantity, (decimal)entry.Value);
}
else
{
cart[productId] = parts[0] == "qty"
? ((int)entry.Value, 0)
: (0, (decimal)entry.Value);
}
}
}
return cart;
}
// 清空购物车
public Task ClearCartAsync(string userId)
{
return _redis.GetDatabase().KeyDeleteAsync($"cart:{userId}");
}
}连接池与性能优化
ConnectionMultiplexer 配置优化
// 生产级 ConnectionMultiplexer 配置
builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
var connectionString = config.GetConnectionString("Redis")!;
var options = ConfigurationOptions.Parse(connectionString);
options.ConnectTimeout = 5000; // 连接超时 5 秒
options.SyncTimeout = 5000; // 同步操作超时
options.AsyncTimeout = 5000; // 异步操作超时
options.AbortOnConnectFail = false; // 连接失败不中止
options.ConnectRetry = 3; // 重试次数
options.ReconnectRetryPolicy = new ExponentialRetry(5000); // 断线重连策略
options.KeepAlive = 60; // TCP KeepAlive 60 秒
options.AllowAdmin = true; // 允许 ADMIN 命令
options.Password = config["Redis:Password"];
var connection = ConnectionMultiplexer.Connect(options);
// 注册连接状态变化事件
connection.ConnectionRestored += (_, e) =>
{
Console.WriteLine($"Redis 重连成功: {e.EndPoint}");
};
connection.ConnectionFailed += (_, e) =>
{
Console.WriteLine($"Redis 连接失败: {e.EndPoint}, 类型: {e.FailureType}");
};
connection.ErrorMessage += (_, e) =>
{
Console.WriteLine($"Redis 错误: {e.Message}");
};
return connection;
});
// Redis 连接健康检查
builder.Services.AddHealthChecks()
.AddRedis(builder.Configuration.GetConnectionString("Redis")!,
name: "redis");序列化优化
// 使用内存池减少 GC 压力
public class OptimizedCacheService
{
private readonly IDistributedCache _cache;
// 复用 JsonSerializerOptions
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
public OptimizedCacheService(IDistributedCache cache) => _cache = cache;
public async Task SetAsync<T>(string key, T value, TimeSpan ttl)
{
await _cache.SetStringAsync(key, JsonSerializer.Serialize(value, JsonOptions),
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = ttl });
}
public async Task<T?> GetAsync<T>(string key)
{
var json = await _cache.GetStringAsync(key);
return json is null ? default : JsonSerializer.Deserialize<T>(json, JsonOptions);
}
}Key 命名规范与治理
命名规范
// Key 命名规范(建议)
// 格式:{业务}:{模块}:{实体}:{标识}
// 示例:
// cache:user:profile:1001 — 用户资料缓存
// cache:product:detail:2001 — 商品详情缓存
// lock:order:create:1001 — 创建订单分布式锁
// rank:product:sales — 商品销量排行
// counter:pv:page:home — 首页 PV 计数
// stream:order:events — 订单事件流
// session:user:token:abc123 — 用户会话
// rate:user:api:1001 — 用户限流计数
public static class CacheKeys
{
public static string UserProfile(long userId) => $"cache:user:profile:{userId}";
public static string ProductDetail(long productId) => $"cache:product:detail:{productId}";
public static string OrderLock(long userId) => $"lock:order:create:{userId}";
public static string ProductRanking(string category) => $"rank:product:{category}";
public static string PageView(string page) => $"counter:pv:page:{page}";
public static string UserSession(string token) => $"session:user:token:{token}";
public static string RateLimit(string userId, string endpoint)
=> $"rate:user:{endpoint}:{userId}";
}监控与告警
// Redis 监控指标采集
public class RedisMetricsCollector : BackgroundService
{
private readonly IConnectionMultiplexer _redis;
private readonly ILogger<RedisMetricsCollector> _logger;
public RedisMetricsCollector(
IConnectionMultiplexer redis,
ILogger<RedisMetricsCollector> logger)
{
_redis = redis;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
var server = _redis.GetServer(_redis.GetEndPoints()[0]);
var info = await server.InfoAsync();
// 关键指标
var usedMemory = info.FirstOrDefault(i => i.Key == "used_memory")?.Value;
var maxMemory = info.FirstOrDefault(i => i.Key == "maxmemory")?.Value;
var connectedClients = info.FirstOrDefault(i => i.Key == "connected_clients")?.Value;
var keyspaceHits = info.FirstOrDefault(i => i.Key == "keyspace_hits")?.Value;
var keyspaceMisses = info.FirstOrDefault(i => i.Key == "keyspace_misses")?.Value;
if (long.TryParse(usedMemory, out var used) &&
long.TryParse(maxMemory, out var max) && max > 0)
{
var usagePercent = (double)used / max * 100;
if (usagePercent > 80)
{
_logger.LogWarning("Redis 内存使用率 {UsagePercent:F1}%", usagePercent);
}
}
_logger.LogDebug(
"Redis 状态: 内存={UsedMemory}, 客户端={Clients}, 命中={Hits}, 未命中={Misses}",
usedMemory, connectedClients, keyspaceHits, keyspaceMisses);
}
catch (Exception ex)
{
_logger.LogError(ex, "采集 Redis 指标失败");
}
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
}
}
}优点
缺点
总结
在 ASP.NET Core 中整合 Redis,真正有价值的部分不只是“把值存进去”,而是把缓存模式、失效策略、并发行为和故障退化设计清楚。用得好,Redis 是性能放大器;用不好,它会变成一致性和排障的放大器。
关键知识点
- Redis 不只是缓存,它还是多种分布式能力组件。
IDistributedCache适合简单缓存抽象,但不覆盖 Redis 全部能力。- Key、TTL、失效方式比“接了 Redis”本身更重要。
- 缓存策略必须和数据库更新策略一起设计。
项目落地视角
- 商品、用户资料、配置字典、权限菜单都常用 Redis 做缓存。
- 排行榜、限流、短信验证码、会话状态也很常见。
- 多实例服务尤其要把缓存与分布式锁、消息通知一起考虑。
- Redis 故障时必须有回源和降级能力,不能直接拖垮主业务。
常见误区
- 只会加缓存,不设计删除缓存和回源策略。
- 用 Redis 存关键业务主数据,却没有恢复和一致性方案。
- Key 命名混乱,后期根本没人敢删。
- 只关注命中率,不关注缓存带来的雪崩和热点问题。
进阶路线
- 深入学习缓存穿透、击穿、雪崩治理。
- 结合 Lua、Stream、RedLock、BloomFilter 做更复杂能力扩展。
- 学习 Redis 主从、哨兵、集群与持久化治理。
- 将 Redis 接入监控、告警、容量规划和慢查询分析。
适用场景
- 热点读缓存。
- 会话和验证码存储。
- 排行榜、限流、分布式锁。
- 轻量消息通知和状态同步。
落地建议
- 所有 Key 建立统一命名规范和过期策略约定。
- 高价值缓存写清楚:来源、TTL、失效条件、回源逻辑。
- 对热点缓存、锁和计数器建立监控指标。
- Redis 不可用时必须有兜底和降级方案。
排错清单
- 命中率低:检查 Key 设计、TTL 是否合理。
- 数据不一致:检查更新后是否及时删除/刷新缓存。
- 突然变慢:检查热点 Key、网络、连接池和 Redis 内存压力。
- 多实例并发异常:检查是否需要分布式锁或幂等控制。
