Wzorzec z odkładaniem do pamięci podręcznej

Azure Cache for Redis

Załaduj dane na żądanie do pamięci podręcznej z magazynu danych. Może to poprawić wydajność oraz pomóc w utrzymaniu spójności między danymi w pamięci podręcznej i danymi w podstawowym magazynie danych.

Kontekst i problem

Aplikacje używają pamięci podręcznej, aby poprawić powtarzalny dostęp do informacji przechowywanych w magazynie danych. Jednak niepraktycznie jest oczekiwać, że dane w pamięci podręcznej będą zawsze całkowicie spójne z danymi w magazynie danych. Aplikacje powinny implementować strategię, dzięki której będą upewniać się, że dane w pamięci podręcznej są możliwie aktualne, ale mogą też wykrywać i obsługiwać sytuacje, w których dane w pamięci podręcznej są przestarzałe.

Rozwiązanie

Wiele komercyjnych systemów buforowania zapewnia operacje odczytu read-through oraz zapisu write-through/write-behind. W tych systemach aplikacja pobiera dane poprzez odwołanie do pamięci podręcznej. Jeśli dane nie znajdują się w pamięci podręcznej, zostaną pobrane z magazynu danych i dodane do pamięci podręcznej. Również wszelkie modyfikacje danych przechowywanych w pamięci podręcznej są automatycznie zapisywane z powrotem do magazynu danych.

W przypadku pamięci podręcznych, które nie zapewniają tej funkcji, odpowiedzialność za utrzymanie danych spada na aplikacje używające pamięci podręcznej.

Aplikacja może emulować funkcję buforowania read-through poprzez wdrożenie strategii odkładania do pamięci podręcznej. Ta strategia ładuje dane do pamięci podręcznej na żądanie. Na rysunku przedstawiono użycie wzorca z odkładaniem do pamięci podręcznej w celu przechowywania danych w pamięci podręcznej.

Użycie wzorca z odkładaniem do pamięci podręcznej w celu przechowywania danych w pamięci podręcznej

Jeśli aplikacja aktualizuje informacje, może stosować strategię zapisu write-through, wprowadzając modyfikację w magazynie danych oraz unieważniając odpowiadający element w pamięci podręcznej.

Jeśli element będzie później wymagany, użycie strategi z odkładaniem do pamięci podręcznej spowoduje pobranie zaktualizowanych danych z magazynu danych i ponowne ich dodanie do pamięci podręcznej.

Problemy i kwestie do rozważenia

Podczas podejmowania decyzji o sposobie wdrożenia tego wzorca należy rozważyć następujące punkty:

Okres istnienia danych w pamięci podręcznej. Wiele pamięci podręcznych implementuje zasady wygasania, które unieważniają dane i usuwają je z pamięci podręcznej, jeśli nie uzyska się do nich dostępu w określonym przedziale czasowym. Aby strategia z odkładaniem do pamięci podręcznej była efektywna, należy upewnić się, że zasady wygasania pasują do wzorca dostępu aplikacji używających danych. Nie należy ustawiać zbyt krótkiego czasu wygasania, ponieważ może to doprowadzić do sytuacji, w której aplikacje będą ciągle pobierać dane z magazynu danych i dodawać je do pamięci podręcznej. Podobnie nie należy ustawiać zbyt długiego czasu wygasania — wtedy dane w pamięci podręcznej prawdopodobnie będą przestarzałe. Należy pamiętać, że buforowanie jest najbardziej efektywne wobec względnie statycznych danych lub danych, które są często odczytywane.

Eksmisja danych. Większość pamięci podręcznych ma ograniczony rozmiar w porównaniu do magazynu danych zawierającego oryginalne dane i będzie eksmitować dane w razie potrzeby. Większość pamięci podręcznych stosuje zasady najdawniej używanych w przypadku elementów do eksmitowania, ale można to zmienić. Skonfiguruj globalne właściwości wygasania oraz inne właściwości pamięci podręcznej, a także właściwości wygasania wszystkich elementów pamięci podręcznej, aby upewnić się, że pamięć podręczna jest ekonomiczna. Nie zawsze stosowanie globalnych zasad eksmisji wobec każdego elementu w pamięci podręcznej jest odpowiednie. Na przykład jeśli element pamięci podręcznej jest bardzo kosztowny w przypadku pobierania z magazynu danych, korzystne może być przechowywanie tego elementu w pamięci podręcznej kosztem elementów, do których dostęp uzyskuje się częściej, ale też które są mniej kosztowne.

Zalewanie pamięci podręcznej. Wiele rozwiązań wstępnie wypełnia pamięć podręczną przy użyciu danych, których aplikacja prawdopodobnie będzie potrzebować w ramach przetwarzania uruchomienia aplikacji. Wzorzec odkładania do pamięci podręcznej może nadal być użyteczny, jeśli niektóre z tych danych wygasną lub zostaną eksmitowane.

Spójność. Implementacja wzorca z odkładaniem do pamięci podręcznej nie gwarantuje spójności między magazynem danych i pamięcią podręczną. Element w magazynie danych może zostać zmieniony w dowolnym momencie przez proces zewnętrzny, a ta zmiana może nie zostać uwzględniona w pamięci podręcznej do następnego załadowania elementu. W systemie replikującym dane w magazynach danych ten problem może być poważny, jeśli synchronizacja występuje często.

Buforowanie lokalne (w pamięci). Pamięć podręczna może być lokalna dla wystąpienia aplikacji i przechowywana w pamięci. Strategia odkładania do pamięci podręcznej może być użyteczna w tym środowisku, jeśli aplikacja wielokrotnie uzyskuje dostęp do tych samych danych. Jednak lokalna pamięć podręczna jest prywatna, więc różne wystąpienia aplikacji mogą mieć kopię tych samych danych w pamięci podręcznej. Te dane mogą szybko utracić spójność pomiędzy pamięciami podręcznymi, więc może być konieczne częstsze wygaszanie danych przechowywanych w prywatnej pamięci podręcznej i ich odświeżanie. W tych scenariuszach należy rozważyć zbadanie użycia udostępnionego lub rozproszonego mechanizmu buforowania.

Kiedy używać tego wzorca

Użyj tego wzorca, gdy:

  • Pamięć podręczna nie zapewnia natywnych operacji read-through i write-through.
  • Żądanie zasobu jest nieprzewidywalne. Ten wzorzec umożliwia aplikacjom ładowanie danych na żądanie. Nie tworzy z wyprzedzeniem założeń dotyczących danych, których aplikacje będą potrzebować.

Ten wzorzec może nie być przydatny w następujących sytuacjach:

  • Gdy zestaw danych w pamięci podręcznej jest statyczny. Jeśli dane zmieszczą się w dostępnej przestrzeni pamięci podręcznej, wypełnij pamięć podręczną danymi podczas uruchamiania i zastosuj zasady, które ochroną dane przed wygaśnięciem.
  • W przypadku buforowania informacji o stanie sesji w aplikacji internetowej hostowanej na farmie internetowej. W tym środowisku należy unikać wprowadzania zależności opartych na koligacji klient-serwer.

Projekt obciążenia

Architekt powinien ocenić, w jaki sposób wzorzec odkładania do pamięci podręcznej może być używany w projekcie obciążenia, aby sprostać celom i zasadom opisanym w filarach platformy Azure Well-Architected Framework. Na przykład:

Filar Jak ten wzorzec obsługuje cele filaru
Decyzje projektowe dotyczące niezawodności pomagają obciążeniu stać się odporne na awarię i zapewnić, że zostanie przywrócony do w pełni funkcjonalnego stanu po wystąpieniu awarii. Buforowanie tworzy replikację danych i, w ograniczony sposób, można użyć do zachowania dostępności często używanych danych, jeśli magazyn danych pochodzenia jest tymczasowo niedostępny. Ponadto w przypadku awarii pamięci podręcznej obciążenie może wrócić do magazynu danych pochodzenia.

- Nadmiarowość RE:05
Wydajność pomagawydajnie sprostać zapotrzebowaniu dzięki optymalizacjom skalowania, danych, kodu. Lepszą wydajność obciążenia można uzyskać podczas używania pamięci podręcznej do odczytu danych, które nie zmieniają się często, a obciążenie jest przeznaczone do tolerowania pewnej ilości nieaktualności.

- PE:08 Wydajność danych
- PE:12 Ciągła optymalizacja wydajności

Podobnie jak w przypadku każdej decyzji projektowej, należy rozważyć wszelkie kompromisy w stosunku do celów innych filarów, które mogą zostać wprowadzone przy użyciu tego wzorca.

Przykład

Na platformie Microsoft Azure możesz użyć usługi Azure Cache for Redis, aby utworzyć rozproszoną pamięć podręczną, która może być współużytkowana przez wiele wystąpień aplikacji.

W poniższym przykładzie kodu użyto klienta StackExchange.Redis, który jest biblioteką klienta Redis napisaną dla platformy .NET. Aby nawiązać połączenie z wystąpieniem usługi Azure Cache for Redis, wywołaj metodę statyczną ConnectionMultiplexer.Connect i przekaż parametry połączenia. Metoda zwraca klasę ConnectionMultiplexer reprezentującą połączenie. Jednym z rozwiązań w zakresie udostępniania wystąpienia klasy ConnectionMultiplexer w aplikacji jest korzystanie z właściwości statycznej, która zwraca połączone wystąpienie podobnie jak w poniższym przykładzie. To podejście oferuje bezpieczny wątkowo sposób na inicjowanie tylko jednego połączonego wystąpienia.

private static ConnectionMultiplexer Connection;

// Redis connection string information
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
    string cacheConnection = ConfigurationManager.AppSettings["CacheConnection"].ToString();
    return ConnectionMultiplexer.Connect(cacheConnection);
});

public static ConnectionMultiplexer Connection => lazyConnection.Value;

Metoda GetMyEntityAsync w poniższym przykładzie kodu przedstawia implementację wzorca z odkładania do pamięci podręcznej. Ta metoda pobiera obiekt z pamięci podręcznej przy użyciu podejścia odczytu.

Obiekt jest identyfikowany przy użyciu identyfikatora w formie liczby całkowitej jako klucza. Metoda GetMyEntityAsync próbuje pobrać element z tym kluczem z pamięci podręcznej. Jeśli zostanie znaleziony pasujący element, zostanie on zwrócony. Jeśli nie ma pasującego elementu w pamięci podręcznej, metoda GetMyEntityAsync pobierze obiekt z magazynu danych, doda go do pamięci podręcznej, a następnie go zwróci. Kod, który faktycznie odczytuje dane z magazynu danych, nie został tutaj pokazany, ponieważ jest zależny od magazynu danych. Należy pamiętać, że element pamięci podręcznej jest skonfigurowany na wygaśnięcie, aby zapobiegać sytuacji, w której będzie przestarzały, jeśli zostanie zaktualizowany w innym miejscu.

// Set five minute expiration as a default
private const double DefaultExpirationTimeInMinutes = 5.0;

public async Task<MyEntity> GetMyEntityAsync(int id)
{
  // Define a unique key for this method and its parameters.
  var key = $"MyEntity:{id}";
  var cache = Connection.GetDatabase();

  // Try to get the entity from the cache.
  var json = await cache.StringGetAsync(key).ConfigureAwait(false);
  var value = string.IsNullOrWhiteSpace(json)
                ? default(MyEntity)
                : JsonConvert.DeserializeObject<MyEntity>(json);

  if (value == null) // Cache miss
  {
    // If there's a cache miss, get the entity from the original store and cache it.
    // Code has been omitted because it is data store dependent.
    value = ...;

    // Avoid caching a null value.
    if (value != null)
    {
      // Put the item in the cache with a custom expiration time that
      // depends on how critical it is to have stale data.
      await cache.StringSetAsync(key, JsonConvert.SerializeObject(value)).ConfigureAwait(false);
      await cache.KeyExpireAsync(key, TimeSpan.FromMinutes(DefaultExpirationTimeInMinutes)).ConfigureAwait(false);
    }
  }

  return value;
}

W przykładach użyto usługi Azure Cache for Redis, aby uzyskać dostęp do magazynu i pobrać informacje z pamięci podręcznej. Aby uzyskać więcej informacji, zobacz Using Azure Cache for Redis and How to create a Web App with Azure Cache for Redis (Korzystanie z usługi Azure Cache for Redis) i How to create a Web App with Azure Cache for Redis (Jak utworzyć aplikację internetową za pomocą usługi Azure Cache for Redis).

Metoda UpdateEntityAsync przedstawiona poniżej demonstruje sposób unieważniania obiektu w pamięci podręcznej, kiedy wartość zostanie zmieniona przez aplikację. Kod aktualizuje oryginalny magazyn danych, a następnie usuwa element pamięci podręcznej z pamięci podręcznej.

public async Task UpdateEntityAsync(MyEntity entity)
{
    // Update the object in the original data store.
    await this.store.UpdateEntityAsync(entity).ConfigureAwait(false);

    // Invalidate the current cache object.
    var cache = Connection.GetDatabase();
    var id = entity.Id;
    var key = $"MyEntity:{id}"; // The key for the cached object.
    await cache.KeyDeleteAsync(key).ConfigureAwait(false); // Delete this key from the cache.
}

Uwaga

Kolejność kroków jest ważna. Zaktualizuj magazyn danych przed usunięciem elementu z pamięci podręcznej. Jeśli najpierw usuniesz element pamięci podręcznej, zaistnieje niewielki przedział czasu, w którym klient może pobrać element przed zaktualizowaniem magazynu danych. Spowoduje to chybienie pamięci podręcznej (ponieważ element został usunięty z pamięci podręcznej), co z kolei doprowadzi do pobrania wcześniejszej wersji elementu z magazynu danych i dodania jej z powrotem do pamięci podręcznej. W rezultacie otrzymamy przestarzałe dane w pamięci podręcznej.

Podczas implementowania tego wzorca mogą być istotne następujące informacje:

  • Wzorzec niezawodnej aplikacji internetowej pokazuje, jak zastosować wzorzec z odkładaniem do pamięci podręcznej do aplikacji internetowych zbieżnych w chmurze.

  • Wskazówki dotyczące buforowania. Zawierają dodatkowe informacje dotyczące sposobu buforowania danych w rozwiązaniu w chmurze oraz problemów, które należy rozważyć podczas implementowania pamięci podręcznej.

  • Podstawy spójności danych. Aplikacje w chmurze zazwyczaj używają danych rozproszonych w różnych magazynach danych. Zarządzanie spójnością danych i jej utrzymywanie w tym środowisku jest krytycznym aspektem systemu, zwłaszcza w obliczu problemów z współbieżnością i dostępnością, które mogą wystąpić. Te podstawy opisują problemy związane ze spójnością rozproszonych danych oraz podsumowują sposób wdrażania spójności ostatecznej przez aplikację w celu zapewnienia dostępności danych.