ASP.NET Core 中的記憶體快取Cache in-memory in ASP.NET Core

作者: Rick AndersonJohn 羅文Steve SmithBy Rick Anderson, John Luo, and Steve Smith

檢視或下載範例程式碼 (英文) (如何下載)View or download sample code (how to download)

快取基本概念Caching basics

快取可以藉由減少產生內容所需的工作, 大幅改善應用程式的效能和擴充性。Caching can significantly improve the performance and scalability of an app by reducing the work required to generate content. 快取最適合不常變更的資料。Caching works best with data that changes infrequently. 快取會製作一份資料複本, 其傳回的速度會比原始來源更快。Caching makes a copy of data that can be returned much faster than from the original source. 應用程式應撰寫並測試為永遠不會依賴快取的資料。Apps should be written and tested to never depend on cached data.

ASP.NET Core 支援數種不同的快取。ASP.NET Core supports several different caches. 最簡單的快取是以IMemoryCache為基礎, 代表儲存在 web 伺服器記憶體中的快取。The simplest cache is based on the IMemoryCache, which represents a cache stored in the memory of the web server. 在多部伺服器的伺服器陣列上執行的應用程式, 應該在使用記憶體內部快取時, 確保會話是固定的。Apps that run on a server farm of multiple servers should ensure that sessions are sticky when using the in-memory cache. [粘滯會話] 可確保來自用戶端的後續要求都會移至相同的伺服器。Sticky sessions ensure that subsequent requests from a client all go to the same server. 例如, Azure Web apps 會使用應用程式要求路由(ARR), 將所有後續要求路由傳送至相同的伺服器。For example, Azure Web apps use Application Request Routing (ARR) to route all subsequent requests to the same server.

Web 伺服陣列中的非粘滯話需要分散式快取, 以避免快取一致性問題。Non-sticky sessions in a web farm require a distributed cache to avoid cache consistency problems. 針對某些應用程式, 分散式快取可支援比記憶體內部快取更高的相應放大。For some apps, a distributed cache can support higher scale-out than an in-memory cache. 使用分散式快取會將快取記憶體卸載至外部進程。Using a distributed cache offloads the cache memory to an external process.

除非快取優先順序設定為CacheItemPriority.NeverRemove, 否則快取會收回記憶體壓力下的快取專案。 IMemoryCacheThe IMemoryCache cache will evict cache entries under memory pressure unless the cache priority is set to CacheItemPriority.NeverRemove. 您可以設定CacheItemPriority , 以調整快取在記憶體壓力下收回專案的優先順序。You can set the CacheItemPriority to adjust the priority with which the cache evicts items under memory pressure.

記憶體內部快取可以儲存任何物件;分散式快取介面僅限於byte[]The in-memory cache can store any object; the distributed cache interface is limited to byte[]. 記憶體內部和分散式快取存放區會以索引鍵/值組的形式快取專案。The in-memory and distributed cache store cache items as key-value pairs.

System.Runtime.Caching/MemoryCacheSystem.Runtime.Caching/MemoryCache

System.Runtime.Caching/MemoryCache(NuGet 套件) 可與搭配使用:System.Runtime.Caching/MemoryCache (NuGet package) can be used with:

  • .NET Standard 2.0 或更新版本。.NET Standard 2.0 or later.
  • 以 .NET Standard 2.0 或更新版本為目標的任何.net 執行Any .NET implementation that targets .NET Standard 2.0 or later. 例如, ASP.NET Core 2.0 或更新版本。For example, ASP.NET Core 2.0 or later.
  • .NET Framework 4.5 或更新版本。.NET Framework 4.5 or later.

/建議使用IMemoryCache MicrosoftExtensionsMemoryCache . Caching. Memory (如本文所述), 因為它已更緊密整合到 ASP.NET Core 中。System.Runtime.Caching /Microsoft.Extensions.Caching.Memory/IMemoryCache (described in this article) is recommended over System.Runtime.Caching/MemoryCache because it's better integrated into ASP.NET Core. 例如, IMemoryCache會以原生方式使用 ASP.NET Core 相依性插入For example, IMemoryCache works natively with ASP.NET Core dependency injection.

System.Runtime.Caching程式碼從 ASP.NET 4.x 移植到 ASP.NET Core 時, 請使用/ MemoryCache做為相容性橋接器。Use System.Runtime.Caching/MemoryCache as a compatibility bridge when porting code from ASP.NET 4.x to ASP.NET Core.

快取指導方針Cache guidelines

  • 程式碼應該一律要有可用於擷取資料的後援選項,而應該依賴快取的值。Code should always have a fallback option to fetch data and not depend on a cached value being available.
  • 快取使用不足的資源, 記憶體。The cache uses a scarce resource, memory. 限制快取成長:Limit cache growth:
    • 使用外部輸入作為快取索引鍵。Do not use external input as cache keys.
    • 使用到期時間來限制快取成長。Use expirations to limit cache growth.
    • 使用 SetSize、Size 和 SizeLimit 來限制快取大小。Use SetSize, Size, and SizeLimit to limit cache size. ASP.NET Core 執行時間不會根據記憶體壓力來限制快取大小。The ASP.NET Core runtime does not limit cache size based on memory pressure. 開發人員需要限制快取大小。It's up to the developer to limit cache size.

使用 IMemoryCacheUsing IMemoryCache

警告

從相依性插入使用共用記憶體快取, SetSizeSize呼叫、 SizeLimit或來限制快取大小, 可能會導致應用程式失敗。Using a shared memory cache from Dependency Injection and calling SetSize, Size, or SizeLimit to limit cache size can cause the app to fail. 在快取上設定大小限制時, 所有專案在新增時都必須指定大小。When a size limit is set on a cache, all entries must specify a size when being added. 這可能會導致問題, 因為開發人員可能無法完全控制使用共用快取的內容。This can lead to issues since developers may not have full control on what uses the shared cache. 例如, Entity Framework Core 使用共用快取, 且未指定大小。For example, Entity Framework Core uses the shared cache and does not specify a size. 如果應用程式設定快取大小限制, 並使用 EF Core, 應用程式InvalidOperationException會擲回。If an app sets a cache size limit and uses EF Core, the app throws an InvalidOperationException. 使用SetSizeSize或來限制快取時,請建立單一快取來進行快取。SizeLimitWhen using SetSize, Size, or SizeLimit to limit cache, create a cache singleton for caching. 如需詳細資訊和範例, 請參閱使用 SetSize、大小和 SizeLimit 來限制快取大小。For more information and an example, see Use SetSize, Size, and SizeLimit to limit cache size.

記憶體內部快取是使用相依性插入從您的應用程式參考的一服務In-memory caching is a service that's referenced from your app using Dependency Injection. AddMemoryCacheConfigureServices呼叫:Call AddMemoryCache in ConfigureServices:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMemoryCache();

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvcWithDefaultRoute();
    }
}

在此函數中要求實例:IMemoryCacheRequest the IMemoryCache instance in the constructor:

public class HomeController : Controller
{
    private IMemoryCache _cache;

    public HomeController(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }

IMemoryCache需要 NuGet 套件Microsoft Extensions. Caching. MemoryIMemoryCache requires NuGet package Microsoft.Extensions.Caching.Memory.

IMemoryCache需要 AspNetCore 的Microsoft.Extensions.Caching.MemoryNuGet 套件。[Microsoft.AspNetCore 所有中繼套件] 中都有提供。IMemoryCache requires NuGet package Microsoft.Extensions.Caching.Memory, which is available in the Microsoft.AspNetCore.All metapackage.

IMemoryCache需要 NuGet 套件 AspNetCore。您可以在中繼套件中找到該儲存體IMemoryCache requires NuGet package Microsoft.Extensions.Caching.Memory, which is available in the Microsoft.AspNetCore.App metapackage.

下列程式碼會使用TryGetValue來檢查快取中是否有時間。The following code uses TryGetValue to check if a time is in the cache. 如果沒有快取時間, 則會建立新的專案, 並將其新增至已設定的快取中。If a time isn't cached, a new entry is created and added to the cache with Set.

public static class CacheKeys
{
    public static string Entry { get { return "_Entry"; } }
    public static string CallbackEntry { get { return "_Callback"; } }
    public static string CallbackMessage { get { return "_CallbackMessage"; } }
    public static string Parent { get { return "_Parent"; } }
    public static string Child { get { return "_Child"; } }
    public static string DependentMessage { get { return "_DependentMessage"; } }
    public static string DependentCTS { get { return "_DependentCTS"; } }
    public static string Ticks { get { return "_Ticks"; } }
    public static string CancelMsg { get { return "_CancelMsg"; } }
    public static string CancelTokenSource { get { return "_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);
}

此時會顯示目前的時間和快取時間:The current time and the cached time are displayed:

@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="CacheGetOrCreateAsync">GetOrCreateAsync</a></li>
        <li><a asp-controller="Home" asp-action="CacheRemove">Remove</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>

當超時時間內有要求時, 快取的值會保留在快取中。DateTimeThe cached DateTime value remains in the cache while there are requests within the timeout period. 下圖顯示從快取中抓取的目前時間和較舊的時間:The following image shows the current time and an older time retrieved from the cache:

顯示兩個不同時間的索引視圖

下列程式碼會使用getorcreate 設定GetOrCreateAsync來快取資料。The following code uses GetOrCreate and GetOrCreateAsync to cache data.

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> CacheGetOrCreateAsync()
{
    var cacheEntry = await
        _cache.GetOrCreateAsync(CacheKeys.Entry, entry =>
    {
        entry.SlidingExpiration = TimeSpan.FromSeconds(3);
        return Task.FromResult(DateTime.Now);
    });

    return View("Cache", cacheEntry);
}

下列程式碼會呼叫 Get 來提取取的時間:The following code calls Get to fetch the cached time:

public IActionResult CacheGet()
{
    var cacheEntry = _cache.Get<DateTime?>(CacheKeys.Entry);
    return View("Cache", cacheEntry);
}

GetOrCreateGetOrCreateAsyncGetCacheExtensions IMemoryCache類別的擴充方法部分, 可延伸的功能。GetOrCreate , GetOrCreateAsync, and Get are extension methods part of the CacheExtensions class that extends the capability of IMemoryCache. 如需其他快取方法的說明, 請參閱IMemoryCache 方法CacheExtensions 方法See IMemoryCache methods and CacheExtensions methods for a description of other cache methods.

MemoryCacheEntryOptionsMemoryCacheEntryOptions

下列範例:The following sample:

  • 設定絕對到期時間。Sets the absolute expiration time. 這是可快取專案的最長時間, 並防止當滑動到期時間持續更新時, 專案變得太過時。This is the maximum time the entry can be cached and prevents the item from becoming too stale when the sliding expiration is continuously renewed.
  • 設定滑動到期時間。Sets a sliding expiration time. 存取此快取專案的要求將會重設滑動到期時鐘。Requests that access this cached item will reset the sliding expiration clock.
  • 將快取優先順序設定CacheItemPriority.NeverRemove為。Sets the cache priority to CacheItemPriority.NeverRemove.
  • 設定在從快取中收回專案之後, 將會呼叫的PostEvictionDelegateSets a PostEvictionDelegate that will be called after the entry is evicted from the cache. 回呼會在與從快取中移除專案的程式碼不同的執行緒上執行。The callback is run on a different thread from the code that removes the item from the cache.
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 來限制快取大小Use SetSize, Size, and SizeLimit to limit cache size

MemoryCache實例可以選擇性地指定並強制執行大小限制。A MemoryCache instance may optionally specify and enforce a size limit. 記憶體大小限制沒有定義的測量單位, 因為快取沒有測量專案大小的機制。The memory size limit does not have a defined unit of measure because the cache has no mechanism to measure the size of entries. 如果已設定快取記憶體大小限制, 所有專案都必須指定大小。If the cache memory size limit is set, all entries must specify size. ASP.NET Core 執行時間不會根據記憶體壓力來限制快取大小。The ASP.NET Core runtime does not limit cache size based on memory pressure. 開發人員需要限制快取大小。It's up to the developer to limit cache size. 指定的大小是開發人員選擇的單位。The size specified is in units the developer chooses.

例如:For example:

  • 如果 web 應用程式主要是快取字串, 則每個快取專案大小都可以是字串長度。If the web app was primarily caching strings, each cache entry size could be the string length.
  • 應用程式可以將所有專案的大小指定為 1, 而大小限制是專案的計數。The app could specify the size of all entries as 1, and the size limit is the count of entries.

下列程式碼會建立可透過相依性插入來存取的無單位固定大小MemoryCache :The following code creates a unitless fixed size MemoryCache accessible by dependency injection:

// using Microsoft.Extensions.Caching.Memory;
public class MyMemoryCache 
{
    public MemoryCache Cache { get; set; }
    public MyMemoryCache()
    {
        Cache = new MemoryCache(new MemoryCacheOptions
        {
            SizeLimit = 1024
        });
    }
}

SizeLimit沒有單位。SizeLimit does not have units. 如果已設定快取記憶體大小, 則快取的專案必須以其認為最適合的任何單位來指定大小。Cached entries must specify size in whatever units they deem most appropriate if the cache memory size has been set. 快取實例的所有使用者都應使用相同的單位系統。All users of a cache instance should use the same unit system. 如果快取的專案大小總和超過所指定SizeLimit的值, 則不會快取專案。An entry will not be cached if the sum of the cached entry sizes exceeds the value specified by SizeLimit. 如果未設定快取大小限制, 則會忽略在該專案上設定的快取大小。If no cache size limit is set, the cache size set on the entry will be ignored.

下列程式碼會MyMemoryCache向相依性插入容器註冊。The following code registers MyMemoryCache with the dependency injection container.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddSingleton<MyMemoryCache>();
}

MyMemoryCache會建立為可感知此大小限制快取之元件的獨立記憶體快取, 並知道如何適當地設定快取專案大小。MyMemoryCache is created as an independent memory cache for components that are aware of this size limited cache and know how to set cache entry size appropriately.

下列程式碼會MyMemoryCache使用:The following code uses MyMemoryCache:

public class AboutModel : PageModel
{
    private MemoryCache _cache;
    public static readonly string MyKey = "_MyKey";

    public AboutModel(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");
    }
}

快取專案的大小可以透過大小SetSize擴充方法來設定:The size of the cache entry can be set by Size or the SetSize extension method:

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");
}

快取相依性Cache dependencies

下列範例示範如何在相依項目過期時將快取項目設定為過期。The following sample shows how to expire a cache entry if a dependent entry expires. CancellationChangeToken 會新增至快取的項目。A CancellationChangeToken is added to the cached item. 當在 CancellationTokenSource 上呼叫 Cancel 時,會撤出兩個快取項目。When Cancel is called on the CancellationTokenSource, both cache entries are evicted.

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 a CancellationTokenSource allows multiple cache entries to be evicted as a group. 使用上述using程式碼中的模式時, 在using區塊內建立的快取專案將會繼承觸發程式和到期設定。With the using pattern in the code above, cache entries created inside the using block will inherit triggers and expiration settings.

其他備註Additional notes

  • 使用回呼來重新擴展快取專案時:When using a callback to repopulate a cache item:

    • 因為回呼尚未完成, 所以多個要求可以將快取的金鑰值空白。Multiple requests can find the cached key value empty because the callback hasn't completed.
    • 這可能會導致數個執行緒重新填入快取的專案。This can result in several threads repopulating the cached item.
  • 當有一個快取專案用來建立另一個時, 子系會複製父系專案的到期權杖和以時間為基礎的到期設定。When one cache entry is used to create another, the child copies the parent entry's expiration tokens and time-based expiration settings. 子系不會因為手動移除或更新父專案而過期。The child isn't expired by manual removal or updating of the parent entry.

  • 使用PostEvictionCallbacks設定在從快取中收回快取專案之後, 將會引發的回呼。Use PostEvictionCallbacks to set the callbacks that will be fired after the cache entry is evicted from the cache.

其他資源Additional resources