文章目录
- 前言
- 一、什么是缓存
- 二、内存缓存
- 三、使用内存缓存
- 1)注册内存缓存服务
- 2)注入与基本使用
- 3)高级用法
- GetOrCreate(避免缓存穿透)
- 异步方法:GetOrCreateAsync(避免缓存穿透)
- 两种过期策略混用
- 4)缓存策略配置
- 5)缓存雪崩
- 解决方案:
- ① 缓存过期时间随机化
- ② 互斥锁控制并发重建
- ③ 后台定时刷新(永不过期策略)
- ④ 多级缓存架构
- ⑤ 熔断降级机制(使用 Polly)
- ⑥ 缓存预热
- ⑦ 监控告警
- 最佳实践建议
- 6)注意事项
- 总结
前言
在 .NET Core 中,缓存是性能优化的重要手段之一。
一、什么是缓存
缓存(Caching)是提升应用性能的关键技术,通过存储频繁访问的数据来减少计算和数据库压力。
数据库中的索引等简单有效的优化功能本质上都是缓存。
二、内存缓存
在ASP.NET Core中,内存缓存(Memory Cache)是一种将数据存储在Web服务器内存中的机制,可显著提升应用性能。
三、使用内存缓存
1)注册内存缓存服务
-
在Program.cs中注册服务
builder.Services.AddMemoryCache();
2)注入与基本使用
-
通过依赖注入获取IMemoryCache实例
public class BookService {private readonly IMemoryCache _cache;public MyService(IMemoryCache cache){_cache = cache;} }
-
设置缓存项
// 简单设置 _cache.Set("key", "value");// 配置过期时间 var options = new MemoryCacheEntryOptions {AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30), // 绝对过期时间SlidingExpiration = TimeSpan.FromMinutes(5) // 滑动过期时间 }; _cache.Set("key", "value", options);
-
获取缓存项
if (_cache.TryGetValue("key", out string value)) {// 使用value }
-
删除缓存项
_cache.Remove("key");
3)高级用法
GetOrCreate(避免缓存穿透)
- 示例
var data = _cache.GetOrCreate("key", entry => {entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);//绝对过期策略return GetDataFromDatabase(); // 耗时操作 });
异步方法:GetOrCreateAsync(避免缓存穿透)
- 示例
var data = await _cache.GetOrCreateAsync("key", async entry => {entry.SlidingExpiration = TimeSpan.FromMinutes(5);//滑动过期策略return await FetchDataAsync(); });
两种过期策略混用
-
使用滑动过期时间策略,如果一个缓存一致被频繁访问,那么这个缓存项就会一直被续期而不过期。这种情况下可以对一个缓存同时设定滑动过期时间策略和绝对过期时间策略,并且把绝对过期时长设定为大于滑动过期时长,这样缓存项的内容会在绝对过期时间内随着访问被滑动过期续期,但是一旦超过了绝对过期时间,缓存项就会被删除。
var data = await _cache.GetOrCreateAsync("key", async entry => {entity.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(100);//绝对过期策略entity.SlidingExpiration=TimeSpan.FromSeconds(10);//滑动过期策略return await FetchDataAsync(); });
4)缓存策略配置
-
过期策略:
- 绝对过期:AbsoluteExpiration/AbsoluteExpirationRelativeToNow
- 滑动过期:SlidingExpiration(每次访问重置时间)
-
优先级:Priority(内存不足时决定清理顺序)
-
大小限制:通过SizeLimit设置总大小,条目通过Size指定占用量。
services.AddMemoryCache(options => {options.SizeLimit = 1024; // 总大小(单位自定义) });// 设置条目大小 _cache.Set("key", "value", new MemoryCacheEntryOptions { Size = 1 });
5)缓存雪崩
- 缓存雪崩通常是指大量缓存同时过期,导致请求直接打到数据库,引发数据库压力激增甚至崩溃的情况。
解决方案:
① 缓存过期时间随机化
- 通过为缓存项设置基础过期时间并添加随机偏移,分散缓存失效时间。
var data = await _cache.GetOrCreateAsync("key", async entry => {//设置基础过期时间并添加随机偏移,分散缓存失效时间,从而避免缓存雪崩entity.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(Random.Shared.NextInt64(30,40));//绝对过期策略entity.SlidingExpiration=TimeSpan.FromSeconds(10);//滑动过期策略return await FetchDataAsync(); });
② 互斥锁控制并发重建
- 使用 SemaphoreSlim 控制数据库查询并发(分布式环境需改用分布式锁)
public class CacheService {private static readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1, 1);public async Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, TimeSpan expiration){if (MemoryCache.TryGetValue(key, out T cachedValue))return cachedValue;await _cacheLock.WaitAsync();try{// 二次检查if (MemoryCache.TryGetValue(key, out cachedValue))return cachedValue;var value = await factory();MemoryCache.Set(key, value, expiration.AddRandomJitter());return value;}finally{_cacheLock.Release();}} }
③ 后台定时刷新(永不过期策略)
-
对热点数据采用后台定时刷新机制:
public class WarmupService : BackgroundService {protected override async Task ExecuteAsync(CancellationToken stoppingToken){while (!stoppingToken.IsCancellationRequested){await RefreshHotData();await Task.Delay(TimeSpan.FromMinutes(30), stoppingToken); // 每隔30分钟刷新}}private async Task RefreshHotData(){// 异步更新缓存逻辑} }
④ 多级缓存架构
- 结合内存缓存和分布式缓存
public class HybridCacheService {public async Task<T> GetAsync<T>(string key){// 第一层:内存缓存if (_memoryCache.TryGetValue(key, out T memoryValue))return memoryValue;// 第二层:分布式缓存var distributedValue = await _distributedCache.GetAsync<T>(key);if (distributedValue != null){_memoryCache.Set(key, distributedValue, TimeSpan.FromMinutes(5)); // 短期内存缓存return distributedValue;}// 第三层:数据库return await RefreshCacheFromDb(key);} }
⑤ 熔断降级机制(使用 Polly)
- 配置数据库访问熔断策略
public void ConfigureServices(IServiceCollection services) {var circuitBreakerPolicy = Policy.Handle<SqlException>().CircuitBreakerAsync(exceptionsBeforeBreaking: 3,durationOfBreak: TimeSpan.FromSeconds(30));services.AddHttpClient<IDatabaseService, DatabaseService>().AddPolicyHandler(circuitBreakerPolicy); }public class DatabaseService {public async Task<Data> GetDataWithFallback(){try{return await GetFromDatabase();}catch (BrokenCircuitException){return GetFallbackData(); // 返回静态默认数据}} }
⑥ 缓存预热
- 应用启动时预加载关键数据
public class Startup {public void Configure(IApplicationBuilder app, IWebHostEnvironment env){app.ApplicationServices.GetService<CacheWarmupper>().Warmup();} }public class CacheWarmupper {public void Warmup(){Parallel.ForEach(GetCriticalKeys(), key =>{PreloadCache(key);});} }
⑦ 监控告警
- 配置健康检查端点
services.AddHealthChecks().AddRedis("redis_conn_str", failureStatus: HealthStatus.Degraded).AddSqlServer("db_conn_str");// 在仪表盘监控: // - 缓存命中率 // - 数据库连接数 // - 错误率 // - 系统负载
最佳实践建议
- 分层失效:按数据重要性设置不同过期策略
- 动态调整:根据系统负载自动调整缓存时间
- 影子Key:设置辅助Key追踪数据访问频率
- 请求合并:对批量查询进行合并处理
- 降级开关:配置功能开关控制缓存策略
6)注意事项
-
线程安全:IMemoryCache是线程安全的。
-
内存管理:避免无限制缓存,合理设置过期时间和大小。
-
数据一致性:数据更新时需手动使缓存失效(Remove或重新Set)。
-
分布式环境:多服务器部署时,内存缓存无法共享,需改用Redis等分布式缓存。
总结
- 缓存不可变数据:确保缓存对象不会被修改,或在缓存时创建副本。
- 监控缓存命中率:评估缓存有效性,调整过期策略。
- 避免缓存敏感数据:内存数据易失,不适合存储机密信息。
通过合理使用内存缓存,可有效减少数据库压力,提升应用响应速度。