簡介
在許多應用程序中,緩存是提升性能的常見方法,尤其是在訪問頻繁且不經常變化的數據時。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
using System.Runtime.Caching;
// 創建默認的 MemoryCache 實例
MemoryCache cache = MemoryCache.Default;
// 或者創建帶名稱的實例
MemoryCache customCache = new MemoryCache("MyCache");
添加緩存項
CacheItemPolicy policy = new CacheItemPolicy
{
AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(10), // 設置絕對過期時間
SlidingExpiration = TimeSpan.FromMinutes(5), // 設置滑動過期時間
Priority = CacheItemPriority.Default, // 設置優先級
RemovedCallback = args => { Console.WriteLine("緩存項已移除"); } // 過期時執行回調
};
cache.Add("key", "value", policy); // 向緩存中添加項
獲取緩存項
var value = cache.Get("key"); // 獲取緩存項
Console.WriteLine(value); // 輸出:value
更新緩存項
cache.Set("key", "newValue", DateTimeOffset.Now.AddMinutes(5)); // 更新緩存項
移除緩存項
cache.Remove("key"); // 移除緩存項
使用 TryGetValue 方法檢查緩存項
object value;
if (cache.TryGetValue("key", out value))
{
Console.WriteLine(value); // 如果存在,打印緩存值
}
else
{
Console.WriteLine("緩存項不存在"); // 如果不存在,打印提示
}
獲取所有緩存項(遍歷)
foreach (var item in cache)
{
Console.WriteLine($"Key: {item.Key}, Value: {item.Value}");
}
自定義緩存實例
// 創建帶自定義配置的緩存
var cacheConfig = new NameValueCollection
{
{"cacheMemoryLimitMegabytes", "100"}, // 100MB 內存限制
{"physicalMemoryLimitPercentage", "50"}, // 物理內存50%
{"pollingInterval", "00:05:00"} // 5分鐘檢查一次
};
var customCache = new MemoryCache("MyCustomCache", cacheConfig);
// 使用自定義緩存
customCache.Set("userData", userProfile, new CacheItemPolicy());
優先級策略
var highPriorityPolicy = new CacheItemPolicy
{
Priority = CacheItemPriority.NotRemovable // 內存不足時不會被移除
};
var lowPriorityPolicy = new CacheItemPolicy
{
Priority = CacheItemPriority.Default // 默認優先級
};
ASP.NET Core 中通過 DI 使用 IMemoryCache
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using System;
[ApiController]
[Route("api/cache")]
public class CacheController : ControllerBase
{
private readonly IMemoryCache _cache;
public CacheController(IMemoryCache cache)
{
_cache = cache;
}
[HttpGet("set")]
public IActionResult SetCache()
{
var key = "myKey";
var value = $"Data at {DateTime.Now:HH:mm:ss}";
var options = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5),
SlidingExpiration = TimeSpan.FromSeconds(30)
};
_cache.Set(key, value, options);
return Ok($"Cached: {value}");
}
[HttpGet("get")]
public IActionResult GetCache()
{
if (_cache.TryGetValue("myKey", out string value))
return Ok($"Cached value: {value}");
return NotFound("Cache miss");
}
}
註冊服務:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCache(); // 註冊 MemoryCache
builder.Services.AddControllers();
緩存依賴(ChangeToken)
基於外部信號失效(如文件更改):
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.FileProviders;
using System;
using System.IO;
using System.Threading.Tasks;
[ApiController]
[Route("api/config")]
public class ConfigController : ControllerBase
{
private readonly IMemoryCache _cache;
private readonly PhysicalFileProvider _fileProvider;
public ConfigController(IMemoryCache cache)
{
_cache = cache;
_fileProvider = new PhysicalFileProvider(Directory.GetCurrentDirectory());
}
[HttpGet]
public async Task<IActionResult> GetConfig()
{
var cacheKey = "configKey";
var config = await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
var file = _fileProvider.GetFileInfo("config.txt");
entry.AddExpirationToken(_fileProvider.Watch("config.txt"));
entry.SlidingExpiration = TimeSpan.FromMinutes(5);
// 讀取文件
string content = await File.ReadAllTextAsync(file.PhysicalPath);
return content;
});
return Ok(config);
}
}
説明:
- 使用
PhysicalFileProvider.Watch創建IChangeToken。 - 文件更改時緩存失效,重新加載。
性能優化
避免過多緩存數據
設置合理的緩存大小和最大容量,避免 MemoryCache 佔用過多內存。可以通過設置緩存的最大項數或緩存最大內存大小來控制。
var policy = new CacheItemPolicy
{
AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(10)
};
var cache = new MemoryCache("MyCache", new NameValueCollection
{
{ "CacheMemoryLimitMegabytes", "100" }, // 限制最大內存為 100 MB
{ "PhysicalMemoryLimitPercentage", "80" }, // 限制物理內存佔用為 80%
{ "PollingInterval", "00:01:00" } // 每 1 分鐘檢查一次緩存
});
合理使用過期策略
-
對於一些短時間有效的數據,建議使用滑動過期(
SlidingExpiration),它可以保證數據的最新性。- 對於長期不變的數據,可以使用絕對過期(
AbsoluteExpiration),避免內存被長時間佔用。
- 對於長期不變的數據,可以使用絕對過期(
避免緩存穿透
- 在向緩存寫入數據之前,確保數據已經在某些存儲中存在,避免因緩存項缺失導致每次訪問都去請求外部數據庫。
- 如果緩存為空,可以緩存空值,以防止緩存穿透。
public T GetOrCreate<T>(string key, Func<T> factory, TimeSpan expiration)
{
if (cache.Get(key) is T result)
return result;
// 特殊值標記緩存穿透
if ("__NULL__".Equals(cache.Get(key)))
return default;
try
{
result = factory();
if (result == null)
{
// 緩存空值避免反覆查詢
cache.Set(key, "__NULL__", TimeSpan.FromMinutes(5));
return default;
}
cache.Set(key, result, expiration);
return result;
}
catch
{
// 異常時設置短期空緩存
cache.Set(key, "__NULL__", TimeSpan.FromMinutes(1));
throw;
}
}
合理設置回調函數
- 使用
RemovedCallback來處理過期的緩存項,執行資源清理或日誌記錄等操作。
CacheItemPolicy policy = new CacheItemPolicy
{
RemovedCallback = args =>
{
Console.WriteLine($"緩存項 {args.CacheItem.Key} 被移除");
}
};
定期清理
- 可以通過
PollingInterval來定期檢查緩存並進行清理,避免緩存中存放過期數據。
緩存統計監控
// 獲取緩存統計信息
var stats = ((MemoryCache)cache).GetCacheStatistics();
Console.WriteLine($"緩存命中率: {stats.CacheHitRatio:P}");
Console.WriteLine($"總緩存項: {stats.TotalCount}");
Console.WriteLine($"緩存大小: {stats.CacheSizeKB} KB");
Console.WriteLine($"內存限制: {stats.MemoryLimitMB} MB");
優缺點
優點
- 高性能:進程內緩存,訪問速度快。
- 輕量簡單:無外部依賴,易於集成。
- 靈活過期:支持絕對、滑動和依賴失效。
- 異步支持:
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文檔: https://learn.microsoft.com/zh-cn/dotnet/api/system.runtime.c...- 緩存模式指南: https://learn.microsoft.com/zh-cn/azure/architecture/patterns...
MemoryCache源碼: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCache.cs