分类
缓存类型
| 类/接口 | 存储位置 | 适用场景 | 并发行为 | 过期策略 | 优点 | 缺点 |
内存缓存 | MemoryCache, IMemoryCache | 本地内存 | 单机应用、Web API | 使用锁机制实现线程安全 | 支持绝对过期和滑动过期 | 快速访问 简单易用 支持过期策略 | 不支持分布式 应用重启数据丢失 |
分布式缓存 | IDistributedCache | 外部服务(如 Redis、SQL) | 微服务、集群部署、云原生应用 | 取决于实现(如 Redis 支持高并发访问) | 支持绝对过期和滑动过期 | 支持跨服务器共享 可持久化 可横向扩展 | 访问速度略慢于内存 需要额外配置和依赖 |
响应缓存 | [ResponseCache]、中间件 | 内存或代理缓存 | 静态页面、API 响应缓存 | 由 HTTP 服务器或代理处理 | 通过属性或 HTTP 头指定缓存时长 | 降低服务器负载 提高响应速度 | 缓存粒度较粗 不适合频繁变化的数据 |
输出缓存 | OutputCache 中间件 | 内存 | ASP.NET Core Web 应用 | 使用锁机制实现线程安全 | 支持标签、策略、依赖等高级缓存控制 | 支持标签、策略、依赖 更灵活的缓存控制 | 仅适用于 Web 场景 需要 .NET 8+ |
自定义缓存 | 自定义类或第三方库 | 任意 | 特殊业务需求 | 取决于具体实现 | 可自定义过期逻辑 | 灵活可控 可结合业务逻辑 | 实现复杂度高 维护成本大 |
内存缓存
内存缓存是将数据存储在应用程序的本地内存中,以便快速访问,避免频繁访问数据库或外部服务。它适用于单机应用或不需要跨服务器共享缓存的场景。
Asp.net core 实例
注册缓存服务
在 Program.cs
或 Startup.cs
中
builder.Services.AddMemoryCache();
控制器中使用缓存
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{
private readonly IMemoryCache _cache;
public UserController(IMemoryCache cache)
{
_cache = cache;
}
[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
string cacheKey = $"User_{id}";
if (!_cache.TryGetValue(cacheKey, out string userData))
{
// 模拟从数据库获取数据
userData = $"用户{id} 的数据";
// 设置缓存选项
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(5)) // 滑动过期
.SetAbsoluteExpiration(TimeSpan.FromMinutes(30)); // 绝对过期
// 写入缓存
_cache.Set(cacheKey, userData, cacheEntryOptions);
}
return Ok(userData);
}
}
TryGetValue
:尝试从缓存中获取数据Set
:如果缓存中没有,就设置缓存SlidingExpiration
:如果在 5 分钟内没有访问,则过期AbsoluteExpiration
:无论访问与否,30 分钟后强制过期
控制台应用示例
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
class Program
{
static MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
static string cacheKey = "currentTime";
static void Main(string[] args)
{
Console.WriteLine("开始并发缓存测试...");
// 启动多个并发任务
Parallel.For(0, 10, i =>
{
Task.Run(() => AccessCache(i));
});
Console.WriteLine("按任意键退出...");
Console.ReadKey();
}
static void AccessCache(int threadId)
{
if (!_cache.TryGetValue(cacheKey, out string cachedTime))
{
lock (_cache) // 保证写入线程安全
{
if (!_cache.TryGetValue(cacheKey, out cachedTime))
{
cachedTime = DateTime.Now.ToString("HH:mm:ss.fff");
var options = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(10))
.SetAbsoluteExpiration(TimeSpan.FromMinutes(1));
_cache.Set(cacheKey, cachedTime, options);
Console.WriteLine($"线程 {threadId}: 缓存未命中,写入时间 {cachedTime}");
}
}
}
else
{
Console.WriteLine($"线程 {threadId}: 缓存命中,读取时间 {cachedTime}");
}
}
}
分布式缓存
分布式缓存(Distributed Cache) 是一种将缓存数据存储在外部服务(如 Redis、SQL Server 等)中的机制,适用于多服务器部署、微服务架构或云原生应用。它通过实现 IDistributedCache
接口来提供统一的缓存访问方式。
实现方式 | NuGet 包名 | 特点 |
---|---|---|
Redis | Microsoft.Extensions.Caching.StackExchangeRedis | 高性能、支持持久化、适合大规模分布式系统 |
SQL Server | Microsoft.Extensions.Caching.SqlServer | 易于集成、适合已有数据库环境 |
NCache | Alachisoft.NCache.OpenSource.SDK | 企业级缓存解决方案 |
Memcached | 第三方库,如 EnyimMemcachedCore | 轻量级、适合简单分布式缓存场景 |
Asp.net core 实例
配置服务
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379"; // Redis 连接字符串
options.InstanceName = "SampleInstance";
});
使用缓存(在控制器或服务中)
using Microsoft.Extensions.Caching.Distributed;
public class MyService
{
private readonly IDistributedCache _cache;
public MyService(IDistributedCache cache)
{
_cache = cache;
}
public async Task<string> GetDataAsync()
{
string cacheKey = "myData";
// 从缓存中获取数据
string cachedData = await _cache.GetStringAsync(cacheKey);
if (cachedData == null)
{
cachedData = $"数据生成时间:{DateTime.Now}";
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
};
// 写入数据
await _cache.SetStringAsync(cacheKey, cachedData, options);
}
return cachedData;
}
}
问题和方案
问题 | 描述 | 方案 |
一致性问题 | 多个节点同时读写缓存,可能导致数据不一致(如缓存穿透、缓存雪崩) | 使用消息队列、事件驱动架构、延迟双删策略 |
网络延迟 | 相比本地内存缓存,访问分布式缓存需要网络通信,增加响应时间 | |
缓存雪崩 | 大量缓存同时过期,导致瞬间大量请求打到数据库,可能引发系统崩溃 | 设置缓存过期时间随机化、使用多级缓存、限流 |
缓存穿透 | 请求的数据在缓存和数据库中都不存在,导致每次都访问数据库 | 使用布隆过滤器、缓存空值 |
缓存击穿 | 热点数据在过期瞬间被大量请求同时访问,导致数据库压力激增 | 使用互斥锁(如 Redis 的分布式锁)防止并发访问热点数据 |
可用性依赖 | 分布式缓存服务(如 Redis)宕机或网络中断会影响整个系统的性能或可用性 | 使用主从复制、哨兵模式、集群部署等高可用架构 |
数据同步复杂 | 缓存与数据库之间的数据同步机制复杂,尤其是写操作后的失效策略 | |
安全性问题 | 分布式缓存暴露在网络中,可能面临未授权访问、数据泄露等风险 |
响应缓存
响应缓存(Response Caching) 是一种用于缓存 HTTP 响应的机制,目的是减少服务器负载、提高响应速度,尤其适用于静态内容或变化不频繁的 API 响应。
特性 | 描述 |
---|---|
缓存位置 | 通常在客户端、代理服务器或中间件中缓存 |
控制方式 | 通过 HTTP 头(如 Cache-Control )或 [ResponseCache] 特性控制 |
适用场景 | 静态页面、公共 API、无需频繁更新的数据 |
不适用场景 | 用户特定数据、频繁变化的内容、需要身份验证的请求 |
ASP.NET Core 实例
配置中间件
builder.Services.AddResponseCaching();
var app = builder.Build();
app.UseResponseCaching();
控制器中使用 [ResponseCache]
特性
[ApiController]
[Route("[controller]")]
public class WeatherController : ControllerBase
{
[HttpGet]
//Duration 缓存持续时间(秒)
//Location 缓存位置(客户端、服务器、任意)
//NoStore 是否禁止缓存(设置为 true 表示不缓存)
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any, NoStore = false)]
public IActionResult GetWeather()
{
var result = new
{
Time = DateTime.Now.ToString("HH:mm:ss"),
Temp = new Random().Next(15, 30)
};
return Ok(result);
}
}
- 响应缓存只缓存 GET 请求。
- 不缓存带有
Authorization
头的请求,除非显式允许。 - 如果你需要更复杂的缓存策略(如标签、依赖、条件缓存),建议使用 Output Caching
输出缓存
输出缓存(Output Caching) 是对传统响应缓存的增强,提供了更强大、更灵活的缓存控制能力,适用于 ASP.NET Core Web 应用。它可以缓存整个 HTTP 响应,并支持标签、策略、条件缓存等高级功能。
特性 | 响应缓存 ([ResponseCache] ) | 输出缓存 (OutputCache ) |
---|---|---|
缓存粒度 | 控制器级别 | 路由级别、标签级别 |
缓存位置 | 客户端/代理 | 服务器端(内存) |
支持策略控制 | ❌ | ✅(基于策略) |
支持标签清除 | ❌ | ✅ |
支持条件缓存 | ❌ | ✅(如基于查询参数) |
.NET 版本要求 | .NET Core 2.0+ | .NET 8+ |
.NET 8 实例
启用中间件(Program.cs
)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOutputCache(); // 注册服务
var app = builder.Build();
app.UseOutputCache(); // 启用中间件
缓存一个 GET 路由
app.MapGet("/time", () =>
{
return Results.Ok(new { Time = DateTime.Now.ToString("HH:mm:ss") });
})
.CacheOutput(p => p.Expire(TimeSpan.FromSeconds(30)));
//这个接口的响应将在服务器端缓存 30 秒,期间所有请求返回相同内容。
使用标签清除缓存
app.MapPost("/clear-cache", (IOutputCacheStore cache) =>
{
return cache.EvictByTagAsync("time-tag");
});
app.MapGet("/time-tagged", () =>
{
return Results.Ok(new { Time = DateTime.Now.ToString("HH:mm:ss") });
})
.CacheOutput(p => p.Expire(TimeSpan.FromSeconds(60)).Tag("time-tag"));