ASP.NET Core 中的記憶體中快取
由 Rick Anderson、John Luo 和 Steve Smith 撰寫
快取藉由減少產生內容所需的工作,可以大幅改善應用程式的效能和可擴縮性。 快取最適合不常變更且產生成本高昂的資料。 快取可讓資料複本傳回的速度比來源快得多。 撰寫和測試應用程式時,應使其永遠不會相依於被快取的資料。
ASP.NET Core 支援數種不同的快取。 最簡單的快取是以 IMemoryCache 為基礎。 IMemoryCache
代表儲存在網頁伺服器記憶體中的快取。 在伺服器陣列 (多部伺服器) 上執行的應用程式應該在使用記憶體內部快取時確保會話黏性。 黏性會話可確保來自用戶端的要求全都移至相同的伺服器。 例如,Azure Web 應用程式會使用應用程式要求路由 (ARR) 將所有要求路由傳送至相同的伺服器。
Web 服務器陣列中的非黏性會話需要分散式快取,以避免快取一致性問題。 對於某些應用程式,分散式快取可支援的向外延展程度比記憶體內部快取更高。 使用分散式快取會將快取記憶體卸載至外部進程。
記憶體內部快取可以儲存任何物件。 分散式快取介面限制為 byte[]
。 記憶體內部和分散式快取會將快取項目儲存為索引鍵/值組。
System.Runtime.Caching/MemoryCache
System.Runtime.Caching/MemoryCache (NuGet 套件) 可以搭配使用:
- .NET Standard 2.0 或更新版本。
- 以 .NET Standard 2.0 或更新版本為目標的任何 .NET 實作。 例如,ASP.NET Core 3.1 或更新版本。
- .NET Framework 4.5 或更新版本。
建議使用 Microsoft.Extensions.Caching.Memory/IMemoryCache
(本文所述),而非 System.Runtime.Caching
/MemoryCache
,因為前者與 ASP.NET Core 的整合程度更理想。 例如, IMemoryCache
以原生方式搭配 ASP.NET Core 相依性插入運作。
將程式碼從 ASP.NET 4.x 移植到 ASP.NET Core 時,使用 System.Runtime.Caching
/MemoryCache
作為相容性橋接器。
快取指導方針
- 程式碼應該一律有後援選項來擷取資料, 而不是相依於可用的快取值。
- 快取會使用稀缺的資源、記憶體。 限制快取增長:
- 請勿將外部輸入插入快取中。 例如,不建議使用任意使用者提供的輸入做為快取索引鍵,因為輸入可能會耗用無法預測的記憶體數量。
- 使用到期日來限制快取增長。
- 使用 SetSize、Size 和 SizeLimit 來限制快取大小。 ASP.NET Core 執行時間不會根據記憶體壓力來限制快取大小。 是否限制快取的大小由開發人員決定。
使用 IMemoryCache
警告
使用來自相依性插入的共用記憶體快取,並呼叫 SetSize
、Size
或 SizeLimit
來限制快取大小,可能會導致應用程式失效。 對快取設定了大小限制時,所有項目在新增時都必須指定大小。 這可能會導致問題,因為開發人員可能無法完全控制使用共用快取的內容。
使用 SetSize
、Size
或 SizeLimit
來限制快取時,請建立快取單一資料庫以執行快取。 如需詳細資訊和範例,請參閱使用 SetSize、Size 和 SizeLimit 來限制快取大小。
共用快取是由其他架構或程式庫共用的快取。
記憶體內部快取是使用相依性插入,並從應用程式參考的服務。 在建構函式中要求 IMemoryCache
執行個體:
public class IndexModel : PageModel
{
private readonly IMemoryCache _memoryCache;
public IndexModel(IMemoryCache memoryCache) =>
_memoryCache = memoryCache;
// ...
下列程式碼會使用 TryGetValue 來檢查時間是否在快取中。 如果時間未被快取,則會建立新的項目,並使用 Set 新增至快取:
public void OnGet()
{
CurrentDateTime = DateTime.Now;
if (!_memoryCache.TryGetValue(CacheKeys.Entry, out DateTime cacheValue))
{
cacheValue = CurrentDateTime;
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
_memoryCache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions);
}
CacheCurrentDateTime = cacheValue;
}
在上述程式碼中,快取項目會設定為滑動的三秒到期。 如果快取項目存取不超過三秒,則會從快取收回。 每次存取快取項目時,都會將其在快取中再保留 3 秒。 CacheKeys
類別是下載範例的一部分。
目前的時間和快取時間隨即顯示:
<ul>
<li>Current Time: @Model.CurrentDateTime</li>
<li>Cached Time: @Model.CacheCurrentDateTime</li>
</ul>
下列程式碼會使用 Set
擴充方法,在不使用 MemoryCacheEntryOptions
的情況下將資料快取一段相對時間:
_memoryCache.Set(CacheKeys.Entry, DateTime.Now, TimeSpan.FromDays(1));
在上述程式碼中,快取項目會設定為一天的相對到期時間。 快取項目會在一天以後從快取收回,即使它在這段到期期間內被存取也一樣。
下列程式碼會使用 GetOrCreate 和 GetOrCreateAsync 來快取資料。
public void OnGetCacheGetOrCreate()
{
var cachedValue = _memoryCache.GetOrCreate(
CacheKeys.Entry,
cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
return DateTime.Now;
});
// ...
}
public async Task OnGetCacheGetOrCreateAsync()
{
var cachedValue = await _memoryCache.GetOrCreateAsync(
CacheKeys.Entry,
cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
return Task.FromResult(DateTime.Now);
});
// ...
}
下列程式碼會呼叫 Get 來擷取快取的時間:
var cacheEntry = _memoryCache.Get<DateTime?>(CacheKeys.Entry);
下列程式碼會取得或建立具有絕對到期時間的快取項目:
var cachedValue = _memoryCache.GetOrCreate(
CacheKeys.Entry,
cacheEntry =>
{
cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
return DateTime.Now;
});
僅具有滑動到期的快取項目集會有永不過期的風險。 如果在滑動到期時間區間內重複存取被快取的項目,則該項目永遠不會過期。 請將滑動到期與絕對到期結合,以確保項目到期。 絕對到期日會設定項目可供快取的時間上限,同時如果該項目在滑動到期間隔內未被要求,仍允許其提早到期。 如果經過了滑動到期間隔或絕對到期時間,則會從快取收回項目。
下列程式碼會取得或建立具有滑動和絕對到期時間的快取項目:
var cachedValue = _memoryCache.GetOrCreate(
CacheKeys.CallbackEntry,
cacheEntry =>
{
cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
return DateTime.Now;
});
上述程式碼保證資料被快取的時間不會超過絕對時間。
GetOrCreate、GetOrCreateAsync 和 Get 是 CacheExtensions 類別中的擴充方法。 這些方法會擴充 IMemoryCache 的功能。
MemoryCacheEntryOptions
下列範例將:
- 將快取優先順序設定為 CacheItemPriority.NeverRemove。
- 設定從快取收回項目之後呼叫的 PostEvictionDelegate。 回呼是在與從快取中移除項目的程式碼不同的執行緒上執行。
public void OnGetCacheRegisterPostEvictionCallback()
{
var memoryCacheEntryOptions = new MemoryCacheEntryOptions()
.SetPriority(CacheItemPriority.NeverRemove)
.RegisterPostEvictionCallback(PostEvictionCallback, _memoryCache);
_memoryCache.Set(CacheKeys.CallbackEntry, DateTime.Now, memoryCacheEntryOptions);
}
private static void PostEvictionCallback(
object cacheKey, object cacheValue, EvictionReason evictionReason, object state)
{
var memoryCache = (IMemoryCache)state;
memoryCache.Set(
CacheKeys.CallbackMessage,
$"Entry {cacheKey} was evicted: {evictionReason}.");
}
使用 SetSize、Size 和 SizeLimit 來限制快取大小
MemoryCache
執行個體可以選擇性地指定並強制執行大小限制。 快取大小限制沒有定義的測量單位,因為快取沒有測量項目大小的機制。 如果設定了快取大小限制,所有項目都必須指定大小。 ASP.NET Core 執行時間不會根據記憶體壓力限制快取大小。 是否限制快取的大小由開發人員決定。 指定的大小是以開發人員選擇的單位來測量。
例如:
- 如果 Web 應用程式主要是快取字串,則每個快取項目大小都可以是字串長度。
- 應用程式可以將所有項目的大小指定為 1,而大小限制則是項目個數。
如果未設定 SizeLimit,快取就會漫無止境地增長。 ASP.NET Core 執行時間不會在系統記憶體不足時修剪快取。 應用程式必須架構為:
下列程式碼會藉由相依性插入建立可存取的無單位固定大小 MemoryCache:
public class MyMemoryCache
{
public MemoryCache Cache { get; } = new MemoryCache(
new MemoryCacheOptions
{
SizeLimit = 1024
});
}
SizeLimit
沒有單位。 如果已設定快取大小限制,快取項目就必須以它們認為最適當的單位來指定大小。 快取執行個體的所有使用者都應該使用相同的單位系統。 如果快取項目的大小總和超過 SizeLimit
指定的值,則不會快取該項目。 如果未設定任何快取大小限制,則會忽略針對項目設定的快取大小。
下列程式碼會向相依性插 容器註冊 MyMemoryCache
:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddSingleton<MyMemoryCache>();
MyMemoryCache
會建立為了解此大小有限快取之元件的獨立記憶體快取,並知道如何適當地設定快取項目大小。
快取項目的大小可以使用 SetSize 擴充方法或 Size 屬性來設定:
if (!_myMemoryCache.Cache.TryGetValue(CacheKeys.Entry, out DateTime cacheValue))
{
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSize(1);
// cacheEntryOptions.Size = 1;
_myMemoryCache.Cache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions);
}
在上述程式碼中,兩行醒目提示的行會達到設定快取項目大小相同的結果。 將呼叫鏈結至 new MemoryCacheOptions()
時,會提供 SetSize
以方便使用。
MemoryCache.Compact
MemoryCache.Compact
嘗試以下列順序移除指定百分比的快取:
- 所有過期的項目。
- 依優先順序排序的項目。 優先順序最低的項目會先被移除。
- 最近使用最少的物件。
- 絕對到期時間最早的項目。
- 滑動到期時間最早的項目。
具有優先權 NeverRemove 的釘選項目永遠不會被移除。 下列程式碼會移除快取項目,並呼叫 Compact
以移除 25% 的快取項目:
_myMemoryCache.Cache.Remove(CacheKeys.Entry);
_myMemoryCache.Cache.Compact(.25);
如需詳細資訊,請參閱 GitHub 上的 Compact 來源。
快取相依性
下列範例示範如果相依項目到期,如何讓快取項目到期。 CancellationChangeToken 會新增至快取項目。 在 CancellationTokenSource
上呼叫 Cancel
時,會收回這兩個快取項目:
public void OnGetCacheCreateDependent()
{
var cancellationTokenSource = new CancellationTokenSource();
_memoryCache.Set(
CacheKeys.DependentCancellationTokenSource,
cancellationTokenSource);
using var parentCacheEntry = _memoryCache.CreateEntry(CacheKeys.Parent);
parentCacheEntry.Value = DateTime.Now;
_memoryCache.Set(
CacheKeys.Child,
DateTime.Now,
new CancellationChangeToken(cancellationTokenSource.Token));
}
public void OnGetCacheRemoveDependent()
{
var cancellationTokenSource = _memoryCache.Get<CancellationTokenSource>(
CacheKeys.DependentCancellationTokenSource);
cancellationTokenSource.Cancel();
}
使用 CancellationTokenSource 允許將多個快取項目收回為群組。 使用上述程式碼中的 using
模式,using
範圍內建立的快取項目會繼承觸發程式和到期設定。
其他注意事項
到期不會在背景中發生。 沒有會主動掃描快取中是否有項目到期的計時器。 快取 (
Get
、Set
、Remove
) 上的任何活動都可以觸發對過期項目的背景掃描。CancellationTokenSource
(CancelAfter) 上的計時器也會移除項目,並觸發針對過期項目的掃瞄。 下列範例會針對已註冊的權杖使用 CancellationTokenSource(TimeSpan)。 當此權杖觸發時,會立即移除項目,並觸發收回回呼:if (!_memoryCache.TryGetValue(CacheKeys.Entry, out DateTime cacheValue)) { cacheValue = DateTime.Now; var cancellationTokenSource = new CancellationTokenSource( TimeSpan.FromSeconds(10)); var cacheEntryOptions = new MemoryCacheEntryOptions() .AddExpirationToken( new CancellationChangeToken(cancellationTokenSource.Token)) .RegisterPostEvictionCallback((key, value, reason, state) => { ((CancellationTokenSource)state).Dispose(); }, cancellationTokenSource); _memoryCache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions); }
使用回呼重新填入快取項目時:
- 多個要求可以找到空白的快取索引鍵值,因為回呼尚未完成。
- 這可能會導致數個執行緒重新填入快取項目。
當一個快取項目用來建立另一個項目時,子系會複製父項目的到期權杖和以時間為基礎的到期設定。 手動移除或更新父系項目不會使子系過期。
使用 PostEvictionCallbacks 來設定快取項目從快取收回之後會觸發的回呼。
對於大部分的應用程式,
IMemoryCache
會啟用。 例如,在Program.cs
中呼叫AddMvc
、AddControllersWithViews
、AddRazorPages
、AddMvcCore().AddRazorViewEngine
和許多其他Add{Service}
方法會啟用IMemoryCache
。 對於未呼叫上述其中一個Add{Service}
方法的應用程式,可能需要在Program.cs
中呼叫 AddMemoryCache。
背景快取更新
使用背景服務 (例如 IHostedService) 來更新快取。 背景服務可以重新計算項目,然後只在備妥時將它們指派給快取。
其他資源
檢視或下載範例程式碼 \(英文\) (如何下載)
快取基本概念
快取藉由減少產生內容所需的工作,可以大幅改善應用程式的效能和可擴縮性。 快取最適合不常變更且產生成本高昂的資料。 快取可讓資料複本傳回的速度比來源快得多。 撰寫和測試應用程式時,應使其永遠不會相依於被快取的資料。
ASP.NET Core 支援數種不同的快取。 最簡單的快取是以 IMemoryCache 為基礎。 IMemoryCache
代表儲存在網頁伺服器記憶體中的快取。 在伺服器陣列 (多部伺服器) 上執行的應用程式應該在使用記憶體內部快取時確保會話黏性。 黏性會話可確保來自用戶端的後續要求全都移至相同的伺服器。 例如,Azure Web 應用程式會使用應用程式要求路由 (ARR) 將所有後續要求路由傳送至相同的伺服器。
Web 服務器陣列中的非黏性會話需要分散式快取,以避免快取一致性問題。 對於某些應用程式,分散式快取可支援的向外延展程度比記憶體內部快取更高。 使用分散式快取會將快取記憶體卸載至外部進程。
記憶體內部快取可以儲存任何物件。 分散式快取介面限制為 byte[]
。 記憶體內部和分散式快取會將快取項目儲存為索引鍵/值組。
System.Runtime.Caching/MemoryCache
System.Runtime.Caching/MemoryCache (NuGet 套件) 可以搭配使用:
- .NET Standard 2.0 或更新版本。
- 以 .NET Standard 2.0 或更新版本為目標的任何 .NET 實作。 例如,ASP.NET Core 3.1 或更新版本。
- .NET Framework 4.5 或更新版本。
建議使用 Microsoft.Extensions.Caching.Memory/IMemoryCache
(本文所述),而非 System.Runtime.Caching
/MemoryCache
,因為前者與 ASP.NET Core 的整合程度更理想。 例如, IMemoryCache
以原生方式搭配 ASP.NET Core 相依性插入運作。
將程式碼從 ASP.NET 4.x 移植到 ASP.NET Core 時,使用 System.Runtime.Caching
/MemoryCache
作為相容性橋接器。
快取指導方針
- 程式碼應該一律有後援選項來擷取資料, 而不是相依於可用的快取值。
- 快取會使用稀缺的資源、記憶體。 限制快取增長:
- 請勿使用外部輸入作為快取索引鍵。
- 使用到期日來限制快取增長。
- 使用 SetSize、Size 和 SizeLimit 來限制快取大小。 ASP.NET Core 執行時間不會根據記憶體壓力來限制快取大小。 是否限制快取的大小由開發人員決定。
使用 IMemoryCache
警告
使用來自相依性插入的共用記憶體快取,並呼叫 SetSize
、Size
或 SizeLimit
來限制快取大小,可能會導致應用程式失效。 對快取設定了大小限制時,所有項目在新增時都必須指定大小。 這可能會導致問題,因為開發人員可能無法完全控制使用共用快取的內容。
使用 SetSize
、Size
或 SizeLimit
來限制快取時,請建立快取單一資料庫以執行快取。 如需詳細資訊和範例,請參閱使用 SetSize、Size 和 SizeLimit 來限制快取大小。
共用快取是由其他架構或程式庫共用的快取。
記憶體內部快取是使用相依性插入,並從應用程式參考的服務。 在建構函式中要求 IMemoryCache
執行個體:
public class HomeController : Controller
{
private IMemoryCache _cache;
public HomeController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
下列程式碼會使用 TryGetValue 來檢查時間是否在快取中。 如果時間未被快取,則會建立新的項目,並使用 Set 新增至快取。 CacheKeys
類別是下載範例的一部分。
public static class CacheKeys
{
public static string Entry => "_Entry";
public static string CallbackEntry => "_Callback";
public static string CallbackMessage => "_CallbackMessage";
public static string Parent => "_Parent";
public static string Child => "_Child";
public static string DependentMessage => "_DependentMessage";
public static string DependentCTS => "_DependentCTS";
public static string Ticks => "_Ticks";
public static string CancelMsg => "_CancelMsg";
public static string CancelTokenSource => "_CancelTokenSource";
}
public IActionResult CacheTryGetValueSet()
{
DateTime cacheEntry;
// Look for cache key.
if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now;
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
// Save data in cache.
_cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
}
return View("Cache", cacheEntry);
}
目前的時間和快取時間隨即顯示:
@model DateTime?
<div>
<h2>Actions</h2>
<ul>
<li><a asp-controller="Home" asp-action="CacheTryGetValueSet">TryGetValue and Set</a></li>
<li><a asp-controller="Home" asp-action="CacheGet">Get</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreate">GetOrCreate</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreateAsynchronous">CacheGetOrCreateAsynchronous</a></li>
<li><a asp-controller="Home" asp-action="CacheRemove">Remove</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreateAbs">CacheGetOrCreateAbs</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreateAbsSliding">CacheGetOrCreateAbsSliding</a></li>
</ul>
</div>
<h3>Current Time: @DateTime.Now.TimeOfDay.ToString()</h3>
<h3>Cached Time: @(Model == null ? "No cached entry found" : Model.Value.TimeOfDay.ToString())</h3>
下列程式碼會使用 Set
擴充方法將資料快取一段相對時間,而不需要建立 MemoryCacheEntryOptions
物件:
public IActionResult SetCacheRelativeExpiration()
{
DateTime cacheEntry;
// Look for cache key.
if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now;
// Save data in cache and set the relative expiration time to one day
_cache.Set(CacheKeys.Entry, cacheEntry, TimeSpan.FromDays(1));
}
return View("Cache", cacheEntry);
}
當逾時期間內有要求時,快取 DateTime
值會保留在快取中。
下列程式碼會使用 GetOrCreate 和 GetOrCreateAsync 來快取資料。
public IActionResult CacheGetOrCreate()
{
var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return DateTime.Now;
});
return View("Cache", cacheEntry);
}
public async Task<IActionResult> CacheGetOrCreateAsynchronous()
{
var cacheEntry = await
_cache.GetOrCreateAsync(CacheKeys.Entry, entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return Task.FromResult(DateTime.Now);
});
return View("Cache", cacheEntry);
}
下列程式碼會呼叫 Get 來擷取快取的時間:
public IActionResult CacheGet()
{
var cacheEntry = _cache.Get<DateTime?>(CacheKeys.Entry);
return View("Cache", cacheEntry);
}
下列程式碼會取得或建立具有絕對到期時間的快取項目:
public IActionResult CacheGetOrCreateAbs()
{
var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
return DateTime.Now;
});
return View("Cache", cacheEntry);
}
僅具有滑動到期的快取項目集會有永不過期的風險。 如果在滑動到期時間區間內重複存取被快取的項目,則該項目永遠不會過期。 請將滑動到期與絕對到期結合,以確保項目到期。 絕對到期日會設定項目可供快取的時間上限,同時如果該項目在滑動到期間隔內未被要求,仍允許其提早到期。 如果經過了滑動到期間隔或絕對到期時間,則會從快取收回項目。
下列程式碼會取得或建立具有滑動和絕對到期時間的快取項目:
public IActionResult CacheGetOrCreateAbsSliding()
{
var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
{
entry.SetSlidingExpiration(TimeSpan.FromSeconds(3));
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
return DateTime.Now;
});
return View("Cache", cacheEntry);
}
上述程式碼保證資料被快取的時間不會超過絕對時間。
GetOrCreate、GetOrCreateAsync 和 Get 是 CacheExtensions 類別中的擴充方法。 這些方法會擴充 IMemoryCache 的功能。
MemoryCacheEntryOptions
下列範例:
- 設定滑動到期時間。 存取此快取項目的要求將會重設滑動到期時鐘。
- 將快取優先順序設定為 CacheItemPriority.NeverRemove。
- 設定在從快取收回項目之後呼叫的 PostEvictionDelegate。 回呼是在與從快取中移除項目的程式碼不同的執行緒上執行。
public IActionResult CreateCallbackEntry()
{
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Pin to cache.
.SetPriority(CacheItemPriority.NeverRemove)
// Add eviction callback
.RegisterPostEvictionCallback(callback: EvictionCallback, state: this);
_cache.Set(CacheKeys.CallbackEntry, DateTime.Now, cacheEntryOptions);
return RedirectToAction("GetCallbackEntry");
}
public IActionResult GetCallbackEntry()
{
return View("Callback", new CallbackViewModel
{
CachedTime = _cache.Get<DateTime?>(CacheKeys.CallbackEntry),
Message = _cache.Get<string>(CacheKeys.CallbackMessage)
});
}
public IActionResult RemoveCallbackEntry()
{
_cache.Remove(CacheKeys.CallbackEntry);
return RedirectToAction("GetCallbackEntry");
}
private static void EvictionCallback(object key, object value,
EvictionReason reason, object state)
{
var message = $"Entry was evicted. Reason: {reason}.";
((HomeController)state)._cache.Set(CacheKeys.CallbackMessage, message);
}
使用 SetSize、Size 和 SizeLimit 來限制快取大小
MemoryCache
執行個體可以選擇性地指定並強制執行大小限制。 快取大小限制沒有定義的測量單位,因為快取沒有測量項目大小的機制。 如果設定了快取大小限制,所有項目都必須指定大小。 ASP.NET Core 執行時間不會根據記憶體壓力來限制快取大小。 是否限制快取的大小由開發人員決定。 指定的大小是以開發人員選擇的單位來測量。
例如:
- 如果 Web 應用程式主要是快取字串,則每個快取項目大小都可以是字串長度。
- 應用程式可以將所有項目的大小指定為 1,而大小限制則是項目個數。
如果未設定 SizeLimit,快取就會漫無止境地增長。 ASP.NET Core 執行時間不會在系統記憶體不足時修剪快取。 應用程式必須架構為:
下列程式碼會藉由相依性插入建立可存取的無單位固定大小 MemoryCache:
// using Microsoft.Extensions.Caching.Memory;
public class MyMemoryCache
{
public MemoryCache Cache { get; private set; }
public MyMemoryCache()
{
Cache = new MemoryCache(new MemoryCacheOptions
{
SizeLimit = 1024
});
}
}
SizeLimit
沒有單位。 如果已設定快取大小限制,就必須以快取項目認為最適當的單位來指定大小。 快取執行個體的所有使用者都應該使用相同的單位系統。 如果快取項目的大小總和超過 SizeLimit
指定的值,則不會快取該項目。 如果未設定任何快取大小限制,則會忽略針對項目設定的快取大小。
下列程式碼會向相依性插入容器註冊 MyMemoryCache
。
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddSingleton<MyMemoryCache>();
}
MyMemoryCache
會建立為了解此大小有限快取之元件的獨立記憶體快取,並知道如何適當地設定快取項目大小。
下列程式碼會使用 MyMemoryCache
:
public class SetSize : PageModel
{
private MemoryCache _cache;
public static readonly string MyKey = "_MyKey";
public SetSize(MyMemoryCache memoryCache)
{
_cache = memoryCache.Cache;
}
[TempData]
public string DateTime_Now { get; set; }
public IActionResult OnGet()
{
if (!_cache.TryGetValue(MyKey, out string cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now.TimeOfDay.ToString();
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Set cache entry size by extension method.
.SetSize(1)
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
// Set cache entry size via property.
// cacheEntryOptions.Size = 1;
// Save data in cache.
_cache.Set(MyKey, cacheEntry, cacheEntryOptions);
}
DateTime_Now = cacheEntry;
return RedirectToPage("./Index");
}
}
快取項目的大小可以透過 Size 或 SetSize 擴充方法來設定:
public IActionResult OnGet()
{
if (!_cache.TryGetValue(MyKey, out string cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now.TimeOfDay.ToString();
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Set cache entry size by extension method.
.SetSize(1)
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
// Set cache entry size via property.
// cacheEntryOptions.Size = 1;
// Save data in cache.
_cache.Set(MyKey, cacheEntry, cacheEntryOptions);
}
DateTime_Now = cacheEntry;
return RedirectToPage("./Index");
}
MemoryCache.Compact
MemoryCache.Compact
嘗試以下列順序移除指定百分比的快取:
- 所有過期的項目。
- 依優先順序排序的項目。 優先順序最低的項目會先被移除。
- 最近使用最少的物件。
- 絕對到期時間最早的項目。
- 滑動到期時間最早的項目。
具有優先順序 NeverRemove 的釘選項目永遠不會移除。 下列程式碼會移除快取項目並呼叫 Compact
:
_cache.Remove(MyKey);
// Remove 33% of cached items.
_cache.Compact(.33);
cache_size = _cache.Count;
如需詳細資訊,請參閱 GitHub 上的 Compact 來源。
快取相依性
下列範例示範如果相依項目到期,如何讓快取項目到期。 CancellationChangeToken 會新增至快取項目。 在 CancellationTokenSource
上呼叫 Cancel
時,會收回這兩個快取項目。
public IActionResult CreateDependentEntries()
{
var cts = new CancellationTokenSource();
_cache.Set(CacheKeys.DependentCTS, cts);
using (var entry = _cache.CreateEntry(CacheKeys.Parent))
{
// expire this entry if the dependant entry expires.
entry.Value = DateTime.Now;
entry.RegisterPostEvictionCallback(DependentEvictionCallback, this);
_cache.Set(CacheKeys.Child,
DateTime.Now,
new CancellationChangeToken(cts.Token));
}
return RedirectToAction("GetDependentEntries");
}
public IActionResult GetDependentEntries()
{
return View("Dependent", new DependentViewModel
{
ParentCachedTime = _cache.Get<DateTime?>(CacheKeys.Parent),
ChildCachedTime = _cache.Get<DateTime?>(CacheKeys.Child),
Message = _cache.Get<string>(CacheKeys.DependentMessage)
});
}
public IActionResult RemoveChildEntry()
{
_cache.Get<CancellationTokenSource>(CacheKeys.DependentCTS).Cancel();
return RedirectToAction("GetDependentEntries");
}
private static void DependentEvictionCallback(object key, object value,
EvictionReason reason, object state)
{
var message = $"Parent entry was evicted. Reason: {reason}.";
((HomeController)state)._cache.Set(CacheKeys.DependentMessage, message);
}
使用 CancellationTokenSource 允許將多個快取項目收回為群組。 使用上述程式碼中的 using
模式,在 using
區塊內建立的快取項目將會繼承觸發程式和到期設定。
其他注意事項
到期不會在背景中發生。 沒有會主動掃描快取中是否有到期項目的計時器。 快取 (
Get
、Set
、Remove
) 上的任何活動都可以觸發對過期項目的背景掃描。CancellationTokenSource
(CancelAfter) 上的計時器也會移除項目,並觸發針對過期項目的掃瞄。 下列範例會針對已註冊的權杖使用 CancellationTokenSource(TimeSpan)。 當此權杖觸發時,它會立即移除項目,並觸發收回回呼:public IActionResult CacheAutoExpiringTryGetValueSet() { DateTime cacheEntry; if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry)) { cacheEntry = DateTime.Now; var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cacheEntryOptions = new MemoryCacheEntryOptions() .AddExpirationToken(new CancellationChangeToken(cts.Token)); _cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions); } return View("Cache", cacheEntry); }
使用回呼重新填入快取項目時:
- 多個要求可以找到空白的快取索引鍵值,因為回呼尚未完成。
- 這可能會導致數個執行緒重新填入快取項目。
當一個快取項目用來建立另一個項目時,子系會複製父項目的到期權杖和以時間為基礎的到期設定。 手動移除或更新父系項目不會使子系過期。
使用 PostEvictionCallbacks 來設定快取項目從快取收回之後會觸發的回呼。 在範例程式碼中,會呼叫 CancellationTokenSource.Dispose() 以釋放
CancellationTokenSource
所使用的非受控資源。 不過,CancellationTokenSource
不會立即被處理,因為它仍在被快取項目使用。CancellationToken
會傳遞至MemoryCacheEntryOptions
,以建立在特定時間之後到期的快取項目。 因此,在快取項目被移除或過期之前,不應該呼叫Dispose
。 範例程式碼會呼叫 RegisterPostEvictionCallback 方法來註冊一個回呼,該回呼會在快取項目被收回時叫用,並在該回呼中處置CancellationTokenSource
。對於大部分的應用程式,
IMemoryCache
會啟用。 例如,在ConfigureServices
中呼叫AddMvc
、AddControllersWithViews
、AddRazorPages
、AddMvcCore().AddRazorViewEngine
和許多其他Add{Service}
方法會啟用IMemoryCache
。 對於未呼叫上述其中一個Add{Service}
方法的應用程式,可能需要在ConfigureServices
中呼叫 AddMemoryCache。
背景快取更新
使用背景服務 (例如 IHostedService) 來更新快取。 背景服務可以重新計算項目,然後只在備妥時將它們指派給快取。