- UID
- 661186
- 积分
- 0
- 金币
- 0
- 精华
- 0
- 威望
- 0
- 贡献
- 0
- 阅读权限
- 220
- 注册时间
- 2010-9-16
- 最后登录
- 2026-5-6
- 在线时间
- 0 小时
积极分子
- 金币
- 0
- 阅读权限
- 220
- 精华
- 0
- 威望
- 0
- 贡献
- 0
- 在线时间
- 0 小时
- 注册时间
- 2010-9-16
|
一、设计思路
1. 架构分层
- 一级缓存:
IMemoryCache(进程内内存缓存,读写纳秒级,无网络开销)
- 二级缓存:
IDistributedCache(Redis 分布式缓存,跨服务共享,毫秒级)
- 数据源:数据库 / 接口(兜底,避免缓存穿透)
2. 读写流程
读取数据(Get)
- 先查本地缓存,命中直接返回
- 本地未命中,查分布式缓存,命中则回写本地缓存
- 分布式未命中,查数据源,查到后同时写入本地 + 分布式缓存
- 未查到:返回空 / 处理缓存穿透
写入 / 更新数据(Set/Remove)
- 先更新数据源(保证数据可靠)
- 同时删除 / 更新 本地缓存 + 分布式缓存(保证双缓存一致性)
3. 关键配置
- 本地缓存过期时间 < 分布式缓存过期时间(避免本地脏数据)
- 支持缓存键前缀、序列化方式、过期时间单独配置
- 支持缓存穿透 (不存在)/ 击穿(单个过期) / 雪崩(大量过期)防护
二、完整代码实现
1. 依赖安装
# 内存缓存(框架自带)
# 分布式缓存(Redis)
Install-Package Microsoft.Extensions.Caching.StackExchangeRedis
# 序列化
Install-Package System.Text.Json
2. 双缓存核心接口
定义统一操作规范,解耦实现:
/// <summary>
/// 双缓存服务接口
/// </summary>
public interface IDoubleCache
{
// 获取缓存
Task<T> GetAsync<T>(string key);
// 设置缓存
Task SetAsync<T>(string key, T value,
TimeSpan? localExpire = null,
TimeSpan? distExpire = null);
// 删除缓存
Task RemoveAsync(string key);
}
3. 双缓存实现类(核心代码)
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using System.Text.Json;
public class DoubleCache : IDoubleCache
{
private readonly IMemoryCache _memoryCache;
private readonly IDistributedCache _distributedCache;
// 默认配置:本地缓存2分钟,分布式缓存5分钟
private readonly TimeSpan _defaultLocalExpire = TimeSpan.FromMinutes(2);
private readonly TimeSpan _defaultDistExpire = TimeSpan.FromMinutes(5);
public DoubleCache(IMemoryCache memoryCache, IDistributedCache distributedCache)
{
_memoryCache = memoryCache;
_distributedCache = distributedCache;
}
/// <summary>
/// 双缓存读取
/// </summary>
public async Task<T> GetAsync<T>(string key)
{
// 1. 优先读本地缓存
if (_memoryCache.TryGetValue(key, out var value) && value != null)
{
return (T)value;
}
// 2. 本地未命中,读分布式缓存
var distValue = await _distributedCache.GetStringAsync(key);
if (!string.IsNullOrEmpty(distValue))
{
var obj = JsonSerializer.Deserialize<T>(distValue);
// 回写本地缓存
_memoryCache.Set(key, obj, _defaultLocalExpire);
return obj;
}
// 3. 都未命中,返回默认值
return default;
}
/// <summary>
/// 双缓存写入
/// </summary>
public async Task SetAsync<T>(string key, T value,
TimeSpan? localExpire = null,
TimeSpan? distExpire = null)
{
if (value == null) return;
// 过期时间配置
var localExp = localExpire ?? _defaultLocalExpire;
var distExp = distExpire ?? _defaultDistExpire;
// 1. 写入本地缓存
_memoryCache.Set(key, value, localExp);
// 2. 写入分布式缓存
var jsonValue = JsonSerializer.Serialize(value);
await _distributedCache.SetStringAsync(key, jsonValue, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = distExp
});
}
/// <summary>
/// 双缓存删除
/// </summary>
public async Task RemoveAsync(string key)
{
// 删除本地
_memoryCache.Remove(key);
// 删除分布式
await _distributedCache.RemoveAsync(key);
}
}
4. 注册服务(Program.cs)
var builder = WebApplication.CreateBuilder(args);
// 1. 注册内存缓存
builder.Services.AddMemoryCache();
// 2. 注册Redis分布式缓存
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379,password=123456"; // Redis连接串
options.InstanceName = "DoubleCache:"; // 缓存键前缀
});
// 3. 注册双缓存服务
builder.Services.AddScoped<IDoubleCache, DoubleCache>();
// 业务服务注册
builder.Services.AddControllers();
5. 业务使用示例(Controller)
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
private readonly IDoubleCache _doubleCache;
// 模拟数据库仓储
private readonly IProductRepository _productRepository;
public ProductController(IDoubleCache doubleCache, IProductRepository productRepository)
{
_doubleCache = doubleCache;
_productRepository = productRepository;
}
/// <summary>
/// 获取商品(双缓存读取)
/// </summary>
[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(int id)
{
var key = $"product:{id}";
// 1. 查双缓存
var product = await _doubleCache.GetAsync<Product>(key);
if (product != null) return Ok(product);
// 2. 缓存未命中,查数据库
product = await _productRepository.GetByIdAsync(id);
if (product == null) return NotFound();
// 3. 写入双缓存
await _doubleCache.SetAsync(key, product);
return Ok(product);
}
/// <summary>
/// 更新商品(双缓存更新)
/// </summary>
[HttpPut("{id}")]
public async Task<IActionResult> UpdateProduct(int id, Product product)
{
// 1. 更新数据库
await _productRepository.UpdateAsync(product);
// 2. 删除双缓存(核心:保证缓存一致性)
await _doubleCache.RemoveAsync($"product:{id}");
return Ok();
}
}
三、优化
1. 缓存穿透防护
缓存空对象,避免大量无效请求打到数据库:
// 在 GetAsync 方法中补充
if (distValue == null)
{
// 缓存空对象,过期时间更短
_memoryCache.Set(key, null, TimeSpan.FromSeconds(30));
return default;
}
2. 缓存雪崩防护
// 随机偏移 10~30 秒
var localExp = localExpire ?? _defaultLocalExpire.Add(TimeSpan.FromSeconds(new Random().Next(10, 30)));
3. 缓存击穿防护
使用互斥锁,防止高并发下缓存失效时大量请求打数据库:
// 读取时加锁
lock (key)
{
if (_memoryCache.TryGetValue(key, out value))
{
return (T)value;
}
}
4. 支持泛型 + 灵活过期时间
已在核心代码中实现,可单独为每个缓存配置:
// 本地缓存1分钟,分布式缓存10分钟
await _doubleCache.SetAsync(key, data,
TimeSpan.FromMinutes(1),
TimeSpan.FromMinutes(10));
5. 跨实例缓存同步(可选)
多服务实例下,本地缓存更新可使用Redis 发布订阅通知所有实例删除本地缓存:
- 实例 A 更新数据 → 删除本地 + 分布式缓存
- 发布 Redis 消息
- 其他实例订阅消息 → 删除本地缓存
四、双缓存策略优缺点
优点
- 性能提高:热点数据走内存缓存,无网络开销
- 一致性强:分布式缓存保证跨实例数据一致
- 高可用:Redis 宕机可降级为纯本地缓存
- 抗流量冲击:秒杀 / 热点场景保护数据库
缺点
- 内存占用:本地缓存会占用应用进程内存
- 一致性成本:多实例需要额外机制同步本地缓存
总结
- 双缓存 = 本地缓存(快)+ 分布式缓存(一致)
- 核心流程:读先本地→再分布式→最后数据库;写先数据库→再删双缓存
- 生产环境:穿透 / 雪崩 / 击穿防护 + 过期时间随机化
- 代码支持灵活配置、泛型、自定义过期时间
来源:https://www.cnblogs.com/chuansheng/p/19916122 |
|