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

藉由Rick AndersonJohn Luo,和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 應用程式會使用應用程式要求路由(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.

IMemoryCache快取將會收回快取項目,記憶體不足的壓力,除非快取的優先順序設定為CacheItemPriority.NeverRemoveThe 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 實作為目標的.NET Standard 2.0 或更新版本。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.

Microsoft.Extensions.Caching.Memory / IMemoryCache (如本文所述) 建議System.Runtime.Caching / MemoryCache因為深入整合到 ASP.NET Core。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 / MemoryCache橋樑相容性移植程式碼,從 ASP.NET 4.x 到 ASP.NET Core。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:

使用 IMemoryCacheUsing IMemoryCache

記憶體中快取服務從您的應用程式使用參考相依性插入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();
    }
}

要求IMemoryCache建構函式的執行個體:Request 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 需要 NuGet 套件Microsoft.Extensions.Caching.Memory,這是用於Microsoft.AspNetCore.All 中繼套件IMemoryCache requires NuGet package Microsoft.Extensions.Caching.Memory, which is available in the Microsoft.AspNetCore.All metapackage.

IMemoryCache 需要 NuGet 套件Microsoft.Extensions.Caching.Memory,這是用於Microsoft.AspNetCore.App 中繼套件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>

快取DateTime值會維持在快取逾時期間內沒有要求。The 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:

索引檢視,其中顯示兩個不同的時間

下列程式碼會使用GetOrCreateGetOrCreateAsync快取資料。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);
}

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

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

GetOrCreate GetOrCreateAsync,並取得擴充方法一部分CacheExtensions擴充的功能的類別IMemoryCacheGetOrCreate , 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.NeverRemoveSets the cache priority to CacheItemPriority.NeverRemove.
  • 設定組PostEvictionDelegate ,之後就會呼叫從快取收回項目的。Sets 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、 大小和 SizeLimit 限制快取大小Use SetSize, Size, and SizeLimit to limit cache size

AMemoryCache執行個體可能會選擇性地指定並強制執行大小限制。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.

下列程式碼建立 unitless 固定大小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. 將快取項目,如果快取項目大小的總和超過所指定的值SizeLimitAn 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