Buforowanie na platformie .NET

W tym artykule poznasz różne mechanizmy buforowania. Buforowanie jest działaniem przechowywania danych w warstwie pośredniej, dzięki czemu kolejne pobieranie danych jest szybsze. Koncepcyjnie buforowanie jest strategią optymalizacji wydajności i zagadnieniami dotyczącymi projektowania. Buforowanie może znacząco poprawić wydajność aplikacji, wprowadzając często zmieniające (lub kosztowne) dane bardziej łatwo dostępne. W tym artykule przedstawiono dwa podstawowe typy buforowania i przedstawiono przykładowy kod źródłowy dla obu typów:

Ważne

Istnieją dwie MemoryCache klasy w obrębie platformy .NET— jedna w System.Runtime.Caching przestrzeni nazw, a druga w Microsoft.Extensions.Caching przestrzeni nazw:

Chociaż ten artykuł koncentruje się na buforowaniu, nie zawiera System.Runtime.Caching pakietu NuGet. Wszystkie odwołania do MemoryCache programu znajdują się w Microsoft.Extensions.Caching przestrzeni nazw.

Microsoft.Extensions.* Wszystkie pakiety są gotowe do wstrzykiwania zależności (DI), zarówno IMemoryCache interfejsy, jak i IDistributedCache mogą być używane jako usługi.

Buforowanie w pamięci

W tej sekcji dowiesz się więcej o pliku Microsoft.Extensions.Buforowanie. Pakiet pamięci. Bieżąca implementacja elementu IMemoryCache to otoka interfejsu ConcurrentDictionary<TKey,TValue>API, która udostępnia bogaty w funkcje interfejs API. Wpisy w pamięci podręcznej są reprezentowane przez element ICacheEntryi mogą być dowolnymi objectelementami . Rozwiązanie pamięci podręcznej w pamięci jest doskonałe dla aplikacji uruchamianych na jednym serwerze, gdzie wszystkie buforowane dane wynajmują pamięć w procesie aplikacji.

Napiwek

W przypadku scenariuszy buforowania z wieloma serwerami należy rozważyć podejście buforowania rozproszonego jako alternatywę dla buforowania w pamięci.

Interfejs API buforowania w pamięci

Użytkownik pamięci podręcznej ma kontrolę zarówno nad przesuwanymi, jak i bezwzględnymi wygasaniami:

Ustawienie wygaśnięcia spowoduje eksmitowanie wpisów w pamięci podręcznej, jeśli nie są one dostępne w ramach przydziału czasu wygaśnięcia. Użytkownicy mają dodatkowe opcje kontrolowania wpisów pamięci podręcznej za pośrednictwem elementu MemoryCacheEntryOptions. Każda ICacheEntry z nich jest sparowana, z MemoryCacheEntryOptions którą uwidacznia funkcję eksmisji wygasania z ustawieniami IChangeTokenpriorytetu za pomocą CacheItemPrioritypolecenia i kontrolując element ICacheEntry.Size. Rozważ następujące metody rozszerzenia:

Przykład pamięci podręcznej w pamięci

Aby użyć implementacji domyślnej IMemoryCache , wywołaj metodę AddMemoryCache rozszerzenia, aby zarejestrować wszystkie wymagane usługi w usłudze DI. W poniższym przykładzie kodu host ogólny jest używany do uwidaczniania funkcji 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();

W zależności od obciążenia platformy .NET można uzyskać dostęp do różnych IMemoryCache elementów, takich jak iniekcja konstruktora. W tym przykładzie używasz IServiceProvider wystąpienia w metodzie host i wywołaj metodę rozszerzenia ogólnego GetRequiredService<T>(IServiceProvider) :

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

Dzięki zarejestrowaniu usług buforowania w pamięci i rozwiązaniu za pomocą di możesz rozpocząć buforowanie. Ten przykład iteruje litery alfabetu angielskiego "A" do "Z". Typ record AlphabetLetter przechowuje odwołanie do litery i generuje komunikat.

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

Napiwek

Modyfikator file dostępu jest używany w typieAlphabetLetter, ponieważ jest on zdefiniowany w pliku Program.cs i uzyskiwany tylko do niego. Aby uzyskać więcej informacji, zobacz plik (odwołanie w C#). Aby wyświetlić pełny kod źródłowy, zobacz sekcję Program.cs .

Przykład zawiera funkcję pomocnika, która iteruje litery alfabetu:

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

    Console.WriteLine();
}

W poprzednim kodzie języka C#:

  • Element Func<char, Task> asyncFunc jest oczekiwany dla każdej iteracji, przekazując bieżący letterelement .
  • Po przetworzeniu wszystkich liter do konsoli jest zapisywany pusty wiersz.

Aby dodać elementy do pamięci podręcznej, wywołaj jedną z Createinterfejsów API lub 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;

W poprzednim kodzie języka C#:

  • Delegaty zmiennej addLettersToCacheTask do IterateAlphabetAsync i są oczekiwane.
  • Argumentem Func<char, Task> asyncFunc jest lambda.
  • Element MemoryCacheEntryOptions jest tworzone przy użyciu bezwzględnego wygaśnięcia względem teraz.
  • Wywołanie zwrotne po eksmisji jest zarejestrowane.
  • Obiekt AlphabetLetter jest tworzone i przekazywany razem Set z elementami letter i options.
  • Litera jest zapisywana w konsoli jako buforowana.
  • Task.Delay Na koniec zostanie zwrócony element .

Dla każdej litery alfabetu wpis pamięci podręcznej jest zapisywany z wygaśnięciem i ogłasza wywołanie zwrotne eksmisji.

Wywołanie zwrotne po eksmisji zapisuje szczegóły wartości eksmitowanej w konsoli:

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

Teraz, gdy pamięć podręczna zostanie wypełniona, zostanie wyświetlone kolejne wywołanie IterateAlphabetAsync metody , ale tym razem wywołasz metodę 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;

Jeśli element cache zawiera letter klucz, a value element jest wystąpieniem AlphabetLetter zapisywanym w konsoli programu . letter Gdy klucz nie znajduje się w pamięci podręcznej, został wykluczony i wywołano wywołanie zwrotne po eksmisji.

Dodatkowe metody rozszerzenia

Zestaw IMemoryCache zawiera wiele metod rozszerzeń opartych na wygodzie, w tym asynchroniczną GetOrCreateAsyncmetodę :

Zebranie wszystkich elementów

Cały przykładowy kod źródłowy aplikacji jest programem najwyższego poziomu i wymaga dwóch pakietów 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.";
}

Możesz dostosować MillisecondsDelayAfterAdd wartości i MillisecondsAbsoluteExpiration , aby obserwować zmiany zachowania w wygaśnięciu i eksmisji buforowanych wpisów. Poniżej przedstawiono przykładowe dane wyjściowe z uruchamiania tego kodu. Ze względu na niedeterministyczny charakter zdarzeń platformy .NET dane wyjściowe mogą się różnić.

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.

Ponieważ bezwzględne wygaśnięcie (MemoryCacheEntryOptions.AbsoluteExpirationRelativeToNow) jest ustawione, wszystkie buforowane elementy zostaną ostatecznie wykluczone.

Buforowanie usługi roboczej

Jedną z typowych strategii buforowania danych jest aktualizowanie pamięci podręcznej niezależnie od usług danych zużywających dane. Szablon usługi procesu roboczego jest doskonałym przykładem, ponieważ BackgroundService działa niezależnie (lub w tle) z innego kodu aplikacji. Po uruchomieniu aplikacji, która hostuje implementację IHostedService, odpowiednia implementacja (w tym przypadku BackgroundService proces roboczy lub "proces roboczy") rozpoczyna działanie w tym samym procesie. Te hostowane usługi są rejestrowane w usłudze DI jako singletons za pośrednictwem AddHostedService<THostedService>(IServiceCollection) metody rozszerzenia. Inne usługi można zarejestrować w usłudze DI z dowolnym okresem istnienia usługi.

Ważne

Okres istnienia usługi jest bardzo ważny, aby zrozumieć. Po wywołaniu AddMemoryCache metody rejestrowania wszystkich usług buforowania w pamięci usługi są rejestrowane jako pojedyncze.

Scenariusz usługi fotograficznej

Wyobraź sobie, że tworzysz usługę zdjęć, która korzysta z interfejsu API innej firmy dostępnego za pośrednictwem protokołu HTTP. Te dane fotograficzne nie zmieniają się bardzo często, ale jest ich wiele. Każde zdjęcie jest reprezentowane przez proste record:

namespace CachingExamples.Memory;

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

W poniższym przykładzie zobaczysz kilka usług zarejestrowanych w usłudze DI. Każda usługa ma jedną odpowiedzialność.

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

W poprzednim kodzie języka C#:

Jest PhotoService odpowiedzialny za pobieranie zdjęć spełniających podane kryteria (lub 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();
        }
    }
}

W poprzednim kodzie języka C#:

  • Konstruktor wymaga IMemoryCache, CacheSignal<Photo>i ILogger.
  • Metoda GetPhotosAsync :
    • Func<Photo, bool> filter Definiuje parametr i zwraca wartość IAsyncEnumerable<Photo>.
    • Wywołuje i czeka na _cacheSignal.WaitAsync() wydanie, dzięki czemu pamięć podręczna zostanie wypełniona przed uzyskaniem dostępu do pamięci podręcznej.
    • Wywołuje _cache.GetOrCreateAsync()metodę , asynchronicznie uzyskując wszystkie zdjęcia w pamięci podręcznej.
    • factory Argument rejestruje ostrzeżenie i zwraca pustą tablicę zdjęć — nigdy nie powinno się to zdarzyć.
    • Każde zdjęcie w pamięci podręcznej jest iterowane, filtrowane i zmaterializowane za pomocą elementu yield return.
    • Na koniec sygnał pamięci podręcznej zostanie zresetowany.

Konsumenci tej usługi mogą wywoływać GetPhotosAsync metodę i odpowiednio obsługiwać zdjęcia. Nie HttpClient jest wymagane, ponieważ pamięć podręczna zawiera zdjęcia.

Sygnał asynchroniczny jest oparty na hermetyzowanym SemaphoreSlim wystąpieniu w ramach pojedynczego pojedynczego typu ogólnego. Obiekt CacheSignal<T> opiera się na wystąpieniu klasy 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();
}

W poprzednim kodzie języka C# wzorzec dekoratora służy do zawijania wystąpienia klasy SemaphoreSlim. Ponieważ element CacheSignal<T> jest zarejestrowany jako pojedynczy, może być używany we wszystkich okresach istnienia usługi z dowolnym typem ogólnym — w tym przypadku Photo. Jest on odpowiedzialny za sygnalizowanie rozmieszczania pamięci podręcznej.

Jest CacheWorker to podklasa :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;
            }
        }
    }
}

W poprzednim kodzie języka C#:

  • Konstruktor wymaga ILogger, HttpClienti IMemoryCache.
  • Element _updateInterval jest definiowany przez trzy godziny.
  • Metoda ExecuteAsync :
    • Pętle podczas działania aplikacji.
    • Wysyła żądanie HTTP do "https://jsonplaceholder.typicode.com/photos"elementu i mapuje odpowiedź jako tablicę Photo obiektów.
    • Tablica zdjęć jest umieszczana w IMemoryCache pod kluczem "Photos" .
    • Nazywa _cacheSignal.Release() się, uwalniając wszystkich konsumentów, którzy czekali na sygnał.
    • Wywołanie metody Task.Delay jest oczekiwane, biorąc pod uwagę interwał aktualizacji.
    • Po opóźnieniu przez trzy godziny pamięć podręczna zostanie ponownie zaktualizowana.

Konsumenci w tym samym procesie mogą poprosić IMemoryCache o zdjęcia, ale CacheWorker jest odpowiedzialny za aktualizowanie pamięci podręcznej.

Rozproszone buforowanie

W niektórych scenariuszach wymagana jest rozproszona pamięć podręczna — na przykład wiele serwerów aplikacji. Rozproszona pamięć podręczna obsługuje wyższe skalowanie w poziomie niż podejście buforowania w pamięci. Użycie rozproszonej pamięci podręcznej odciąża pamięć podręczną do procesu zewnętrznego, ale wymaga dodatkowych operacji we/wy sieci i wprowadza nieco większe opóźnienie (nawet jeśli nominalne).

Rozproszone abstrakcji buforowania są częścią Microsoft.Extensions.Caching.Memory pakietu NuGet i istnieje nawet AddDistributedMemoryCache metoda rozszerzenia.

Uwaga

Element AddDistributedMemoryCache powinien być używany tylko w scenariuszach tworzenia i/lub testowania i nie jest opłacalną implementacją produkcyjną.

Weź pod uwagę dowolną z dostępnych implementacji z IDistributedCache następujących pakietów:

Interfejs API rozproszonego buforowania

Interfejsy API buforowania rozproszonego są nieco bardziej pierwotne niż ich odpowiedniki interfejsu API buforowania w pamięci. Pary klucz-wartość są nieco bardziej podstawowe. Klucze buforowania w pamięci są oparte na objectobiekcie , natomiast klucze rozproszone to string. W przypadku buforowania w pamięci wartość może być dowolną silnie typizowaną ogólną wartością, natomiast wartości w buforowaniu rozproszonym są utrwalane jako byte[]. Nie oznacza to, że różne implementacje nie uwidaczniają silnie typizowane wartości ogólne, ale byłoby to szczegóły implementacji.

Tworzenie wartości

Aby utworzyć wartości w rozproszonej pamięci podręcznej, wywołaj jeden z zestawów interfejsów API:

Korzystając z rekordu AlphabetLetter z przykładu w pamięci podręcznej, można serializować obiekt w formacie JSON, a następnie kodować string jako :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);

Podobnie jak buforowanie w pamięci, wpisy pamięci podręcznej mogą mieć opcje ułatwiające dostosowanie ich istnienia w pamięci podręcznej — w tym przypadku DistributedCacheEntryOptions.

Tworzenie metod rozszerzenia

Istnieje kilka wygodnych metod rozszerzenia do tworzenia wartości, które pomagają uniknąć kodowania string reprezentacji obiektów w obiekcie byte[]:

Odczytywanie wartości

Aby odczytać wartości z rozproszonej pamięci podręcznej, wywołaj jeden z interfejsów API pobierania:

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

Gdy wpis pamięci podręcznej zostanie odczytany z pamięci podręcznej, możesz uzyskać reprezentację zakodowaną string w formacie UTF8 z pliku byte[]

Odczytywanie metod rozszerzenia

Istnieje kilka wygodnych metod rozszerzeń do odczytywania wartości, które pomagają uniknąć dekodowania byte[] w string reprezentacjach obiektów:

Aktualizowanie wartości

Nie ma możliwości zaktualizowania wartości w rozproszonej pamięci podręcznej za pomocą pojedynczego wywołania interfejsu API, zamiast tego wartości mogą mieć ich przesuwane wygasania resetowania przy użyciu jednego z interfejsów API odświeżania:

Jeśli wartość rzeczywista musi zostać zaktualizowana, musisz usunąć wartość, a następnie ponownie ją dodać.

Usuwanie wartości

Aby usunąć wartości w rozproszonej pamięci podręcznej, wywołaj jeden z usuniętych interfejsów API:

Napiwek

Chociaż istnieją synchroniczne wersje wyżej wymienionych interfejsów API, należy wziąć pod uwagę fakt, że implementacje rozproszonych pamięci podręcznych są zależne od operacji we/wy sieci. Z tego powodu preferowane jest częściej niż nie używanie asynchronicznych interfejsów API.

Zobacz też