简介
在许多应用程序中,缓存是提升性能的常见方法,尤其是在访问频繁且不经常变化的数据时。MemoryCache 是 .NET 提供的一个内存缓存实现,它允许在内存中存储数据,以减少对数据库、文件系统或其他远程服务的访问,进而提升系统响应速度。
MemoryCache 的核心优势是:
- 高效:内存操作非常快速,适合用于缓存短期有效的数据。
- 轻量:它是一个线程安全的缓存系统,且易于在
.NET应用中配置和使用。 - 灵活:支持过期时间、优先级设置等多种功能,能够满足大多数缓存需求。
核心功能
- 线程安全
MemoryCache是线程安全的,允许多个线程同时访问缓存中的数据。
- 过期策略
- 绝对过期:指定一个具体的时间点,缓存项在该时间点后过期。
- 滑动过期:缓存项最后访问后的指定时间段内过期。
- 缓存项优先级
- 可以为缓存项设置优先级,允许缓存管理器在内存不足时根据优先级回收缓存项。
- 回调:
PostEvictionCallback:缓存项移除时触发回调。CacheEntryOptions:支持自定义过期和移除逻辑。
- 依赖关系:
- 使用
ChangeToken支持基于外部信号的缓存失效(如文件更改)。
- 使用
- 线程安全:内置并发控制,支持多线程访问。
DI集成:通过IMemoryCache接口与ASP.NET Core DI无缝集成。- 数据大小限制
- 可以设置缓存的最大容量,以防止占用过多内存。
- 支持绝对过期和滑动过期组合使用
- 能灵活配置缓存失效的时间策略。
核心 API
MemoryCache 主要通过 IMemoryCache 接口操作,位于 Microsoft.Extensions.Caching.Memory 命名空间。核心 API 如下:
- 接口方法:
ICacheEntry CreateEntry(object key):创建缓存项。bool TryGetValue(object key, out object value):尝试获取缓存值。void Remove(object key):移除缓存项。TItem Get<TItem>(object key):获取指定类型的缓存值。TItem GetOrCreate<TItem>(object key, Func<ICacheEntry, TItem> factory):获取或创建缓存值。Task<TItem> GetOrCreateAsync<TItem>(object key, Func<ICacheEntry, Task<TItem>> factory):异步获取或创建。TItem Set<TItem>(object key, TItem value, MemoryCacheEntryOptions options = null):设置缓存值。
MemoryCacheEntryOptions:DateTimeOffset? AbsoluteExpiration:绝对过期时间。TimeSpan? AbsoluteExpirationRelativeToNow:相对当前时间的绝对过期。TimeSpan? SlidingExpiration:滑动过期时间。CacheItemPriority Priority:缓存项优先级。IChangeToken ExpirationTokens:依赖的变更令牌。Action<ICacheEntry, EvictionReason, object> PostEvictionCallbacks:移除回调。long? Size:缓存项大小(用于大小限制,.NET 6+)。
MemoryCacheOptions:TimeSpan ExpirationScanFrequency:过期扫描间隔(默认 1 分钟)。long? SizeLimit:缓存总大小限制(.NET 6+)。double CompactionPercentage:内存压力下压缩比例(.NET 6+)。
注意: 旧版本.net 使用
System.Runtime.Caching新版本.net 使用Microsoft.Extensions.Caching.Memory
API 用法
创建与初始化 MemoryCache
1using System.Runtime.Caching; 2 3// 创建默认的 MemoryCache 实例 4MemoryCache cache = MemoryCache.Default; 5 6// 或者创建带名称的实例 7MemoryCache customCache = new MemoryCache("MyCache"); 8
添加缓存项
1CacheItemPolicy policy = new CacheItemPolicy 2{ 3 AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(10), // 设置绝对过期时间 4 SlidingExpiration = TimeSpan.FromMinutes(5), // 设置滑动过期时间 5 Priority = CacheItemPriority.Default, // 设置优先级 6 RemovedCallback = args => { Console.WriteLine("缓存项已移除"); } // 过期时执行回调 7}; 8 9cache.Add("key", "value", policy); // 向缓存中添加项 10
获取缓存项
1var value = cache.Get("key"); // 获取缓存项 2Console.WriteLine(value); // 输出:value 3
更新缓存项
1cache.Set("key", "newValue", DateTimeOffset.Now.AddMinutes(5)); // 更新缓存项 2
移除缓存项
1cache.Remove("key"); // 移除缓存项 2
使用 TryGetValue 方法检查缓存项
1object value; 2if (cache.TryGetValue("key", out value)) 3{ 4 Console.WriteLine(value); // 如果存在,打印缓存值 5} 6else 7{ 8 Console.WriteLine("缓存项不存在"); // 如果不存在,打印提示 9} 10
获取所有缓存项(遍历)
1foreach (var item in cache) 2{ 3 Console.WriteLine($"Key: {item.Key}, Value: {item.Value}"); 4} 5
自定义缓存实例
1// 创建带自定义配置的缓存 2var cacheConfig = new NameValueCollection 3{ 4 {"cacheMemoryLimitMegabytes", "100"}, // 100MB 内存限制 5 {"physicalMemoryLimitPercentage", "50"}, // 物理内存50% 6 {"pollingInterval", "00:05:00"} // 5分钟检查一次 7}; 8 9var customCache = new MemoryCache("MyCustomCache", cacheConfig); 10 11// 使用自定义缓存 12customCache.Set("userData", userProfile, new CacheItemPolicy()); 13
优先级策略
1var highPriorityPolicy = new CacheItemPolicy 2{ 3 Priority = CacheItemPriority.NotRemovable // 内存不足时不会被移除 4}; 5 6var lowPriorityPolicy = new CacheItemPolicy 7{ 8 Priority = CacheItemPriority.Default // 默认优先级 9}; 10
ASP.NET Core 中通过 DI 使用 IMemoryCache
1using Microsoft.AspNetCore.Mvc; 2using Microsoft.Extensions.Caching.Memory; 3using System; 4 5[ApiController] 6[Route("api/cache")] 7public class CacheController : ControllerBase 8{ 9 private readonly IMemoryCache _cache; 10 11 public CacheController(IMemoryCache cache) 12 { 13 _cache = cache; 14 } 15 16 [HttpGet("set")] 17 public IActionResult SetCache() 18 { 19 var key = "myKey"; 20 var value = $"Data at {DateTime.Now:HH:mm:ss}"; 21 var options = new MemoryCacheEntryOptions 22 { 23 AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5), 24 SlidingExpiration = TimeSpan.FromSeconds(30) 25 }; 26 27 _cache.Set(key, value, options); 28 return Ok($"Cached: {value}"); 29 } 30 31 [HttpGet("get")] 32 public IActionResult GetCache() 33 { 34 if (_cache.TryGetValue("myKey", out string value)) 35 return Ok($"Cached value: {value}"); 36 return NotFound("Cache miss"); 37 } 38} 39
注册服务:
1var builder = WebApplication.CreateBuilder(args); 2builder.Services.AddMemoryCache(); // 注册 MemoryCache 3builder.Services.AddControllers(); 4
缓存依赖(ChangeToken)
基于外部信号失效(如文件更改):
1using Microsoft.AspNetCore.Mvc; 2using Microsoft.Extensions.Caching.Memory; 3using Microsoft.Extensions.FileProviders; 4using System; 5using System.IO; 6using System.Threading.Tasks; 7 8[ApiController] 9[Route("api/config")] 10public class ConfigController : ControllerBase 11{ 12 private readonly IMemoryCache _cache; 13 private readonly PhysicalFileProvider _fileProvider; 14 15 public ConfigController(IMemoryCache cache) 16 { 17 _cache = cache; 18 _fileProvider = new PhysicalFileProvider(Directory.GetCurrentDirectory()); 19 } 20 21 [HttpGet] 22 public async Task<IActionResult> GetConfig() 23 { 24 var cacheKey = "configKey"; 25 var config = await _cache.GetOrCreateAsync(cacheKey, async entry => 26 { 27 var file = _fileProvider.GetFileInfo("config.txt"); 28 entry.AddExpirationToken(_fileProvider.Watch("config.txt")); 29 entry.SlidingExpiration = TimeSpan.FromMinutes(5); 30 // 读取文件 31 string content = await File.ReadAllTextAsync(file.PhysicalPath); 32 return content; 33 }); 34 35 return Ok(config); 36 } 37} 38
说明:
- 使用
PhysicalFileProvider.Watch创建IChangeToken。 - 文件更改时缓存失效,重新加载。
性能优化
避免过多缓存数据
设置合理的缓存大小和最大容量,避免 MemoryCache 占用过多内存。可以通过设置缓存的最大项数或缓存最大内存大小来控制。
1var policy = new CacheItemPolicy 2{ 3 AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(10) 4}; 5var cache = new MemoryCache("MyCache", new NameValueCollection 6{ 7 { "CacheMemoryLimitMegabytes", "100" }, // 限制最大内存为 100 MB 8 { "PhysicalMemoryLimitPercentage", "80" }, // 限制物理内存占用为 80% 9 { "PollingInterval", "00:01:00" } // 每 1 分钟检查一次缓存 10}); 11
合理使用过期策略
- 对于一些短时间有效的数据,建议使用滑动过期(
SlidingExpiration),它可以保证数据的最新性。 - 对于长期不变的数据,可以使用绝对过期(
AbsoluteExpiration),避免内存被长时间占用。
避免缓存穿透
- 在向缓存写入数据之前,确保数据已经在某些存储中存在,避免因缓存项缺失导致每次访问都去请求外部数据库。
- 如果缓存为空,可以缓存空值,以防止缓存穿透。
1public T GetOrCreate<T>(string key, Func<T> factory, TimeSpan expiration) 2{ 3 if (cache.Get(key) is T result) 4 return result; 5 6 // 特殊值标记缓存穿透 7 if ("__NULL__".Equals(cache.Get(key))) 8 return default; 9 10 try 11 { 12 result = factory(); 13 if (result == null) 14 { 15 // 缓存空值避免反复查询 16 cache.Set(key, "__NULL__", TimeSpan.FromMinutes(5)); 17 return default; 18 } 19 20 cache.Set(key, result, expiration); 21 return result; 22 } 23 catch 24 { 25 // 异常时设置短期空缓存 26 cache.Set(key, "__NULL__", TimeSpan.FromMinutes(1)); 27 throw; 28 } 29} 30
合理设置回调函数
- 使用
RemovedCallback来处理过期的缓存项,执行资源清理或日志记录等操作。
1CacheItemPolicy policy = new CacheItemPolicy 2{ 3 RemovedCallback = args => 4 { 5 Console.WriteLine($"缓存项 {args.CacheItem.Key} 被移除"); 6 } 7}; 8
定期清理
- 可以通过
PollingInterval来定期检查缓存并进行清理,避免缓存中存放过期数据。
缓存统计监控
1// 获取缓存统计信息 2var stats = ((MemoryCache)cache).GetCacheStatistics(); 3 4Console.WriteLine($"缓存命中率: {stats.CacheHitRatio:P}"); 5Console.WriteLine($"总缓存项: {stats.TotalCount}"); 6Console.WriteLine($"缓存大小: {stats.CacheSizeKB} KB"); 7Console.WriteLine($"内存限制: {stats.MemoryLimitMB} MB"); 8
优缺点
优点
- 高性能:进程内缓存,访问速度快。
- 轻量简单:无外部依赖,易于集成。
- 灵活过期:支持绝对、滑动和依赖失效。
- 异步支持:
GetOrCreateAsync适合异步加载。 DI集成:与ASP.NET Core无缝结合。- 内存管理:
.NET 6+支持大小限制和压缩。
缺点
- 进程内限制:数据不跨进程或实例共享,重启丢失。
- 内存占用:大数据量可能导致内存压力。
- 无分布式支持:不适合多实例部署(需用
Redis)。 - 手动管理:需显式设置键和过期策略。
- 有限功能:无高级功能(如标签、区域缓存)。
常见使用场景
适用场景
- 数据缓存
- 在
Web应用中缓存数据库查询结果、API请求结果等,减少数据库查询的压力,提高响应速度。 - 示例:缓存用户信息、商品列表等。
- 在
- 会话存储
- 使用缓存来存储用户会话信息,避免每次请求都查询数据库。
- 示例:存储用户的登录状态或临时数据。
- 频繁访问的数据
- 在频繁读取但不常更新的数据上使用缓存来提高性能。
- 示例:热点新闻、热销商品、排行榜等。
API限流- 用于控制
API调用频率,存储用户请求的时间戳,避免过多的请求对后台服务产生压力。 - 示例:
API请求次数的缓存,防止暴力请求。
- 用于控制
- 耗时操作结果缓存
- 对于计算成本较高的操作,可以将结果缓存起来,减少重复计算。
- 示例:图片处理后的结果、文件读取操作的缓存等。
不适用场景
- 分布式系统共享数据
- 大型数据集(大于内存限制)
- 需要持久化的数据
- 严格的实时数据一致性要求
与相关技术对比
| 特性 | MemoryCache | HttpRuntime.Cache | IDistributedCache | Redis |
|---|---|---|---|---|
| 存储位置 | 内存 | 内存 | 分布式存储 | 分布式 |
| 应用范围 | 通用 | 通用 | 分布式系统 | 分布式 |
| 性能 | 最高 | 高 | 中 | 中高 |
| 持久化 | ❌ | ❌ | ✅ | ✅ |
| 过期策略 | 丰富 | 丰富 | 基础 | 丰富 |
| 集群支持 | ❌ | ❌ | ✅ | ✅ |
| .NET Core | ✅ | ❌ | ✅ | ✅ |
总结
MemoryCache 是 .NET 中轻量高效的进程内缓存组件,适合高频读取、低更新频率的场景,如热点数据、配置或计算结果。它提供灵活的过期策略、回调和依赖失效,支持异步操作和 ASP.NET Core DI。相比 IDistributedCache(如 Redis),它性能更高但限于单实例。
资源和文档
MSDN文档: learn.microsoft.com/zh-cn/dotne…- 缓存模式指南: learn.microsoft.com/zh-cn/azure…
MemoryCache源码: github.com/dotnet/runt…