ASP.NET Core의 메모리 내 캐시Cache in-memory in ASP.NET Core

작성자: Rick Anderson, John LuoSteve 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. 응용 프로그램이 캐시된 데이터에 의존하지 않도록 만들어야 하고 테스트해야 합니다.You should write and test your app to never depend on cached data.

ASP.NET Core는 몇 가지 다른 종류의 캐시를 지원합니다.ASP.NET Core supports several different caches. 가장 간단한 캐시는 웹 서버의 메모리에 저장된 캐시를 나타내는 IMemoryCache를 기반으로 합니다.The simplest cache is based on the IMemoryCache, which represents a cache stored in the memory of the web server. 다수의 서버로 구성된 서버 팜에서 실행되는 응용 프로그램이 메모리 내 캐시를 사용할 경우에는 세션이 고정적인지 확인해야 합니다.Apps which 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 웹 앱은 이어지는 모든 후속 요청을 동일한 서버로 라우트하기 위해서 응용 프로그램 요청 라우팅(ARR)을 사용합니다.For example, Azure Web apps use Application Request Routing (ARR) to route all subsequent requests to the same server.

웹 팜에서 비-고정 세션을 사용할 경우에는 캐시 일관성 문제가 발생하지 않도록 분산 캐시가 필요합니다.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.NeverRemove로 설정되지 않은 캐시 항목을 메모리 부하에 따라 제거합니다.The 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[].

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

System.Runtime.Caching/MemoryCache (NuGet 패키지) 함께 사용할 수 있습니다.System.Runtime.Caching/MemoryCache (NuGet package) can be used with:

  • .NET 표준 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 topic) 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

  • 코드에서 데이터를 인출 하는 대체 (fallback) 옵션을 항상 있어야 하 고 되지 사용할 수 있는 캐시 된 값에 따라 달라 집니다.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:

IMemoryCache 사용하기Using IMemoryCache

메모리 내 캐시는 응용 프로그램에서 종속성 주입을 통해서 참조되는 서비스입니다.In-memory caching is a service that's referenced from your app using Dependency Injection. ConfigureServices에서 AddMemoryCache를 호출합니다.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.Memory합니다.IMemoryCache 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. 만약 시간이 캐시되어 있지 않다면 새로운 항목이 생성되고 Set을 통해서 캐시에 추가됩니다.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 (and no eviction due to memory pressure). 다음 그림은 현재 시간과 캐시에서 조회한 그보다 오래된 시간을 보여줍니다.The following image shows the current time and an older time retrieved from the cache:

두 개의 서로 다른 시간을 표시하는 Index 뷰

다음 코드는 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);
}

다음 코드는 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);
}

캐시 메서드에 대한 설명은 IMemoryCache 메서드CacheExtensions 메서드를 참고하시기 바랍니다.See IMemoryCache methods and CacheExtensions methods for a description of the 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.
  • 캐시에서 항목이 제거된 후에 호출되는 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

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. 지정 된 크기가 단위는 개발자가 선택 합니다.The size specified is in units the developer chooses.

예를 들어:For example:

  • 웹 앱 문자열 캐싱 주로 된 각 캐시 항목 크기 문자열 길이 수 있습니다.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. CancellationTokenSourceCancel이 호출되면 캐시된 두 항목 모두 제거됩니다.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