Bagikan melalui


Cache di .NET

Dalam artikel ini, Anda mempelajari tentang berbagai mekanisme cache. Penyimpanan cache adalah tindakan menyimpan data dalam lapisan menengah, membuat pengambilan data berikutnya lebih cepat. Secara konseptual, caching adalah strategi optimasi performa dan pertimbangan desain. Cache dapat secara signifikan meningkatkan performa aplikasi dengan membuat data yang jarang berubah (atau mahal untuk diambil) lebih mudah diakses. Artikel ini memperkenalkan tiga pendekatan penyimpanan sementara dan menyediakan kode sumber contoh untuk masing-masing.

Penting

Ada dua MemoryCache class dalam .NET, satu di namespace System.Runtime.Caching dan yang lain di namespace Microsoft.Extensions.Caching:

Meskipun artikel ini berfokus pada penembolokan, artikel ini tidak menyertakan System.Runtime.Caching paket NuGet. Semua referensi ke MemoryCache berada dalam ruang nama Microsoft.Extensions.Caching.

Semua paket Microsoft.Extensions.* siap untuk injeksi dependensi (DI). Antarmuka IMemoryCache, HybridCache, dan IDistributedCache dapat digunakan sebagai layanan.

Penembolokan memori

Di bagian ini, Anda mempelajari tentang paket Microsoft.Extensions.Caching.Memory . Implementasi IMemoryCache saat ini adalah pembungkus yang membungkus ConcurrentDictionary<TKey,TValue>, mengekspos API yang kaya fitur. Entri dalam cache diwakili oleh ICacheEntry dan dapat berupa .object Solusi cache dalam memori sangat cocok untuk aplikasi yang berjalan di satu server, di mana data yang di-cache mengalokasikan memori dalam proses aplikasi.

Petunjuk / Saran

Untuk skenario pencache-an multi-server, pertimbangkan pendekatan Pencache-an Terdistribusi sebagai alternatif untuk pencache-an dalam memori.

API cache dalam memori

Konsumen cache memiliki kontrol atas kedaluwarsa geser dan absolut:

Mengatur kedaluwarsa menyebabkan entri dalam cache dihapus jika tidak diakses dalam batas waktu kedaluwarsa. Konsumen memiliki opsi tambahan untuk mengontrol entri cache, melalui MemoryCacheEntryOptions. Masing-masing ICacheEntry dipasangkan dengan MemoryCacheEntryOptions, yang mengekspos fungsionalitas pengeluaran kedaluwarsa dengan IChangeToken, pengaturan prioritas dengan CacheItemPriority, dan mengontrol ICacheEntry.Size. Metode ekstensi yang relevan adalah:

Contoh cache dalam memori

Untuk menggunakan implementasi default IMemoryCache , panggil AddMemoryCache metode ekstensi untuk mendaftarkan semua layanan yang diperlukan dengan DI. Dalam sampel kode berikut, host generik digunakan untuk mengekspos fungsionalitas DI:

using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache();
using IHost host = builder.Build();

Bergantung pada beban kerja .NET Anda, Anda mungkin mengakses IMemoryCache secara berbeda, misalnya injeksi konstruktor. Dalam sampel ini, Anda menggunakan IServiceProvider instans pada host dan memanggil metode ekstensi generik GetRequiredService<T>(IServiceProvider) :

IMemoryCache cache =
    host.Services.GetRequiredService<IMemoryCache>();

Dengan layanan penembolokan dalam memori terdaftar, dan diselesaikan melalui DI, Anda siap untuk memulai penembolokan. Sampel ini berulang melalui huruf dalam alfabet bahasa Inggris 'A' hingga 'Z'. record AlphabetLetter Tipe menyimpan referensi ke surat, dan menghasilkan pesan.

file record AlphabetLetter(char Letter)
{
    internal string Message =>
        $"The '{Letter}' character is the {Letter - 64} letter in the English alphabet.";
}

Petunjuk / Saran

Pengubah file akses digunakan pada tipe AlphabetLetter, karena didefinisikan di dalam dan hanya dapat diakses dari file Program.cs. Untuk informasi selengkapnya, lihat file (Referensi C#). Untuk melihat kode sumber lengkap, lihat bagian Program.cs .

Sampel mencakup fungsi pembantu yang melakukan iterasi melalui huruf alfabet:

static async ValueTask IterateAlphabetAsync(
    Func<char, Task> asyncFunc)
{
    for (char letter = 'A'; letter <= 'Z'; ++letter)
    {
        await asyncFunc(letter);
    }

    Console.WriteLine();
}

Dalam kode C# sebelumnya:

  • Func<char, Task> asyncFunc ditunggu pada setiap iterasi, dengan melewatkan letter saat ini.
  • Setelah semua huruf diproses, baris kosong ditulis ke konsol.

Untuk menambahkan item ke dalam cache, panggil salah satu API di antara Create atau Set.

var addLettersToCacheTask = IterateAlphabetAsync(letter =>
{
    MemoryCacheEntryOptions options = new()
    {
        AbsoluteExpirationRelativeToNow =
            TimeSpan.FromMilliseconds(MillisecondsAbsoluteExpiration)
    };

    _ = options.RegisterPostEvictionCallback(OnPostEviction);

    AlphabetLetter alphabetLetter =
        cache.Set(
            letter, new AlphabetLetter(letter), options);

    Console.WriteLine($"{alphabetLetter.Letter} was cached.");

    return Task.Delay(
        TimeSpan.FromMilliseconds(MillisecondsDelayAfterAdd));
});
await addLettersToCacheTask;

Dalam kode C# sebelumnya:

  • Variabel addLettersToCacheTask mendelegasikan tugas ke IterateAlphabetAsync dan kemudian menunggu.
  • Func<char, Task> asyncFunc diberi argumen lambda.
  • MemoryCacheEntryOptions diinisialisasi dengan kedaluwarsa absolut yang dihitung dari sekarang.
  • Panggilan balik pasca-pengeluaran didaftarkan.
  • Objek AlphabetLetter diinstansiasi, dan diteruskan ke Set beserta letter dan options.
  • Pesan ditulis ke konsol dan disimpan dalam cache.
  • Akhirnya, Task.Delay dikembalikan.

Untuk setiap huruf dalam alfabet, entri cache ditulis dengan fungsi callback untuk kedaluwarsa dan setelah penghapusan.

Panggilan balik pasca-penghapusan menuliskan rincian nilai yang dihapus ke konsol.

static void OnPostEviction(
    object key, object? letter, EvictionReason reason, object? state)
{
    if (letter is AlphabetLetter alphabetLetter)
    {
        Console.WriteLine($"{alphabetLetter.Letter} was evicted for {reason}.");
    }
};

Sekarang setelah cache diisi, panggilan lain ke IterateAlphabetAsync ditunggu, tetapi kali ini Anda memanggil IMemoryCache.TryGetValue:

var readLettersFromCacheTask = IterateAlphabetAsync(letter =>
{
    if (cache.TryGetValue(letter, out object? value) &&
        value is AlphabetLetter alphabetLetter)
    {
        Console.WriteLine($"{letter} is still in cache. {alphabetLetter.Message}");
    }

    return Task.CompletedTask;
});
await readLettersFromCacheTask;

Jika cache berisi kunci letter, dan value adalah instans dari AlphabetLetter, maka value tersebut ditulis ke konsol. letter Ketika kunci tidak ada di cache, kunci tersebut dikeluarkan dan panggilan balik pasca pengeluarannya dipanggil.

Metode ekstensi tambahan

Hadir IMemoryCache dengan banyak metode ekstensi berbasis kenyamanan, termasuk asinkron GetOrCreateAsync:

Satukan semuanya

Seluruh kode sumber aplikasi sampel adalah program tingkat atas dan memerlukan dua paket NuGet:

using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache();
using IHost host = builder.Build();

IMemoryCache cache =
    host.Services.GetRequiredService<IMemoryCache>();

const int MillisecondsDelayAfterAdd = 50;
const int MillisecondsAbsoluteExpiration = 750;

static void OnPostEviction(
    object key, object? letter, EvictionReason reason, object? state)
{
    if (letter is AlphabetLetter alphabetLetter)
    {
        Console.WriteLine($"{alphabetLetter.Letter} was evicted for {reason}.");
    }
};

static async ValueTask IterateAlphabetAsync(
    Func<char, Task> asyncFunc)
{
    for (char letter = 'A'; letter <= 'Z'; ++letter)
    {
        await asyncFunc(letter);
    }

    Console.WriteLine();
}

var addLettersToCacheTask = IterateAlphabetAsync(letter =>
{
    MemoryCacheEntryOptions options = new()
    {
        AbsoluteExpirationRelativeToNow =
            TimeSpan.FromMilliseconds(MillisecondsAbsoluteExpiration)
    };

    _ = options.RegisterPostEvictionCallback(OnPostEviction);

    AlphabetLetter alphabetLetter =
        cache.Set(
            letter, new AlphabetLetter(letter), options);

    Console.WriteLine($"{alphabetLetter.Letter} was cached.");

    return Task.Delay(
        TimeSpan.FromMilliseconds(MillisecondsDelayAfterAdd));
});
await addLettersToCacheTask;

var readLettersFromCacheTask = IterateAlphabetAsync(letter =>
{
    if (cache.TryGetValue(letter, out object? value) &&
        value is AlphabetLetter alphabetLetter)
    {
        Console.WriteLine($"{letter} is still in cache. {alphabetLetter.Message}");
    }

    return Task.CompletedTask;
});
await readLettersFromCacheTask;

await host.RunAsync();

file record AlphabetLetter(char Letter)
{
    internal string Message =>
        $"The '{Letter}' character is the {Letter - 64} letter in the English alphabet.";
}

Anda dapat menyesuaikan nilai MillisecondsDelayAfterAdd dan MillisecondsAbsoluteExpiration untuk mengamati perubahan perilaku terhadap kadaluarsa dan penghapusan entri cache. Berikut ini adalah contoh output dari menjalankan kode ini. (Karena sifat nondeterministik dari peristiwa .NET, output Anda mungkin berbeda.)

A was cached.
B was cached.
C was cached.
D was cached.
E was cached.
F was cached.
G was cached.
H was cached.
I was cached.
J was cached.
K was cached.
L was cached.
M was cached.
N was cached.
O was cached.
P was cached.
Q was cached.
R was cached.
S was cached.
T was cached.
U was cached.
V was cached.
W was cached.
X was cached.
Y was cached.
Z was cached.

A was evicted for Expired.
C was evicted for Expired.
B was evicted for Expired.
E was evicted for Expired.
D was evicted for Expired.
F was evicted for Expired.
H was evicted for Expired.
K was evicted for Expired.
L was evicted for Expired.
J was evicted for Expired.
G was evicted for Expired.
M was evicted for Expired.
N was evicted for Expired.
I was evicted for Expired.
P was evicted for Expired.
R was evicted for Expired.
O was evicted for Expired.
Q was evicted for Expired.
S is still in cache. The 'S' character is the 19 letter in the English alphabet.
T is still in cache. The 'T' character is the 20 letter in the English alphabet.
U is still in cache. The 'U' character is the 21 letter in the English alphabet.
V is still in cache. The 'V' character is the 22 letter in the English alphabet.
W is still in cache. The 'W' character is the 23 letter in the English alphabet.
X is still in cache. The 'X' character is the 24 letter in the English alphabet.
Y is still in cache. The 'Y' character is the 25 letter in the English alphabet.
Z is still in cache. The 'Z' character is the 26 letter in the English alphabet.

Karena kedaluwarsa absolut (MemoryCacheEntryOptions.AbsoluteExpirationRelativeToNow) diatur, semua item yang di-cache pada akhirnya akan dikeluarkan.

Caching Layanan Pekerja

Salah satu strategi umum untuk penyimpanan sementara data adalah memperbarui cache secara independen dari layanan data yang memanfaatkan. Templat Layanan Pekerja adalah contoh yang bagus, karena BackgroundService berjalan secara independen (atau di latar belakang) dari kode aplikasi lainnya. Ketika aplikasi mulai berjalan yang menghosting implementasi IHostedService, implementasi yang sesuai (dalam hal BackgroundService ini atau "pekerja") mulai berjalan dalam proses yang sama. Layanan yang dihosting ini terdaftar di DI sebagai singleton, melalui metode perpanjangan AddHostedService<THostedService>(IServiceCollection). Layanan lain dapat didaftarkan dengan DI dengan masa pakai layanan apa pun.

Penting

Masa pakai layanan penting untuk dipahami. Ketika Anda memanggil AddMemoryCache untuk mendaftarkan semua layanan penyimpanan dalam memori, layanan-layanan tersebut terdaftar sebagai entitas tunggal (singleton).

Skenario layanan foto

Bayangkan Anda mengembangkan layanan foto yang bergantung pada API pihak ketiga yang dapat diakses melalui HTTP. Data foto ini tidak sering berubah, tetapi ada banyak sekali. Setiap foto diwakili oleh simbol sederhana: record

namespace CachingExamples.Memory;

public readonly record struct Photo(
    int AlbumId,
    int Id,
    string Title,
    string Url,
    string ThumbnailUrl);

Dalam contoh berikut, Anda akan melihat beberapa layanan yang didaftarkan dengan DI. Setiap layanan memiliki satu tanggung jawab.

using CachingExamples.Memory;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddMemoryCache();
builder.Services.AddHttpClient<CacheWorker>();
builder.Services.AddHostedService<CacheWorker>();
builder.Services.AddScoped<PhotoService>();
builder.Services.AddSingleton(typeof(CacheSignal<>));

using IHost host = builder.Build();

await host.StartAsync();

Dalam kode C# sebelumnya:

PhotoService bertanggung jawab untuk mendapatkan foto yang cocok dengan kriteria yang diberikan (atau filter):

using Microsoft.Extensions.Caching.Memory;

namespace CachingExamples.Memory;

public sealed class PhotoService(
        IMemoryCache cache,
        CacheSignal<Photo> cacheSignal,
        ILogger<PhotoService> logger)
{
    public async IAsyncEnumerable<Photo> GetPhotosAsync(Func<Photo, bool>? filter = default)
    {
        try
        {
            await cacheSignal.WaitAsync();

            Photo[] photos =
                (await cache.GetOrCreateAsync(
                    "Photos", _ =>
                    {
                        logger.LogWarning("This should never happen!");

                        return Task.FromResult(Array.Empty<Photo>());
                    }))!;

            // If no filter is provided, use a pass-thru.
            filter ??= _ => true;

            foreach (Photo photo in photos)
            {
                if (!default(Photo).Equals(photo) && filter(photo))
                {
                    yield return photo;
                }
            }
        }
        finally
        {
            cacheSignal.Release();
        }
    }
}

Dalam kode C# sebelumnya:

  • Konstruktor memerlukan IMemoryCache, , CacheSignal<Photo>dan ILogger.
  • Metode tersebut:GetPhotosAsync
    • Mendefinisikan parameter Func<Photo, bool> filter, dan mengembalikan IAsyncEnumerable<Photo>.
    • Memanggil dan menunggu _cacheSignal.WaitAsync() untuk melepaskan; ini memastikan bahwa cache diisi terlebih dahulu sebelum mengakses cache.
    • Secara asinkron memanggil _cache.GetOrCreateAsync(), mengambil semua foto di cache.
    • Argumen factory mencatat peringatan dan mengembalikan array foto kosong - ini seharusnya tidak pernah terjadi.
    • Setiap foto dalam cache diulang, difilter, dan diwujudkan dengan yield return.
    • Akhirnya, sinyal cache diatur ulang.

Pengguna layanan ini bebas untuk memanggil metode GetPhotosAsync, dan menangani foto secara sesuai. Tidak HttpClient diperlukan karena cache berisi foto.

Sinyal asinkron didasarkan pada instans SemaphoreSlim yang dienkapsulasi, dalam singleton dengan tipe generik yang dibatasi. CacheSignal<T> bergantung pada sebuah instance dari SemaphoreSlim.

namespace CachingExamples.Memory;

public sealed class CacheSignal<T>
{
    private readonly SemaphoreSlim _semaphore = new(1, 1);

    /// <summary>
    /// Exposes a <see cref="Task"/> that represents the asynchronous wait operation.
    /// When signaled (consumer calls <see cref="Release"/>), the 
    /// <see cref="Task.Status"/> is set as <see cref="TaskStatus.RanToCompletion"/>.
    /// </summary>
    public Task WaitAsync() => _semaphore.WaitAsync();

    /// <summary>
    /// Exposes the ability to signal the release of the <see cref="WaitAsync"/>'s operation.
    /// Callers who were waiting, will be able to continue.
    /// </summary>
    public void Release() => _semaphore.Release();
}

Dalam kode C# sebelumnya, pola dekorator digunakan untuk membungkus instans dari SemaphoreSlim. Karena CacheSignal<T> terdaftar sebagai singleton, CacheSignal<T> dapat digunakan di seluruh masa hidup layanan dengan tipe generik apa pun—dalam hal ini, . Ini bertanggung jawab untuk menandakan inisialisasi cache.

CacheWorker adalah subkelas dari BackgroundService:

using System.Net.Http.Json;
using Microsoft.Extensions.Caching.Memory;

namespace CachingExamples.Memory;

public sealed class CacheWorker(
    ILogger<CacheWorker> logger,
    HttpClient httpClient,
    CacheSignal<Photo> cacheSignal,
    IMemoryCache cache) : BackgroundService
{
    private readonly TimeSpan _updateInterval = TimeSpan.FromHours(3);

    private bool _isCacheInitialized = false;

    private const string Url = "https://jsonplaceholder.typicode.com/photos";

    public override async Task StartAsync(CancellationToken cancellationToken)
    {
        await cacheSignal.WaitAsync();
        await base.StartAsync(cancellationToken);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            logger.LogInformation("Updating cache.");

            try
            {
                Photo[]? photos =
                    await httpClient.GetFromJsonAsync<Photo[]>(
                        Url, stoppingToken);

                if (photos is { Length: > 0 })
                {
                    cache.Set("Photos", photos);
                    logger.LogInformation(
                        "Cache updated with {Count:#,#} photos.", photos.Length);
                }
                else
                {
                    logger.LogWarning(
                        "Unable to fetch photos to update cache.");
                }
            }
            finally
            {
                if (!_isCacheInitialized)
                {
                    cacheSignal.Release();
                    _isCacheInitialized = true;
                }
            }

            try
            {
                logger.LogInformation(
                    "Will attempt to update the cache in {Hours} hours from now.",
                    _updateInterval.Hours);

                await Task.Delay(_updateInterval, stoppingToken);
            }
            catch (OperationCanceledException)
            {
                logger.LogWarning("Cancellation acknowledged: shutting down.");
                break;
            }
        }
    }
}

Dalam kode C# sebelumnya:

  • Konstruktor memerlukan ILogger, , HttpClientdan IMemoryCache.
  • _updateInterval didefinisikan selama tiga jam.
  • Metode tersebut:ExecuteAsync
    • Perulangan ketika aplikasi sedang berjalan.
    • Membuat permintaan HTTP ke "https://jsonplaceholder.typicode.com/photos", dan memetakan respons sebagai array Photo objek.
    • Array foto ditempatkan di dalam IMemoryCache dengan kunci "Photos".
    • _cacheSignal.Release() berfungsi untuk melepaskan setiap konsumen yang menunggu sinyal.
    • Panggilan ke Task.Delay ditunggu, mengingat interval pembaruan.
    • Setelah tertunda selama tiga jam, cache kembali diperbarui.

Pengguna dalam proses yang berlangsung dapat meminta foto-foto dari IMemoryCache, tetapi CacheWorker bertanggung jawab untuk memperbarui cache.

Penembolokan hibrid

Perpustakaan HybridCache memadukan keuntungan dari caching dalam memori dan terdistribusi sambil mengatasi tantangan umum dengan API caching yang sudah ada. Diperkenalkan di .NET 9, HybridCache menyediakan API terpadu yang menyederhanakan implementasi caching dan mencakup fitur bawaan seperti perlindungan terhadap lonjakan akses dan serialisasi yang dapat dikonfigurasi.

Fitur utama

HybridCache menawarkan beberapa keuntungan daripada menggunakan IMemoryCache dan IDistributedCache secara terpisah:

  • Penembolokan dua tingkat: Secara otomatis mengelola lapisan cache dalam-memori (L1) dan cache terdistribusi (L2). Data diambil dari cache dalam memori terlebih dahulu untuk kecepatan, kemudian dari cache terdistribusi jika diperlukan, dan akhirnya dari sumbernya.
  • Perlindungan stempel: Mencegah beberapa permintaan bersamaan mengeksekusi operasi mahal yang sama. Hanya satu permintaan yang mengambil data sementara yang lain menunggu hasilnya.
  • Serialisasi yang dapat dikonfigurasi: Mendukung beberapa format serialisasi termasuk JSON (default), protobuf, dan XML.
  • Invalidasi berbasis tag: Mengelompokkan entri cache terkait dengan tag untuk pembatalan batch yang efisien.
  • API yang disederhanakan: Metode GetOrCreateAsync ini menangani cache yang terlewat, serialisasi, dan penyimpanan secara otomatis.

Kapan menggunakan HybridCache

Pertimbangkan untuk menggunakan HybridCache saat:

  • Anda memerlukan cache lokal (dalam memori) dan terdistribusi di lingkungan multi-server.
  • Anda ingin perlindungan terhadap skenario stampede cache.
  • Anda lebih suka API yang disederhanakan daripada mengoordinasikan IMemoryCache secara manual dan IDistributedCache.
  • Anda memerlukan pembatalan cache berbasis tag untuk entri terkait.

Petunjuk / Saran

Untuk aplikasi server tunggal dengan kebutuhan cache sederhana, cache dalam memori mungkin cukup. Untuk aplikasi multi-server tanpa perlu perlindungan stampede atau pembatalan berdasarkan tag, pertimbangkan cache terdistribusi.

Penyiapan HybridCache

Untuk menggunakan HybridCache, instal Microsoft.Extensions.Caching.Hybrid paket NuGet:

dotnet add package Microsoft.Extensions.Caching.Hybrid

Daftarkan layanan HybridCache dengan DI dengan memanggil AddHybridCache:

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHybridCache();

Kode sebelumnya mendaftar HybridCache dengan opsi default. Anda juga dapat mengonfigurasi opsi global:

var builderWithOptions = Host.CreateApplicationBuilder(args);
builderWithOptions.Services.AddHybridCache(options =>
{
    options.MaximumPayloadBytes = 1024 * 1024; // 1 MB
    options.MaximumKeyLength = 1024;
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(5),
        LocalCacheExpiration = TimeSpan.FromMinutes(2)
    };
});

Penggunaan dasar

Metode utama untuk berinteraksi dengan HybridCache adalah GetOrCreateAsync. Metode ini memeriksa cache untuk entri dengan kunci yang ditentukan dan, jika tidak ditemukan, memanggil metode pabrik untuk mengambil data:

async Task<WeatherData> GetWeatherDataAsync(HybridCache cache, string city)
{
    return await cache.GetOrCreateAsync(
        $"weather:{city}",
        async cancellationToken =>
        {
            // Simulate fetching from an external API
            await Task.Delay(100, cancellationToken);
            return new WeatherData(city, 72, "Sunny");
        }
    );
}

Dalam kode C# sebelumnya:

  • Metode ini GetOrCreateAsync mengambil kunci unik dan metode pabrik.
  • Jika data tidak ada di cache, metode pabrik dipanggil untuk mengambilnya.
  • Data secara otomatis disimpan dalam memori dan cache terdistribusi.
  • Hanya satu permintaan bersamaan yang mengeksekusi metode pabrik; yang lainnya menunggu hasilnya.

Opsi entri

Anda dapat mengambil alih default global untuk entri cache tertentu menggunakan HybridCacheEntryOptions:

async Task<WeatherData> GetWeatherWithOptionsAsync(HybridCache cache, string city)
{
    var entryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(10),
        LocalCacheExpiration = TimeSpan.FromMinutes(5)
    };

    return await cache.GetOrCreateAsync(
        $"weather:{city}",
        async cancellationToken => new WeatherData(city, 72, "Sunny"),
        entryOptions
    );
}

Opsi entri memungkinkan Anda untuk mengonfigurasi:

Invalidasi berbasis tag

Tag memungkinkan Anda mengelompokkan entri cache terkait dan membatalkannya bersama-sama. Ini berguna untuk skenario di mana data terkait perlu di-refresh sebagai unit:

async Task<CustomerData> GetCustomerAsync(HybridCache cache, int customerId)
{
    var tags = new[] { "customer", $"customer:{customerId}" };

    return await cache.GetOrCreateAsync(
        $"customer:{customerId}",
        async cancellationToken => new CustomerData(customerId, "John Doe", "john@example.com"),
        new HybridCacheEntryOptions { Expiration = TimeSpan.FromMinutes(30) },
        tags
    );
}

Untuk membatalkan semua entri dengan tag tertentu:

async Task InvalidateCustomerCacheAsync(HybridCache cache, int customerId)
{
    await cache.RemoveByTagAsync($"customer:{customerId}");
}

Anda juga dapat membatalkan beberapa tag sekaligus:

async Task InvalidateAllCustomersAsync(HybridCache cache)
{
    await cache.RemoveByTagAsync(new[] { "customer", "orders" });
}

Nota

Pembatalan berbasis tag adalah operasi logis. Ini tidak secara aktif menghapus nilai dari cache tetapi memastikan bahwa entri yang ditandai diperlakukan sebagai "cache miss". Entri akhirnya kedaluwarsa berdasarkan masa pakai yang dikonfigurasi.

Menghapus entri cache

Untuk menghapus entri cache tertentu menurut kunci, gunakan RemoveAsync metode :

async Task RemoveWeatherDataAsync(HybridCache cache, string city)
{
    await cache.RemoveAsync($"weather:{city}");
}

Untuk membatalkan semua entri yang di-cache, gunakan tag "*" kartubebas yang dipesan:

async Task InvalidateAllCacheAsync(HybridCache cache)
{
    await cache.RemoveByTagAsync("*");
}

Serialization

Untuk skenario penyimpanan sementara terdistribusi, HybridCache memerlukan serialisasi. Secara default, ia menangani string dan byte[] secara internal dan menggunakan System.Text.Json untuk jenis lain. Anda dapat mengonfigurasi serializer kustom untuk jenis tertentu atau menggunakan serializer tujuan umum:

// Custom serialization example
// Note: This requires implementing a custom IHybridCacheSerializer<T>
var builderWithSerializer = Host.CreateApplicationBuilder(args);
builderWithSerializer.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(10),
        LocalCacheExpiration = TimeSpan.FromMinutes(5)
    };
});
// To add a custom serializer, uncomment and provide your implementation:
// .AddSerializer<WeatherData, CustomWeatherDataSerializer>();

Mengonfigurasi cache terdistribusi

HybridCache menggunakan implementasi yang dikonfigurasi IDistributedCache untuk cache terdistribusi (L2). Meskipun IDistributedCache tidak dikonfigurasi, HybridCache masih memberikan cache dalam memori dan perlindungan dari lonjakan. Untuk menambahkan Redis sebagai cache terdistribusi:

// Distributed cache with Redis
var builderWithRedis = Host.CreateApplicationBuilder(args);
builderWithRedis.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
});
builderWithRedis.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(30),
        LocalCacheExpiration = TimeSpan.FromMinutes(5)
    };
});

Untuk informasi selengkapnya tentang implementasi cache terdistribusi, lihat Penembolokan terdistribusi.

Cache terdistribusi

Dalam beberapa skenario, cache terdistribusi diperlukan—demikian halnya dengan beberapa server aplikasi. Cache terdistribusi mendukung skala yang lebih besar daripada pendekatan cache dalam memori. Menggunakan cache terdistribusi memindahkan memori cache ke proses eksternal atau server terpisah, tetapi memerlukan input/output jaringan tambahan dan menambah sedikit latensi, meskipun nominal.

Abstraksi cache terdistribusi merupakan bagian dari paket Microsoft.Extensions.Caching.Memory NuGet, dan bahkan terdapat metode ekstensi AddDistributedMemoryCache.

Perhatian

AddDistributedMemoryCache hanya boleh digunakan dalam skenario pengembangan atau pengujian dan bukan implementasi produksi yang layak.

Pertimbangkan salah satu implementasi yang IDistributedCache tersedia dari paket berikut:

API pencache-an terdistribusi

API caching terdistribusi sedikit lebih primitif dibandingkan dengan API caching dalam memori. Pasangan kunci-nilai sedikit lebih mendasar. Kunci cache dalam memori didasarkan pada object, sedangkan kunci yang didistribusikan adalah string. Dengan cache dalam memori, nilainya dapat berupa generik bertipe kuat, sedangkan nilai dalam cache terdistribusi dipertahankan sebagai byte[]. Bukan berarti bahwa berbagai implementasi tidak mengekspos nilai generik berjenis tipe, tetapi itu adalah rincian implementasi.

Membuat nilai

Untuk membuat nilai dalam cache terdistribusi, panggil salah satu API yang ditetapkan:

AlphabetLetter Dengan menggunakan rekaman dari contoh cache dalam memori, Anda dapat membuat serialisasi objek ke JSON lalu mengodekan string sebagai byte[]:

DistributedCacheEntryOptions options = new()
{
    AbsoluteExpirationRelativeToNow =
        TimeSpan.FromMilliseconds(MillisecondsAbsoluteExpiration)
};

AlphabetLetter alphabetLetter = new(letter);
string json = JsonSerializer.Serialize(alphabetLetter);
byte[] bytes = Encoding.UTF8.GetBytes(json);

await cache.SetAsync(letter.ToString(), bytes, options);

Sama seperti penyimpanan sementara dalam memori, entri cache dapat memiliki opsi untuk menyempurnakan eksistensinya di cache—dalam hal ini, DistributedCacheEntryOptions.

Membuat metode ekstensi

Ada beberapa metode ekstensi berbasis kenyamanan untuk membuat nilai. Metode ini membantu menghindari pengodean string representasi objek ke dalam byte[]:

Membaca nilai

Untuk membaca nilai dari cache terdistribusi, panggil salah satu API di bawah ini: Get

AlphabetLetter? alphabetLetter = null;
byte[]? bytes = await cache.GetAsync(letter.ToString());
if (bytes is { Length: > 0 })
{
    string json = Encoding.UTF8.GetString(bytes);
    alphabetLetter = JsonSerializer.Deserialize<AlphabetLetter>(json);
}

Setelah entri cache dibaca dari cache, Anda dapat memperoleh representasi string yang dikodekan dalam format UTF8 dari byte[].

Memahami metode ekstensi

Terdapat beberapa metode ekstensi berbasis kemudahan untuk membaca nilai. Metode ini membantu menghindari decoding byte[] menjadi string representasi objek:

Memperbarui nilai

Tidak ada cara untuk memperbarui nilai dalam cache terdistribusi dengan satu panggilan API. Sebagai gantinya, nilai dapat mengatur ulang kedaluwarsa gesernya dengan salah satu API refresh:

Jika nilai aktual perlu diperbarui, Anda harus menghapus nilai lalu menambahkannya kembali.

Hapus nilai

Untuk menghapus nilai dalam cache terdistribusi, panggil salah satu API dari Remove.

Petunjuk / Saran

Meskipun ada versi sinkron dari API ini, pertimbangkan fakta bahwa implementasi cache terdistribusi bergantung pada I/O jaringan. Untuk alasan ini, biasanya lebih baik menggunakan API asinkron.

Lihat juga