Ukládání do mezipaměti v ASP.NET Core

Rick Anderson, John Luoa Steve Smith

Zobrazení nebo stažení ukázkového kódu (stažení)

Ukládání do mezipaměti základy

Ukládání do mezipaměti výrazně zlepšit výkon a škálovatelnost aplikace omezením práce potřebné ke generování obsahu. Ukládání do mezipaměti funguje nejlépe s daty, která se mění zřídka a jejich generování je nákladné. Ukládání do mezipaměti vytváří kopii dat, která je možné vrátit mnohem rychleji než ze zdroje. Aplikace by se měly napsat a otestovat tak, aby nikdy nezávisejí na datech uložených v mezipaměti.

ASP.NET Core podporuje několik různých mezipamětí. Nejjednodušší mezipaměť je založená na IMemoryCache. IMemoryCache představuje mezipaměť uloženou v paměti webového serveru. Aplikace běžící na serverové farmě (více serverů) by měly zajistit, aby relace byly při použití mezipaměti v paměti přichycené. Přichycené relace zajišťují, že všechny následné požadavky od klienta přechádnou na stejný server. Například webové aplikace Azure používají směrování žádostí o aplikace (ARR) ke směrování všech následných požadavků na stejný server.

Neuchycené relace ve webové farmě vyžadují distribuovanou mezipaměť, aby se předešlo problémům s konzistencí mezipaměti. U některých aplikací může distribuovaná mezipaměť podporovat větší škálování na více než mezipaměť v paměti. Použití distribuované mezipaměti přesoutí paměť mezipaměti na externí proces.

Mezipaměť v paměti může ukládat libovolný objekt. Rozhraní distribuované mezipaměti je omezené na byte[] . Položky mezipaměti v paměti a distribuované mezipaměti jsou uložené jako páry klíč-hodnota.

System.runtime. Ukládání do mezipaměti/MemoryCache

System.Runtime.Caching/MemoryCache(NuGet balíček) lze použít s:

  • .NET Standard verze 2.0 nebo novější.
  • Jakákoli implementace rozhraní .NET, která je .NET Standard 2.0 nebo novější. Můžete například ASP.NET Core 3.1 nebo novější.
  • .NET Framework verze 4.5 nebo novější.

Microsoft.Extensions. Ukládání do mezipaměti. Místo toho se doporučuje paměť (popsaná v tomto článku), protože je lépe / IMemoryCache System.Runtime.Caching / MemoryCache integrovaná do ASP.NET Core. Například funguje IMemoryCache nativně s injektáží ASP.NET Core závislostí.

Použijte System.Runtime.Caching / MemoryCache jako most kompatibility při přenosu kódu z ASP.NET 4.x do ASP.NET Core.

Pokyny pro mezipaměť

  • Kód by měl mít vždy záložní možnost načtení dat a nezávisí na dostupné hodnotě uložené v mezipaměti.
  • Mezipaměť používá nedostatek prostředků, paměti. Omezení růstu mezipaměti:
    • Nepoužívejte externí vstup jako klíče mezipaměti.
    • K omezení růstu mezipaměti použijte vypršení platnosti.
    • Velikost mezipaměti můžete omezit pomocí vlastností SetSize, Size a SizeLimit. Modul runtime ASP.NET Core velikost mezipaměti neomeze na základě tlaku na paměť. Omezení velikosti mezipaměti je na vývojáři.

Použití IMemoryCache

Upozornění

Použití sdílené mezipaměti paměti z injektáže závislostí a volání , nebo k omezení velikosti mezipaměti může SetSize způsobit selhání Size SizeLimit aplikace. Pokud je pro mezipaměť nastavený limit velikosti, musí všechny položky při přidání určit velikost. To může vést k problémům, protože vývojáři nemusí mít úplnou kontrolu nad tím, co sdílenou mezipaměť používá. Pokud k omezení mezipaměti používáte , nebo , vytvořte jednu mezipaměť pro SetSize Size ukládání do SizeLimit mezipaměti. Další informace a příklad najdete v tématu Omezení velikosti mezipaměti pomocí vlastností SetSize, Size a SizeLimit. Sdílená mezipaměť je sdílená jinými rozhraními nebo knihovnami.

Ukládání do mezipaměti v paměti je služba, na kterou se odkazuje z aplikace využívající injektáž závislostí. Vyžádejte IMemoryCache si instanci v konstruktoru :

public class HomeController : Controller
{
    private IMemoryCache _cache;

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

Následující kód pomocí TryGetValue zkontroluje, jestli je čas v mezipaměti. Pokud čas není uložen v mezipaměti, vytvoří se nová položka a přidá se do mezipaměti pomocíset . Třída CacheKeys je součástí ukázky stahování.

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

Zobrazí se aktuální čas a čas v mezipaměti:

@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>

Následující kód používá metodu rozšíření Set k ukládání dat do mezipaměti pro relativní čas bez vytvoření MemoryCacheEntryOptions objektu .

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

Hodnota uložená DateTime v mezipaměti zůstane v mezipaměti, zatímco během časového limitu existují požadavky.

Následující kód používá GetOrCreate a GetOrCreateAsync k ukládání dat do mezipaměti.

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

Následující kód volá Get, aby načítá čas v mezipaměti:

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

Následující kód získá nebo vytvoří položku v mezipaměti s absolutním vypršením platnosti:

public IActionResult CacheGetOrCreateAbs()
{
    var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
        return DateTime.Now;
    });

    return View("Cache", cacheEntry);
}

U položky uložené v mezipaměti s klouzavým vypršením platnosti hrozí, že se stane zastaralou. Pokud se k této položce přistupuje častěji než k klouzavým intervalu vypršení platnosti, platnost položky nikdy nevyprší. Zkombinujte klouzavá vypršení platnosti s absolutním vypršením platnosti, abyste zaručit, že platnost položky vyprší po uplynutí absolutní doby vypršení platnosti. Absolutní vypršení platnosti nastavuje horní mez toho, jak dlouho se položka může ukládat do mezipaměti a zároveň umožňuje, aby platnost položky vypršela dříve, pokud není požadována v rámci klouzavého intervalu vypršení platnosti. Pokud je zadané absolutní i klouzavé vypršení platnosti, jsou vypršení platnosti logicky ORed. Pokud klouzavá doba vypršení platnosti nebo absolutní doba vypršení platnosti, položka je vyřazena z mezipaměti.

Následující kód získá nebo vytvoří položku v mezipaměti s klouzavým i absolutním vypršením platnosti:

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

Předchozí kód zaručuje, že data nebudou uložena v mezipaměti déle než absolutní čas.

GetOrCreate, GetOrCreateAsync a jsou rozšiřující metody ve třídě Get CacheExtensions . Tyto metody rozšiřují schopnost IMemoryCache .

MemoryCacheEntryOptions

Následující ukázka:

  • Nastaví klouzanou dobu vypršení platnosti. Požadavky, které přistupují k této položce uložené v mezipaměti, resetují posuvné hodiny vypršení platnosti.
  • Nastaví prioritu mezipaměti na CacheItemPriority.NeverRemove.
  • Nastaví PostEvictionDelegate, který bude volán po vyřazení položky z mezipaměti. Zpětné volání se spustí na jiném vlákně než kód, který položku odebere z mezipaměti.
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);
}

K omezení velikosti mezipaměti použijte SetSize, Size a SizeLimit.

Instance MemoryCache může volitelně určit a vynutit omezení velikosti. Omezení velikosti mezipaměti nemá definovanou měrnou jednotku, protože mezipaměť nemá žádný mechanismus měření velikosti položek. Pokud je nastaven limit velikosti mezipaměti, musí všechny položky určit velikost. Modul runtime ASP.NET Core velikost mezipaměti neomeze na základě tlaku v paměti. Omezení velikosti mezipaměti je na vývojáři. Zadaná velikost je v jednotkách, které si vývojář zvolí.

Například:

  • Pokud webová aplikace primárně ukládala řetězce do mezipaměti, může mít každá velikost položky mezipaměti délku řetězce.
  • Aplikace může zadat velikost všech položek jako 1 a omezení velikosti je počet položek.

Pokud SizeLimit není nastavená, mezipaměť se zvětšuje bez vázání. Modul runtime ASP.NET Core oříznutí mezipaměti, když je nedostatek systémové paměti. Aplikace musí být architektonické tak, aby:

  • Omezení růstu mezipaměti.
  • Volání Compact nebo pokud je Remove dostupná paměť omezená:

Následující kód vytvoří pevnou velikost bez jednotek MemoryCache přístupnou injektáží závislostí:

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

SizeLimit nemá jednotky. Pokud jste nastavili omezení velikosti mezipaměti, položky uložené v mezipaměti musí určit velikost v jednotkách, které budou pro tyto jednotky nejvhodnější. Všichni uživatelé instance mezipaměti by měli používat stejný systém jednotek. Položka nebude uložena do mezipaměti, pokud součet velikostí uložených velikosti uložených v mezipaměti překročí hodnotu určenou parametrem SizeLimit . Pokud není nastavený žádný limit velikosti mezipaměti, velikost mezipaměti nastavená u položky se bude ignorovat.

Následující kód se zaregistruje MyMemoryCache v kontejneru injektáže závislostí.

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddSingleton<MyMemoryCache>();
}

MyMemoryCache je vytvořen jako nezávislá mezipaměť paměti pro komponenty, které si tuto velikost omezená mezipaměť uvědomují a ví, jak odpovídajícím způsobem nastavit velikost položky mezipaměti.

Následující kód používá 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");
    }
}

Velikost položky mezipaměti lze nastavit pomocí Size nebo SetSize rozšiřujících metod:

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 se pokusí odebrat zadané procento mezipaměti v následujícím pořadí:

  • Všechny položky, jejichž platnost vypršela.
  • Položky podle priority. Položky s nejnižší prioritou se nejprve odebraly.
  • Nejméně použité objekty.
  • Položky s nejstarším absolutním vypršením platnosti.
  • Položky s nejstarším posuvným vypršením platnosti.

Připnuté položky s prioritou NeverRemove se nikdy neodebraly. Následující kód odebere položku mezipaměti a zavolá Compact :

_cache.Remove(MyKey);

// Remove 33% of cached items.
_cache.Compact(.33);   
cache_size = _cache.Count;

Další informace najdete v článku GitHub zdroj na serveru.

Závislosti mezipaměti

Následující příklad ukazuje, jak ukončit platnost položky mezipaměti, pokud vyprší platnost závislé položky. Do CancellationChangeToken položky uložené v mezipaměti se přidá . Při Cancel volání v se obě položky CancellationTokenSource mezipaměti vyřazeny.

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

Použití umožňuje vyřazení více položek CancellationTokenSource mezipaměti jako skupiny. Se vzorem ve výše uvedeném kódu budou položky mezipaměti vytvořené uvnitř bloku dědit triggery a using using nastavení vypršení platnosti.

Další poznámky

  • Vypršení platnosti se neděje na pozadí. Neexistuje žádný časovač, který aktivně vyhledává položky, jejichž platnost vypršela, v mezipaměti. Jakákoli aktivita v mezipaměti ( , , ) může aktivovat kontrolu Get Set Remove prošlé položky na pozadí. Časovač na ( ) také odebere položku a aktivuje kontrolu CancellationTokenSource CancelAfter položek, jejichž platnost vypršela. Následující příklad používá cancellationTokenSource(TimeSpan) pro registrovaný token. Když se tento token vyhodí, okamžitě odebere položku a vynutí zpětná volání:
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);
}
  • Při použití zpětného volání k opětovnému vytypění položky mezipaměti:

    • Více požadavků může najít hodnotu klíče uloženého v mezipaměti prázdnou, protože zpětné volání se nedokončilo.
    • To může mít za následek naplnění položky uložené v mezipaměti několika vlákny.
  • Když se jedna položka mezipaměti použije k vytvoření jiné položky, podřízený objekt zkopíruje tokeny vypršení platnosti nadřazené položky a nastavení vypršení platnosti podle času. Platnost podřízené položky nevy vypršela ručním odebráním nebo aktualizací nadřazené položky.

  • Slouží k nastavení zpětných volání, která se budou aktivovat po vyřazení položky PostEvictionCallbacks mezipaměti z mezipaměti.

  • U většiny aplikací IMemoryCache je tato možnost povolená. Například volání , AddMvc , , a mnoha dalších metod v systému povoluje AddControllersWithViews AddRazorPages AddMvcCore().AddRazorViewEngine Add{Service} ConfigureServices IMemoryCache . Pro aplikace, které nevolají jednu z předchozích metod, může být nutné volat Add{Service} v AddMemoryCache ConfigureServices .

Aktualizace mezipaměti na pozadí

K aktualizaci mezipaměti použijte službu IHostedService na pozadí, například . Služba na pozadí může položky přepočítat a pak je přiřadit do mezipaměti, jenom když jsou připravené.

Další materiály