缓存(Caching) 是一种临时存储数据的技术 ,目的是避免重复计算或重复访问慢速资源 (如数据库、网络 API、文件系统),从而显著提升系统性能和响应速度。
✅ 简单说:"把常用的数据放在更快的地方,下次直接拿,不用再算/再查。"
🎯 为什么需要缓存?
| 操作 | 典型耗时 |
|---|---|
| CPU 一级缓存访问 | ~1 纳秒 |
| 内存访问 | ~100 纳秒 |
| SSD 读取 | ~100 微秒 |
| 数据库查询 | 110 毫秒 |
| 网络 API 调用 | 50500 毫秒 |
👉 缓存能将"毫秒级"操作变成"微秒甚至纳秒级"!
🔁 缓存的基本工作流程
用户请求数据
│
▼
[缓存中查找]?
├── 是 → 直接返回(**缓存命中,Cache Hit**)
└── 否 →
│
▼
[从原始源获取](数据库/API等)
│
▼
[存入缓存](可选:设置过期时间)
│
▼
返回数据
🧱 缓存的核心要素
| 要素 | 说明 |
|---|---|
| Key(键) | 唯一标识缓存项(如 "user:123") |
| Value(值) | 被缓存的数据(对象、字符串、字节等) |
| TTL(Time-To-Live) | 过期时间(避免数据陈旧) |
| 淘汰策略 | 内存满时如何清理(LRU、LFU 等) |
💻 C# / .NET 中的缓存实现方式
1️⃣ 内存缓存(In-Memory Cache)
- 适用场景:单机应用、开发测试、非关键数据
- .NET 实现 :
IMemoryCache(来自Microsoft.Extensions.Caching.Memory)
示例:
// 注册服务(Program.cs)
builder.Services.AddMemoryCache();
// 使用
public class UserService
{
private readonly IMemoryCache _cache;
public UserService(IMemoryCache cache) => _cache = cache;
public User GetUser(int id)
{
// 尝试从缓存获取
if (_cache.TryGetValue($"user:{id}", out User user))
return user;
// 缓存未命中:查数据库
user = _db.Users.Find(id);
// 存入缓存(5分钟过期)
_cache.Set($"user:{id}", user, TimeSpan.FromMinutes(5));
return user;
}
}
⚠️ 缺点:进程重启后丢失;多实例部署时数据不一致。
2️⃣ 分布式缓存(Distributed Cache)
- 适用场景:Web 集群、微服务、高可用系统
- 常用中间件:Redis(最流行)、Memcached
- .NET 实现 :
IDistributedCache(抽象接口)
示例(使用 Redis):
// 安装包:StackExchange.Redis.Extensions 或 Microsoft.Extensions.Caching.StackExchangeRedis
// Program.cs
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
});
// 使用
public class ProductService
{
private readonly IDistributedCache _cache;
public async Task<Product> GetProductAsync(int id)
{
var json = await _cache.GetStringAsync($"product:{id}");
if (json != null)
return JsonSerializer.Deserialize<Product>(json);
// 查数据库
var product = await _db.Products.FindAsync(id);
// 存入 Redis(10分钟过期)
await _cache.SetStringAsync(
$"product:{id}",
JsonSerializer.Serialize(product),
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) }
);
return product;
}
}
✅ 优点:多实例共享、持久化(可选)、高并发支持。
3️⃣ HTTP 响应缓存(Response Caching)
- 适用场景:Web API、静态内容
- .NET 实现 :
[ResponseCache]特性 或 中间件
示例:
[HttpGet("{id}")]
[ResponseCache(Duration = 60)] // 浏览器/代理缓存 60 秒
public async Task<ActionResult<Product>> Get(int id)
{
return await _service.GetProductAsync(id);
}
💡 这会在 HTTP 响应头中添加
Cache-Control: public, max-age=60,让客户端或 CDN 缓存结果。
🧠 缓存策略(何时更新/失效?)
| 策略 | 说明 | 适用场景 |
|---|---|---|
| TTL(过期时间) | 固定时间后自动失效 | 数据允许短暂不一致(如商品详情) |
| 主动刷新 | 后台定时更新缓存 | 高频访问 + 数据变化可预测 |
| 写穿透(Write-Through) | 更新数据库时同步更新缓存 | 强一致性要求 |
| 失效(Invalidate) | 数据变更时删除缓存 | 简单可靠(推荐) |
| 懒加载(Lazy Load) | 首次访问时加载 | 降低启动开销 |
✅ 最佳实践 :"更新数据库后,删除缓存"(而非更新缓存),避免并发问题。
⚠️ 缓存常见问题与解决方案
| 问题 | 说明 | 解决方案 |
|---|---|---|
| 缓存雪崩 | 大量缓存同时过期,瞬间压垮数据库 | 设置随机 TTL(如 5min ± 30s) |
| 缓存穿透 | 查询不存在的数据,绕过缓存直击 DB | 缓存空值(null)+ 布隆过滤器 |
| 缓存击穿 | 热点 key 过期瞬间被大量请求打爆 | 互斥锁(只让一个线程重建缓存) |
| 数据不一致 | DB 和缓存内容不同步 | 采用"先更新 DB,再删缓存"策略 |
🌐 缓存层级(从快到慢)
| 层级 | 技术 | 位置 |
|---|---|---|
| L1 | CPU 缓存 | 硬件 |
| L2 | 内存缓存 | 应用进程内 |
| L3 | Redis/Memcached | 本地或远程服务器 |
| L4 | CDN 缓存 | 边缘节点 |
| L5 | 数据库查询缓存 | DB 内部(如 MySQL Query Cache) |
💡 一个请求可能经过多层缓存!
✅ 总结:缓存的核心价值
| 维度 | 效果 |
|---|---|
| 性能 | 响应时间降低 10~1000 倍 |
| 吞吐量 | 系统 QPS 显著提升 |
| 成本 | 减少数据库/外部 API 调用,节省资源 |
| 用户体验 | 页面加载更快,操作更流畅 |
🧠 记住 :
"缓存不是银弹,但没有缓存,现代高性能系统寸步难行。"
在 .NET 开发中:
- 单机用
IMemoryCache - 分布式用
IDistributedCache+ Redis - Web API 用
[ResponseCache]