Cache dalam memori di ASP.NET Core

Oleh Rick Anderson, John Luo, dan Steve Smith

Penembolokan dapat secara signifikan meningkatkan performa dan skalabilitas aplikasi dengan mengurangi pekerjaan yang diperlukan untuk menghasilkan konten. Penembolokan berfungsi paling baik dengan data yang jarang berubah dan mahal untuk dihasilkan. Penembolokan membuat salinan data yang dapat dikembalikan jauh lebih cepat daripada dari sumbernya. Aplikasi harus ditulis dan diuji agar tidak pernah bergantung pada data yang di-cache.

ASP.NET Core mendukung beberapa cache yang berbeda. Cache paling sederhana didasarkan pada IMemoryCache. IMemoryCache mewakili cache yang disimpan dalam memori server web. Aplikasi yang berjalan di farm server (beberapa server) harus memastikan sesi lengket saat menggunakan cache dalam memori. Sesi tempel memastikan bahwa permintaan dari klien semuanya masuk ke server yang sama. Misalnya, aplikasi Azure Web menggunakan Perutean Permintaan Aplikasi (ARR) untuk merutekan semua permintaan ke server yang sama.

Sesi yang tidak lengket di farm web memerlukan cache terdistribusi untuk menghindari masalah konsistensi cache. Untuk beberapa aplikasi, cache terdistribusi dapat mendukung peluasan skala yang lebih tinggi daripada cache dalam memori. Menggunakan cache terdistribusi membongkar memori cache ke proses eksternal.

Cache dalam memori dapat menyimpan objek apa pun. Antarmuka cache terdistribusi terbatas pada byte[]. Item cache penyimpanan cache dalam memori dan cache terdistribusi sebagai pasangan kunci-nilai.

System.Runtime.Caching/MemoryCache

System.Runtime.Caching/MemoryCache (Paket NuGet) dapat digunakan dengan:

  • .NET Standard 2.0 atau yang lebih baru.
  • Implementasi .NET apa pun yang menargetkan .NET Standard 2.0 atau yang lebih baru. Misalnya, ASP.NET Core 3.1 atau yang lebih baru.
  • .NET Framework 4.5 atau yang lebih baru.

Microsoft.Extensions.Caching.MemoryIMemoryCache/ (dijelaskan dalam artikel ini) direkomendasikan karena System.Runtime.Caching/MemoryCache lebih baik diintegrasikan ke dalam ASP.NET Core. Misalnya, IMemoryCache bekerja secara asli dengan injeksi dependensi inti ASP.NET.

Gunakan System.Runtime.Caching/MemoryCache sebagai jembatan kompatibilitas saat mentransfer kode dari ASP.NET 4.x ke ASP.NET Core.

Panduan cache

  • Kode harus selalu memiliki opsi fallback untuk mengambil data dan tidak bergantung pada nilai cache yang tersedia.
  • Cache menggunakan sumber daya yang langka, memori. Batasi pertumbuhan cache:
    • Jangan masukkan input eksternal ke dalam cache. Sebagai contoh, menggunakan input yang disediakan pengguna arbitrer sebagai kunci cache tidak disarankan karena input mungkin menggunakan jumlah memori yang tidak dapat diprediksi.
    • Gunakan kedaluwarsa untuk membatasi pertumbuhan cache.
    • Gunakan SetSize, Size, dan SizeLimit untuk membatasi ukuran cache. Runtime ASP.NET Core tidak membatasi ukuran cache berdasarkan tekanan memori. Terserah pengembang untuk membatasi ukuran cache.

Menggunakan IMemoryCache

Peringatan

Menggunakan cache memori bersama dari Injeksi Dependensi dan panggilan SetSize, , Sizeatau SizeLimit untuk membatasi ukuran cache dapat menyebabkan aplikasi gagal. Ketika batas ukuran diatur pada cache, semua entri harus menentukan ukuran saat ditambahkan. Ini dapat menyebabkan masalah karena pengembang mungkin tidak memiliki kontrol penuh tentang apa yang menggunakan cache bersama. Saat menggunakan SetSize, Size, atau SizeLimit untuk membatasi cache, buat cache singleton untuk penembolokan. Untuk informasi selengkapnya dan contohnya, lihat Menggunakan SetSize, Size, dan SizeLimit untuk membatasi ukuran cache. Cache bersama dibagikan oleh kerangka kerja atau pustaka lain.

Penembolokan dalam memori adalah layanan yang dirujuk dari aplikasi menggunakan Injeksi Dependensi. IMemoryCache Minta instans di konstruktor:

public class IndexModel : PageModel
{
    private readonly IMemoryCache _memoryCache;

    public IndexModel(IMemoryCache memoryCache) =>
        _memoryCache = memoryCache;

    // ...

Kode berikut menggunakan TryGetValue untuk memeriksa apakah waktu ada di cache. Jika waktu tidak di-cache, entri baru dibuat dan ditambahkan ke cache dengan 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;
}

Dalam kode sebelumnya, entri cache dikonfigurasi dengan kedaluwarsa geser tiga detik. Jika entri cache tidak diakses selama lebih dari tiga detik, entri tersebut akan dikeluarkan dari cache. Setiap kali entri cache diakses, entri tersebut tetap berada di cache selama 3 detik lebih lanjut. Kelas CacheKeys adalah bagian dari sampel unduhan.

Waktu saat ini dan waktu yang di-cache ditampilkan:

<ul>
    <li>Current Time: @Model.CurrentDateTime</li>
    <li>Cached Time: @Model.CacheCurrentDateTime</li>
</ul>

Kode berikut menggunakan Set metode ekstensi untuk menyimpan data untuk waktu relatif tanpa MemoryCacheEntryOptions:

_memoryCache.Set(CacheKeys.Entry, DateTime.Now, TimeSpan.FromDays(1));

Dalam kode sebelumnya, entri cache dikonfigurasi dengan kedaluwarsa relatif satu hari. Entri cache dikeluarkan dari cache setelah satu hari, bahkan jika diakses dalam periode batas waktu ini.

Kode berikut menggunakan GetOrCreate dan GetOrCreateAsync untuk menyimpan data.

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

    // ...
}

Kode berikut memanggil Get untuk mengambil waktu yang di-cache:

var cacheEntry = _memoryCache.Get<DateTime?>(CacheKeys.Entry);

Kode berikut mendapatkan atau membuat item yang di-cache dengan kedaluwarsa absolut:

var cachedValue = _memoryCache.GetOrCreate(
    CacheKeys.Entry,
    cacheEntry =>
    {
        cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
        return DateTime.Now;
    });

Item yang di-cache yang diatur hanya dengan kedaluwarsa geser berisiko tidak pernah kedaluwarsa. Jika item yang di-cache berulang kali diakses dalam interval kedaluwarsa geser, item tidak pernah kedaluwarsa. Gabungkan kedaluwarsa geser dengan kedaluwarsa absolut untuk menjamin item kedaluwarsa. Kedaluwarsa absolut mengatur batas atas pada berapa lama item dapat di-cache sambil tetap mengizinkan item kedaluwarsa lebih awal jika tidak diminta dalam interval kedaluwarsa geser. Jika interval kedaluwarsa geser atau waktu kedaluwarsa absolut berlalu, item dikeluarkan dari cache.

Kode berikut mendapatkan atau membuat item yang di-cache dengan geser dan kedaluwarsa absolut:

var cachedValue = _memoryCache.GetOrCreate(
    CacheKeys.CallbackEntry,
    cacheEntry =>
    {
        cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
        cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
        return DateTime.Now;
    });

Kode sebelumnya menjamin data tidak akan di-cache lebih lama dari waktu absolut.

GetOrCreate, GetOrCreateAsync, dan Get adalah metode ekstensi di CacheExtensions kelas . Metode ini memperluas kemampuan IMemoryCache.

MemoryCacheEntryOptions

Lihat contoh berikut:

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

Gunakan SetSize, Size, dan SizeLimit untuk membatasi ukuran cache

Instans MemoryCache dapat secara opsional menentukan dan menerapkan batas ukuran. Batas ukuran cache tidak memiliki satuan pengukuran yang ditentukan karena cache tidak memiliki mekanisme untuk mengukur ukuran entri. Jika batas ukuran cache diatur, semua entri harus menentukan ukuran. Runtime ASP.NET Core tidak membatasi ukuran cache berdasarkan tekanan memori. Terserah pengembang untuk membatasi ukuran cache. Ukuran yang ditentukan adalah dalam unit yang dipilih pengembang.

Contohnya:

  • Jika aplikasi web terutama menyimpan string, setiap ukuran entri cache bisa menjadi panjang string.
  • Aplikasi ini dapat menentukan ukuran semua entri sebagai 1, dan batas ukurannya adalah jumlah entri.

Jika SizeLimit tidak diatur, cache tumbuh tanpa terikat. Runtime ASP.NET Core tidak memangkas cache saat memori sistem rendah. Aplikasi harus dirancang untuk:

  • Batasi pertumbuhan cache.
  • Panggilan Compact atau Remove ketika memori yang tersedia terbatas.

Kode berikut membuat ukuran MemoryCache tetap tanpa unit yang dapat diakses oleh injeksi dependensi:

public class MyMemoryCache
{
    public MemoryCache Cache { get; } = new MemoryCache(
        new MemoryCacheOptions
        {
            SizeLimit = 1024
        });
}

SizeLimit tidak memiliki unit. Entri cache harus menentukan ukuran dalam unit apa pun yang dianggap paling tepat jika batas ukuran cache telah ditetapkan. Semua pengguna instans cache harus menggunakan sistem unit yang sama. Entri tidak akan di-cache jika jumlah ukuran entri yang di-cache melebihi nilai yang ditentukan oleh SizeLimit. Jika tidak ada batas ukuran cache yang diatur, ukuran cache yang diatur pada entri diabaikan.

Kode berikut mendaftar MyMemoryCache dengan kontainer injeksi dependensi:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddSingleton<MyMemoryCache>();

MyMemoryCache dibuat sebagai cache memori independen untuk komponen yang mengetahui cache terbatas ukuran ini dan tahu cara mengatur ukuran entri cache dengan tepat.

Ukuran entri cache dapat diatur menggunakan SetSize metode ekstensi atau Size properti :

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

Dalam kode sebelumnya, dua baris yang disorot mencapai hasil yang sama untuk mengatur ukuran entri cache. SetSize disediakan untuk kenyamanan saat merantai panggilan ke new MemoryCacheOptions().

MemoryCache.Compact

MemoryCache.Compact mencoba menghapus persentase cache yang ditentukan dalam urutan berikut:

  • Semua item kedaluwarsa.
  • Item berdasarkan prioritas. Item prioritas terendah dihapus terlebih dahulu.
  • Objek yang terakhir digunakan.
  • Item dengan kedaluwarsa absolut paling awal.
  • Item dengan kedaluwarsa geser paling awal.

Item yang disematkan dengan prioritas NeverRemovetidak pernah dihapus. Kode berikut menghapus item cache dan panggilan Compact untuk menghapus 25% entri cache:

_myMemoryCache.Cache.Remove(CacheKeys.Entry);
_myMemoryCache.Cache.Compact(.25);

Untuk informasi selengkapnya, lihat Sumber ringkas di GitHub.

Dependensi cache

Contoh berikut menunjukkan cara kedaluwarsa entri cache jika entri dependen kedaluwarsa. CancellationChangeToken ditambahkan ke item yang di-cache. Ketika Cancel dipanggil pada CancellationTokenSource, kedua entri cache dikeluarkan:

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

Menggunakan memungkinkan CancellationTokenSource beberapa entri cache dikeluarkan sebagai grup. using Dengan pola dalam kode di atas, entri cache yang dibuat di dalam using cakupan mewarisi pemicu dan pengaturan kedaluwarsa.

Catatan tambahan

  • Kedaluwarsa tidak terjadi di latar belakang. Tidak ada timer yang secara aktif memindai cache untuk item yang kedaluwarsa. Setiap aktivitas pada cache (Get, Set, Remove) dapat memicu pemindaian latar belakang untuk item yang kedaluwarsa. Timer pada CancellationTokenSource (CancelAfter) juga menghapus entri dan memicu pemindaian untuk item yang kedaluwarsa. Contoh berikut menggunakan CancellationTokenSource(TimeSpan) untuk token terdaftar. Ketika token ini diaktifkan, token ini segera menghapus entri dan menembakkan panggilan balik pengeluaran:

    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);
    }
    
  • Saat menggunakan panggilan balik untuk mengisi ulang item cache:

    • Beberapa permintaan dapat menemukan nilai kunci yang di-cache kosong karena panggilan balik belum selesai.
    • Ini dapat mengakibatkan beberapa utas mengisi ulang item yang di-cache.
  • Ketika satu entri cache digunakan untuk membuat yang lain, anak menyalin token kedaluwarsa entri induk dan pengaturan kedaluwarsa berbasis waktu. Anak tidak kedaluwarsa oleh penghapusan manual atau pembaruan entri induk.

  • Gunakan PostEvictionCallbacks untuk mengatur panggilan balik yang akan diaktifkan setelah entri cache dikeluarkan dari cache.

  • Untuk sebagian besar aplikasi, IMemoryCache diaktifkan. Misalnya, memanggil AddMvc, , AddControllersWithViewsAddRazorPages, AddMvcCore().AddRazorViewEngine, dan banyak metode lain Add{Service} di Program.cs, memungkinkan IMemoryCache. Untuk aplikasi yang tidak memanggil salah satu metode sebelumnyaAdd{Service}, mungkin perlu untuk memanggil AddMemoryCache .Program.cs

Pembaruan cache latar belakang

Gunakan layanan latar belakang seperti IHostedService untuk memperbarui cache. Layanan latar belakang dapat mengolah ulang entri lalu menetapkannya ke cache hanya ketika sudah siap.

Sumber Daya Tambahan:

Melihat atau mengunduh kode sampel (cara mengunduh)

Dasar-dasar penembolokan

Penembolokan dapat secara signifikan meningkatkan performa dan skalabilitas aplikasi dengan mengurangi pekerjaan yang diperlukan untuk menghasilkan konten. Penembolokan berfungsi paling baik dengan data yang jarang berubah dan mahal untuk dihasilkan. Penembolokan membuat salinan data yang dapat dikembalikan jauh lebih cepat daripada dari sumbernya. Aplikasi harus ditulis dan diuji agar tidak pernah bergantung pada data yang di-cache.

ASP.NET Core mendukung beberapa cache yang berbeda. Cache paling sederhana didasarkan pada IMemoryCache. IMemoryCache mewakili cache yang disimpan dalam memori server web. Aplikasi yang berjalan di farm server (beberapa server) harus memastikan sesi lengket saat menggunakan cache dalam memori. Sesi lekat memastikan bahwa permintaan berikutnya dari klien semuanya masuk ke server yang sama. Misalnya, aplikasi Azure Web menggunakan Perutean Permintaan Aplikasi (ARR) untuk merutekan semua permintaan berikutnya ke server yang sama.

Sesi yang tidak lengket di farm web memerlukan cache terdistribusi untuk menghindari masalah konsistensi cache. Untuk beberapa aplikasi, cache terdistribusi dapat mendukung peluasan skala yang lebih tinggi daripada cache dalam memori. Menggunakan cache terdistribusi membongkar memori cache ke proses eksternal.

Cache dalam memori dapat menyimpan objek apa pun. Antarmuka cache terdistribusi terbatas pada byte[]. Item cache penyimpanan cache dalam memori dan cache terdistribusi sebagai pasangan kunci-nilai.

System.Runtime.Caching/MemoryCache

System.Runtime.Caching/MemoryCache (Paket NuGet) dapat digunakan dengan:

  • .NET Standard 2.0 atau yang lebih baru.
  • Implementasi .NET apa pun yang menargetkan .NET Standard 2.0 atau yang lebih baru. Misalnya, ASP.NET Core 3.1 atau yang lebih baru.
  • .NET Framework 4.5 atau yang lebih baru.

Microsoft.Extensions.Caching.MemoryIMemoryCache/ (dijelaskan dalam artikel ini) direkomendasikan karena System.Runtime.Caching/MemoryCache lebih baik diintegrasikan ke dalam ASP.NET Core. Misalnya, IMemoryCache bekerja secara asli dengan injeksi dependensi inti ASP.NET.

Gunakan System.Runtime.Caching/MemoryCache sebagai jembatan kompatibilitas saat mentransfer kode dari ASP.NET 4.x ke ASP.NET Core.

Panduan cache

  • Kode harus selalu memiliki opsi fallback untuk mengambil data dan tidak bergantung pada nilai cache yang tersedia.
  • Cache menggunakan sumber daya yang langka, memori. Batasi pertumbuhan cache:
    • Jangan gunakan input eksternal sebagai kunci cache.
    • Gunakan kedaluwarsa untuk membatasi pertumbuhan cache.
    • Gunakan SetSize, Size, dan SizeLimit untuk membatasi ukuran cache. Runtime ASP.NET Core tidak membatasi ukuran cache berdasarkan tekanan memori. Terserah pengembang untuk membatasi ukuran cache.

Menggunakan IMemoryCache

Peringatan

Menggunakan cache memori bersama dari Injeksi Dependensi dan panggilan SetSize, , Sizeatau SizeLimit untuk membatasi ukuran cache dapat menyebabkan aplikasi gagal. Ketika batas ukuran diatur pada cache, semua entri harus menentukan ukuran saat ditambahkan. Ini dapat menyebabkan masalah karena pengembang mungkin tidak memiliki kontrol penuh tentang apa yang menggunakan cache bersama. Saat menggunakan SetSize, Size, atau SizeLimit untuk membatasi cache, buat cache singleton untuk penembolokan. Untuk informasi selengkapnya dan contohnya, lihat Menggunakan SetSize, Size, dan SizeLimit untuk membatasi ukuran cache. Cache bersama dibagikan oleh kerangka kerja atau pustaka lain.

Penembolokan dalam memori adalah layanan yang dirujuk dari aplikasi menggunakan Injeksi Dependensi. IMemoryCache Minta instans di konstruktor:

public class HomeController : Controller
{
    private IMemoryCache _cache;

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

Kode berikut menggunakan TryGetValue untuk memeriksa apakah waktu ada di cache. Jika waktu tidak di-cache, entri baru dibuat dan ditambahkan ke cache dengan Set. Kelas CacheKeys adalah bagian dari sampel unduhan.

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

Waktu saat ini dan waktu yang di-cache ditampilkan:

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

Kode berikut menggunakan Set metode ekstensi untuk menyimpan data untuk waktu relatif tanpa membuat MemoryCacheEntryOptions objek:

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

Nilai yang di-cache DateTime tetap berada di cache sementara ada permintaan dalam periode batas waktu.

Kode berikut menggunakan GetOrCreate dan GetOrCreateAsync untuk menyimpan 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> CacheGetOrCreateAsynchronous()
{
    var cacheEntry = await
        _cache.GetOrCreateAsync(CacheKeys.Entry, entry =>
        {
            entry.SlidingExpiration = TimeSpan.FromSeconds(3);
            return Task.FromResult(DateTime.Now);
        });

    return View("Cache", cacheEntry);
}

Kode berikut memanggil Get untuk mengambil waktu yang di-cache:

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

Kode berikut mendapatkan atau membuat item yang di-cache dengan kedaluwarsa absolut:

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

    return View("Cache", cacheEntry);
}

Item yang di-cache yang diatur hanya dengan kedaluwarsa geser berisiko tidak pernah kedaluwarsa. Jika item yang di-cache berulang kali diakses dalam interval kedaluwarsa geser, item tidak pernah kedaluwarsa. Gabungkan kedaluwarsa geser dengan kedaluwarsa absolut untuk menjamin item kedaluwarsa. Kedaluwarsa absolut mengatur batas atas pada berapa lama item dapat di-cache sambil tetap mengizinkan item kedaluwarsa lebih awal jika tidak diminta dalam interval kedaluwarsa geser. Jika interval kedaluwarsa geser atau waktu kedaluwarsa absolut berlalu, item dikeluarkan dari cache.

Kode berikut mendapatkan atau membuat item yang di-cache dengan geser dan kedaluwarsa absolut:

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

Kode sebelumnya menjamin data tidak akan di-cache lebih lama dari waktu absolut.

GetOrCreate, GetOrCreateAsync, dan Get adalah metode ekstensi di CacheExtensions kelas . Metode ini memperluas kemampuan IMemoryCache.

MemoryCacheEntryOptions

Contoh berikut:

  • Mengatur waktu kedaluwarsa geser. Permintaan yang mengakses item yang di-cache ini akan mengatur ulang jam kedaluwarsa geser.
  • Mengatur prioritas cache ke CacheItemPriority.NeverRemove.
  • PostEvictionDelegate Mengatur yang akan dipanggil setelah entri dikeluarkan dari cache. Panggilan balik dijalankan pada utas yang berbeda dari kode yang menghapus item dari 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);
}

Gunakan SetSize, Size, dan SizeLimit untuk membatasi ukuran cache

Instans MemoryCache dapat secara opsional menentukan dan menerapkan batas ukuran. Batas ukuran cache tidak memiliki satuan ukuran yang ditentukan karena cache tidak memiliki mekanisme untuk mengukur ukuran entri. Jika batas ukuran cache diatur, semua entri harus menentukan ukuran. Runtime ASP.NET Core tidak membatasi ukuran cache berdasarkan tekanan memori. Terserah pengembang untuk membatasi ukuran cache. Ukuran yang ditentukan adalah dalam unit yang dipilih pengembang.

Contohnya:

  • Jika aplikasi web terutama menyimpan string, setiap ukuran entri cache bisa menjadi panjang string.
  • Aplikasi ini dapat menentukan ukuran semua entri sebagai 1, dan batas ukurannya adalah jumlah entri.

Jika SizeLimit tidak diatur, cache tumbuh tanpa terikat. Runtime ASP.NET Core tidak memangkas cache saat memori sistem rendah. Aplikasi harus dirancang untuk:

  • Batasi pertumbuhan cache.
  • Panggilan Compact atau Remove ketika memori yang tersedia terbatas:

Kode berikut membuat ukuran MemoryCache tetap tanpa unit yang dapat diakses oleh injeksi dependensi:

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

SizeLimit tidak memiliki unit. Entri cache harus menentukan ukuran dalam unit apa pun yang dianggap paling tepat jika batas ukuran cache telah ditetapkan. Semua pengguna instans cache harus menggunakan sistem unit yang sama. Entri tidak akan di-cache jika jumlah ukuran entri yang di-cache melebihi nilai yang ditentukan oleh SizeLimit. Jika tidak ada batas ukuran cache yang diatur, ukuran cache yang diatur pada entri akan diabaikan.

Kode berikut mendaftar MyMemoryCache dengan kontainer injeksi dependensi.

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

MyMemoryCache dibuat sebagai cache memori independen untuk komponen yang mengetahui cache terbatas ukuran ini dan tahu cara mengatur ukuran entri cache dengan tepat.

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

Ukuran entri cache dapat diatur oleh Size atau SetSize metode ekstensi:

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 mencoba menghapus persentase cache yang ditentukan dalam urutan berikut:

  • Semua item kedaluwarsa.
  • Item berdasarkan prioritas. Item prioritas terendah dihapus terlebih dahulu.
  • Objek yang terakhir digunakan.
  • Item dengan kedaluwarsa absolut paling awal.
  • Item dengan kedaluwarsa geser paling awal.

Item yang disematkan dengan prioritas NeverRemove tidak pernah dihapus. Kode berikut menghapus item cache dan panggilan Compact:

_cache.Remove(MyKey);

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

Untuk informasi selengkapnya, lihat Sumber ringkas di GitHub.

Dependensi cache

Contoh berikut menunjukkan cara kedaluwarsa entri cache jika entri dependen kedaluwarsa. CancellationChangeToken ditambahkan ke item yang di-cache. Ketika Cancel dipanggil pada CancellationTokenSource, kedua entri cache dikeluarkan.

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

Menggunakan memungkinkan CancellationTokenSource beberapa entri cache dikeluarkan sebagai grup. using Dengan pola dalam kode di atas, entri cache yang dibuat di dalam using blok akan mewarisi pemicu dan pengaturan kedaluwarsa.

Catatan tambahan

  • Kedaluwarsa tidak terjadi di latar belakang. Tidak ada timer yang secara aktif memindai cache untuk item yang kedaluwarsa. Setiap aktivitas pada cache (Get, Set, Remove) dapat memicu pemindaian latar belakang untuk item yang kedaluwarsa. Timer pada CancellationTokenSource (CancelAfter) juga menghapus entri dan memicu pemindaian untuk item yang kedaluwarsa. Contoh berikut menggunakan CancellationTokenSource(TimeSpan) untuk token terdaftar. Ketika token ini diaktifkan, token ini segera menghapus entri dan mengaktifkan panggilan balik pengeluaran:

    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);
    }
    
  • Saat menggunakan panggilan balik untuk mengisi ulang item cache:

    • Beberapa permintaan dapat menemukan nilai kunci yang di-cache kosong karena panggilan balik belum selesai.
    • Ini dapat mengakibatkan beberapa utas mengisi ulang item yang di-cache.
  • Ketika satu entri cache digunakan untuk membuat yang lain, anak menyalin token kedaluwarsa entri induk dan pengaturan kedaluwarsa berbasis waktu. Anak tidak kedaluwarsa oleh penghapusan manual atau pembaruan entri induk.

  • Gunakan PostEvictionCallbacks untuk mengatur panggilan balik yang akan diaktifkan setelah entri cache dikeluarkan dari cache. Dalam kode contoh, CancellationTokenSource.Dispose() dipanggil untuk merilis sumber daya yang tidak dikelola yang CancellationTokenSourcedigunakan oleh . Namun, CancellationTokenSource tidak segera dibuang karena masih digunakan oleh entri cache. diteruskan CancellationToken ke MemoryCacheEntryOptions untuk membuat entri cache yang kedaluwarsa setelah waktu tertentu. Jadi Dispose tidak boleh dipanggil sampai entri cache dihapus atau kedaluwarsa. Kode contoh memanggil RegisterPostEvictionCallback metode untuk mendaftarkan panggilan balik yang akan dipanggil ketika entri cache dikeluarkan, dan membuang dalam panggilan balik tersebut CancellationTokenSource .

  • Untuk sebagian besar aplikasi, IMemoryCache diaktifkan. Misalnya, memanggil AddMvc, , AddControllersWithViewsAddRazorPages, AddMvcCore().AddRazorViewEngine, dan banyak metode lain Add{Service} di ConfigureServices, memungkinkan IMemoryCache. Untuk aplikasi yang tidak memanggil salah satu metode sebelumnyaAdd{Service}, mungkin perlu untuk memanggil AddMemoryCache .ConfigureServices

Pembaruan cache latar belakang

Gunakan layanan latar belakang seperti IHostedService untuk memperbarui cache. Layanan latar belakang dapat mengolah ulang entri lalu menetapkannya ke cache hanya ketika sudah siap.

Sumber Daya Tambahan: