IGeekFan.AspNetCore.OutputCaching.FreeRedis
ASP.NET Core 输出缓存(Output Caching)的 FreeRedis 分布式存储实现,将缓存内容写入 Redis,支持多实例共享与按标签(Tag)失效。
对应 ASP.NET Core 内置的
Microsoft.AspNetCore.OutputCaching中间件(.NET 7+),并非旧的 Response Caching。
功能概览
| 功能 | 说明 |
|---|---|
| 分布式存储 | 输出缓存内容存入 Redis,多实例共享 |
| 标签失效 | 按 Tag 批量过期缓存(EvictByTagAsync) |
| 连接复用 | 可复用已有的 FreeRedis IRedisClient |
| 键前缀 | 通过 InstanceName 隔离不同应用 |
支持的框架与依赖
| 项 | 值 |
|---|---|
| 目标框架 | net8.0、net10.0 |
| 主要依赖 | FreeRedis 1.5.5、Microsoft.Extensions.Options |
| 集成 | Microsoft.AspNetCore.OutputCaching(框架自带) |
安装
dotnet add package IGeekFan.AspNetCore.OutputCaching.FreeRedis
快速开始
场景:多实例 Web 部署
1. 配置 Redis
{
"ConnectionStrings": {
"Redis": "127.0.0.1:6379,password=***,defaultDatabase=2"
}
}
2. 注册服务
需要先调用框架内置的 AddOutputCache(),再用本包把存储替换为 FreeRedis:
using FreeRedis;
using IGeekFan.AspNetCore.OutputCaching.FreeRedis;
var builder = WebApplication.CreateBuilder(args);
// 1. 启用输出缓存(框架内置)
builder.Services.AddOutputCache(options =>
{
// 默认策略:缓存 60 秒
options.AddBasePolicy(policy => policy.Expire(TimeSpan.FromSeconds(60)));
});
// 2. 用 FreeRedis 作为分布式存储
builder.Services.AddFreeRedisOutputCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("Redis");
options.InstanceName = "MyApp:"; // 可选:键前缀,隔离不同应用
});
var app = builder.Build();
app.UseOutputCache();
app.MapGet("/weather", () => Results.Ok(new { Temp = 26 }))
.CacheOutput(); // 该路由走输出缓存
app.Run();
3. 在 Controller 上启用
[ApiController]
[Route("api/[controller]")]
public class ArticleController : ControllerBase
{
// 缓存 30 秒,并打上 "articles" 标签
[HttpGet("{id}")]
[OutputCache(Duration = 30, Tags = new[] { "articles" })]
public async Task<IActionResult> Get(Guid id) => Ok(/* ... */);
}
场景:复用已有 FreeRedis 客户端
如果项目中已经有一个 IRedisClient(例如 SignalR、DataProtection 共用),可直接传入,避免重复建连:
using FreeRedis;
using IGeekFan.AspNetCore.OutputCaching.FreeRedis;
var builder = WebApplication.CreateBuilder(args);
var redis = new RedisClient(builder.Configuration.GetConnectionString("Redis"));
builder.Services.AddSingleton<IRedisClient>(redis);
builder.Services.AddOutputCache();
// 复用现有客户端(无需 Configuration,可选 InstanceName)
builder.Services.AddFreeRedisOutputCache(redis, options =>
{
options.InstanceName = "MyApp:";
});
按标签失效
缓存写入时会把 Tags 维护到 Redis 的 tag: 集合中;调用 EvictByTagAsync 即可批量清除:
// 手动按标签失效(例如在内容更新后)
public class ArticleManager
{
private readonly IOutputCacheStore _store;
public ArticleManager(IOutputCacheStore store) => _store = store;
public async Task InvalidateAsync(CancellationToken ct)
{
// 清除所有带 "articles" 标签的缓存条目
await _store.EvictByTagAsync("articles", ct);
}
}
也可通过框架内置的 IOutputCacheStore 在中间件/过滤器中统一触发,无需直接引用本包类型。
Redis 键结构
| 键 | 前缀 | 说明 |
|---|---|---|
| 缓存值 | {InstanceName}oc:{key} | 序列化后的响应内容 |
| 标签集合 | {InstanceName}tag:{tag} | 该标签关联的缓存值键集合(Redis Set) |
InstanceName 为空时前缀省略,默认即 oc: / tag:。
配置选项
| 选项 | 类型 | 说明 | 必填 |
|---|---|---|---|
Configuration | string? | FreeRedis 连接字符串;使用 IRedisClient 重载时可不填 | 否* |
InstanceName | string? | 所有键的前缀,用于多应用隔离 | 否 |
* 仅在使用 Action<FreeRedisOutputCacheOptions> 重载且未传入 IRedisClient 时,Configuration 为必填;缺失会抛出 InvalidOperationException。
运维建议
| 建议 | 说明 |
|---|---|
| 独立 Redis DB | 缓存分配独立 Redis DB,避免与业务数据相互干扰 |
| 合理设置过期 | 通过 Duration/Expire 控制 TTL,避免脏数据长期驻留 |
| 多实例一致 | 所有实例使用同一 Redis,保证缓存视图一致 |
| 高可用 | 生产环境启用 Redis 持久化与哨兵/集群 |
| 监控内存 | 关注 Redis 内存与键数量,必要时用 Tag 精准失效 |
故障排查
| 问题 | 解决方案 |
|---|---|
Configuration must be provided | 使用 Action<> 重载时必须设置 FreeRedisOutputCacheOptions.Configuration |
| 缓存未生效 | 确认已调用框架的 AddOutputCache() 且管道中有 UseOutputCache() |
| 多实例不一致 | 检查各实例是否指向同一 Redis 实例与相同 InstanceName |
| 内存增长 | 检查 TTL 设置,或使用 Tag 主动失效 |
相关文档
- SignalR.FreeRedis - 复用同一 Redis 做消息广播
- DataProtection.FreeRedis - 同一 Redis 存密钥
- Core - 基础包
- 包能力矩阵