Wskazówki dotyczące buforowania

Azure Cache for Redis

Buforowanie jest typową techniką, której celem jest zwiększanie wydajności i skalowalności systemu. Dane są buforowane przez tymczasowe kopiowanie często używanych danych do szybkiego magazynu znajdującego się w pobliżu aplikacji. Jeśli ten szybki magazyn danych znajduje się bliżej aplikacji niż oryginalne źródło, buforowanie może znacznie poprawić czasy odpowiedzi aplikacji klienckich poprzez szybszą obsługę danych.

Buforowanie jest najbardziej efektywne, gdy wystąpienie klienta wielokrotnie odczytuje te same dane, zwłaszcza jeśli oryginalny magazyn danych spełnia wszystkie poniższe warunki:

  • Pozostaje względnie statyczny.
  • Jest powolny w porównaniu do prędkości pamięci podręcznej.
  • Podlega wysokiemu poziomowi rywalizacji.
  • Jest daleko, gdy opóźnienia sieci mogą powodować spowolnienie dostępu.

Buforowanie w aplikacjach rozproszonych

Aplikacje rozproszone zwykle implementują jedną lub obie następujące strategie podczas buforowania danych:

  • Używają prywatnej pamięci podręcznej, w której dane są przechowywane lokalnie na komputerze z uruchomionym wystąpieniem aplikacji lub usługi.
  • Używają udostępnionej pamięci podręcznej, pełniąc rolę wspólnego źródła, do którego można uzyskać dostęp przez wiele procesów i maszyn.

W obu przypadkach buforowanie można wykonać po stronie klienta i po stronie serwera. Buforowanie po stronie klienta jest wykonywane przez proces, który zapewnia interfejs użytkownika dla systemu, np. przeglądarkę internetową lub aplikację. Buforowanie po stronie serwera jest wykonywane przez proces, który zapewnia usługi biznesowe uruchamiane zdalnie.

Buforowanie prywatne

Najbardziej podstawowym typem pamięci podręcznej jest magazyn w pamięci. Jest on przechowywany w przestrzeni adresowej jednego procesu. Kod uruchomiony w tym procesie uzyskuje do niego dostęp bezpośrednio. Dostęp do tego typu pamięci podręcznej jest szybki. Może również zapewnić skuteczne środki do przechowywania skromnych ilości danych statycznych. Rozmiar pamięci podręcznej jest zwykle ograniczany przez ilość pamięci dostępnej na maszynie, która hostuje proces.

Jeżeli potrzebujesz buforować większą ilość informacji, niż jest to fizycznie możliwe w pamięci, możesz zapisać dane w pamięci podręcznej w lokalnym systemie plików. Ten proces będzie wolniejszy niż dostęp do danych przechowywanych w pamięci, ale nadal powinien być szybszy i bardziej niezawodny niż pobieranie danych w sieci.

Jeśli masz wiele wystąpień aplikacji używających tego modelu, które są uruchomione równocześnie, każde wystąpienie aplikacji ma swoją własną, niezależną pamięć podręczną zawierającą własną kopię danych.

Pomyśl o pamięci podręcznej jak o migawce oryginalnych danych w pewnym momencie w przeszłości. Jeśli te dane nie są statyczne, prawdopodobnie różne wystąpienia aplikacji przechowują różne wersje danych w swoich pamięciach podręcznych. Dlatego też to samo zapytanie wykonane wobec tych wystąpień może zwrócić inne wyniki, jak pokazano na rysunku 1.

The results of using an in-memory cache in different instances of an application

Rysunek 1. Używanie pamięci podręcznej w pamięci w różnych wystąpieniach aplikacji.

Udostępniona pamięć podręczna

Jeśli używasz udostępnionej pamięci podręcznej, może pomóc złagodzić obawy, że dane mogą się różnić w każdej pamięci podręcznej, co może wystąpić w przypadku buforowania w pamięci. Buforowanie w udostępnionej pamięci gwarantuje, że różne wystąpienia aplikacji będą korzystać z tych samych danych w pamięci podręcznej. Lokalizuje pamięć podręczną w oddzielnej lokalizacji, która jest zwykle hostowana jako część oddzielnej usługi, jak pokazano na rysunku 2.

The results of using a shared cache

Rysunek 2. Używanie udostępnionej pamięci podręcznej.

Ważną korzyścią płynącą z użycia udostępnionej pamięci podręcznej jest zapewniana przez nią skalowalność. Wiele usług udostępnionej pamięci podręcznej jest implementowanych przy użyciu klastra serwerów i używa oprogramowania do przezroczystego dystrybuowania danych w klastrze. Wystąpienie aplikacji po prostu wysyła żądanie do usługi udostępnionej pamięci podręcznej. Podstawowa infrastruktura określa lokalizację buforowanych danych w klastrze. Możesz łatwo skalować pamięć podręczną poprzez dodawanie kolejnych serwerów.

Istnieją dwie główne wady związane z buforowaniem w udostępnionej pamięci podręcznej:

  • Pamięć podręczna jest wolniejsza do uzyskiwania dostępu, ponieważ nie jest już przechowywana lokalnie w każdym wystąpieniu aplikacji.
  • Wymaganie związane z implementowaniem oddzielnej usługi pamięci podręcznej może powodować większą złożoność rozwiązania.

Zagadnienia dotyczące użycia pamięci podręcznej

W poniższych sekcjach szczegółowo opisano zagadnienia dotyczące projektowania i używania pamięci podręcznej.

Podejmowanie decyzji o tym, kiedy buforować dane

Buforowanie może znacznie zwiększyć wydajność, skalowalność i dostępność. Im więcej masz danych i im większa jest liczba użytkowników, którzy muszą uzyskiwać dostęp do tych danych, tym większe mogą być korzyści z buforowania. Buforowanie zmniejsza opóźnienie i rywalizację związaną z obsługą dużych ilości współbieżnych żądań w oryginalnym magazynie danych.

Przykładowo baza danych może obsługiwać ograniczoną liczbę połączeń współbieżnych. Jednak pobieranie danych z udostępnionej pamięci podręcznej, zamiast polegania na podstawowej bazie danych, umożliwia aplikacji klienckiej uzyskiwanie dostępu do tych danych nawet wtedy, gdy liczba dostępnych połączeń jest obecnie wyczerpana. Jeśli ponadto baza danych stanie się niedostępna, możliwe, że aplikacje klienckie nadal będą w stanie używać danych przechowywanych w pamięci podręcznej.

Rozważ buforowanie danych, które są często odczytywane, ale rzadko modyfikowane (na przykład danych o wyższych proporcjach operacji odczytu do operacji zapisu). Jednak nie zaleca się użycia pamięci podręcznej jako autorytatywnego magazynu najważniejszych informacji. Zamiast tego upewnij się, że wszystkie zmiany, na które aplikacja nie może pozwolić sobie na utratę, są zawsze zapisywane w trwałym magazynie danych. Jeśli pamięć podręczna jest niedostępna, aplikacja nadal może działać przy użyciu magazynu danych i nie utracisz ważnych informacji.

Określanie sposobu efektywnego buforowania danych

Kluczem do efektywnego buforowania danych jest określenie najważniejszych danych do zapisu w pamięci podręcznej oraz buforowanie ich w odpowiednim momencie. Dane można dodawać do pamięci podręcznej na żądanie przy pierwszym pobieraniu przez aplikację. Aplikacja musi pobrać dane tylko raz z magazynu danych i że kolejny dostęp może być spełniony przy użyciu pamięci podręcznej.

Alternatywnie pamięć podręczną można częściowo lub całkowicie wypełnić danymi z wyprzedzeniem, zazwyczaj podczas uruchamiania aplikacji (podejście zwane wstępnym wypełnianiem). Jednak zaimplementowanie wstępnego wypełniania w przypadku dużej pamięci podręcznej nie jest zalecane, ponieważ takie podejście może powodować nagłe, wysokie obciążenia w oryginalnym magazynie danych podczas uruchamiania aplikacji.

Często analiza wzorców użycia może pomóc w podjęciu decyzji, czy należy całkowicie lub częściowo wstępnie wypełnić pamięć podręczną, a także w wybraniu danych do buforowania. Można na przykład zainicjować pamięć podręczną ze statycznymi danymi profilu użytkownika dla klientów, którzy regularnie korzystają z aplikacji (być może codziennie), ale nie dla klientów korzystających z aplikacji tylko raz w tygodniu.

Buforowanie zazwyczaj dobrze działa w przypadku danych, które są niezmienne lub które zmieniają się rzadko. Przykłady obejmują informacje dodatkowe, takie jak informacje o produkcie i cenniku w aplikacji handlu elektronicznego lub udostępnione zasoby statyczne, których utworzenie jest kosztowne. Niektóre lub wszystkie te dane mogą zostać załadowane do pamięci podręcznej podczas uruchamiania aplikacji, aby zminimalizować zapotrzebowanie na zasoby i poprawić wydajność. Możesz również mieć proces w tle, który okresowo aktualizuje dane referencyjne w pamięci podręcznej, aby upewnić się, że jest aktualny. Lub proces w tle może odświeżyć pamięć podręczną po zmianie danych referencyjnych.

Buforowanie jest mniej przydatne w przypadku danych dynamicznych, chociaż istnieje kilka wyjątków od tej reguły (zobacz sekcję Buforowanie wysoce dynamicznych danych w dalszej części tego artykułu, aby uzyskać więcej informacji). Gdy oryginalne dane zmieniają się regularnie, buforowane informacje szybko stają się nieaktualne lub obciążenie związane z synchronizacją pamięci podręcznej z oryginalnym magazynem danych zmniejsza skuteczność buforowania.

Pamięć podręczna nie musi zawierać pełnych danych dla jednostki. Jeśli na przykład element danych reprezentuje obiekt wielowartościowy, taki jak klient bankowy z nazwą, adresem i saldem konta, niektóre z tych elementów mogą pozostać statyczne, takie jak nazwa i adres. Inne elementy, takie jak saldo konta, mogą być bardziej dynamiczne. W takich sytuacjach może być przydatne buforowanie statycznych części danych i pobieranie (lub obliczanie) tylko pozostałych informacji, gdy jest to wymagane.

Zalecamy przeprowadzenie testów wydajnościowych i analizy użycia w celu określenia, czy wstępnie wypełniane lub na żądanie ładowanie pamięci podręcznej, czy kombinacja obu tych operacji jest odpowiednia. Decyzję należy podjąć w oparciu o zmienność i wzorzec użytkowania danych. Wykorzystanie pamięci podręcznej i analiza wydajności są ważne w aplikacjach, które napotykają duże obciążenia i muszą być wysoce skalowalne. Na przykład w wysoce skalowalnych scenariuszach można zainicjować pamięć podręczną, aby zmniejszyć obciążenie magazynu danych w godzinach szczytu.

Ponadto buforowania można użyć, aby unikać powtarzania obliczeń, gdy aplikacja jest uruchomiona. Jeśli operacja przekształca dane lub wykonuje skomplikowane obliczenia, wyniki operacji można zapisać w pamięci podręcznej. Jeśli następnie wymagane jest takie samo obliczenie, aplikacja może po prostu pobrać wyniki z pamięci podręcznej.

Aplikacja może zmodyfikować dane przechowywane w pamięci podręcznej. Niemniej jednak najlepiej jest myśleć o pamięci podręcznej jako o przejściowym magazynie danych, który może zniknąć w dowolnym momencie. Nie przechowuj cennych danych tylko w pamięci podręcznej; upewnij się, że informacje są przechowywane w oryginalnym magazynie danych. Dzięki temu, jeśli pamięć podręczna stanie się niedostępna, zminimalizujesz szansę utraty danych.

Buforowanie wysoce dynamicznych danych

Podczas przechowywania szybko zmieniających się informacji w trwałym magazynie danych może to narzucić obciążenie systemu. Na przykład rozważmy urządzenie, które ciągle zgłasza stan lub jakąś inną miarę. Jeśli aplikacja nie będzie buforować tych danych, ponieważ buforowane dane będą niemal zawsze przestarzałe, to takie samo założenie może być prawdziwe w przypadku przechowywania tych informacji w magazynie danych i pobierania ich z niego. Zmiany mogą zajść w czasie wymaganym na zapisanie i pobranie tych danych.

W takiej sytuacji należy rozważyć korzyści przechowywania informacji dynamicznych bezpośrednio w pamięci podręcznej zamiast w trwałym magazynie danych. Jeśli dane są niekrytyczne i nie wymagają inspekcji, nie ma znaczenia, czy sporadyczne zmiany zostaną utracone.

Zarządzanie wygasaniem danych w pamięci podręcznej

W większości przypadków dane przechowywane w pamięci podręcznej stanowią kopię danych przechowywanych w oryginalnym magazynie danych. Dane w oryginalnym magazynie danych mogą ulec zmianie po umieszczeniu ich w pamięci podręcznej, przez co dane w pamięci podręcznej staną się przestarzałe. Wiele systemów buforowania umożliwia skonfigurowanie pamięci podręcznej tak, aby zapewnić wygasanie danych i zredukować okres, w którym dane mogą być nieaktualne.

Gdy dane buforowane wygasają, zostaną usunięte z pamięci podręcznej, a aplikacja musi pobrać dane z oryginalnego magazynu danych (może umieścić nowo pobrane informacje z powrotem w pamięci podręcznej). Domyślne zasady wygasania możesz ustawić podczas konfigurowania pamięci podręcznej. W wielu usługach pamięci podręcznej możesz też ustawić okres ważności dla poszczególnych obiektów podczas programowego zapisywania ich w pamięci podręcznej. Niektóre pamięci podręczne umożliwiają określenie okresu wygaśnięcia jako wartości bezwzględnej lub jako wartości przesuwanej, która powoduje usunięcie elementu z pamięci podręcznej, jeśli nie jest on dostępny w określonym czasie. To ustawienie przesłania wszelkie zasady wygasania obejmujące całą pamięć podręczną, ale tylko dla określonych obiektów.

Uwaga

Należy dokładnie rozważyć okres ważności pamięci podręcznej oraz obiektów, które zawiera. Jeśli ustawisz zbyt krótki okres, obiekty będą wygasać zbyt szybko, co wpłynie na zredukowanie korzyści związanych ze stosowaniem pamięci podręcznej. Jeśli wprowadzisz zbyt długi okres, może dojść do sytuacji, w której dane będą nieaktualne.

Istnieje również możliwość zapełnienia pamięci podręcznej, jeśli dane będą mogły pozostawać w niej przez długi czas. W takim przypadku wszelkie żądania dodania nowych elementów do pamięci podręcznej mogą spowodować wymuszone usunięcie niektórych elementów w procesie zwanym eksmisją. Usługi pamięci podręcznej zazwyczaj eksmitują dane najdawniej używane (LRU, least recently used), ale możesz zastąpić te zasady i uniemożliwić eksmitowanie elementów. Jeśli jednak przyjmiesz takie podejście, istnieje ryzyko przekroczenia pamięci dostępnej w pamięci podręcznej. Aplikacja, która spróbuje dodać element do pamięci podręcznej, zostanie zakończona niepowodzeniem z powodu wyjątku.

Niektóre implementacje buforowania mogą zapewniać dodatkowe zasady eksmisji. Istnieje kilka typów zasad eksmisji. Są to:

  • Ostatnio używane zasady (w oczekiwaniu, że dane nie będą ponownie wymagane).
  • Zasady „pierwszy na wejściu — pierwszy na wyjściu” (najpierw eksmitowane są najstarsze dane).
  • Zasady jawnego usuwania oparte na wyzwalanym zdarzeniu (np. modyfikacji danych).

Unieważnianie danych w pamięci podręcznej po stronie klienta

Dane, które są przechowywane w pamięci podręcznej po stronie klienta, są zazwyczaj uważane za dane wykraczające poza nadzór usługi zapewniającej dane klientowi. Usługa nie może bezpośrednio wymusić na kliencie dodawania ani usuwania informacji z pamięci podręcznej po stronie klienta.

Oznacza to, że jest możliwe, aby klient używał nieprawidłowo skonfigurowanej pamięci podręcznej i nadal korzystał z przestarzałych informacji. Jeśli przykładowo zasady wygasania pamięci podręcznej nie zostaną prawidłowo zaimplementowane, klient może używać nieaktualnych informacji, które zostały zbuforowane lokalnie, podczas gdy informacje w oryginalnym źródle danych uległy zmianie.

Jeśli tworzysz aplikację internetową, która obsługuje dane za pośrednictwem połączenia HTTP, możesz niejawnie wymusić na kliencie internetowym (takim jak przeglądarka lub internetowy serwer proxy), aby pobrać najnowsze informacje. Możesz to zrobić, jeśli zasób jest aktualizowany w oparciu o zmiany w identyfikatorze URI tego zasobu. Klienci internetowi zazwyczaj używają identyfikatora URI zasobu jako klucza w pamięci podręcznej po stronie klienta, więc w przypadku zmiany identyfikatora URI klient internetowy ignoruje wszelkie wcześniej zbuforowane wersje zasobu i pobiera zamiast tego nową wersję.

Zarządzanie współbieżnością w pamięci podręcznej

Pamięci podręczne są często zaprojektowane z myślą o współdzieleniu przez wiele wystąpień aplikacji. Każde wystąpienie aplikacji może odczytywać i modyfikować dane w pamięci podręcznej. W rezultacie te same problemy ze współbieżnością, które występują w udostępnionych magazynach danych, mają zastosowanie do pamięci podręcznej. W sytuacji, gdy aplikacja musi zmodyfikować dane przechowywane w pamięci podręcznej, może być konieczne upewnienie się, że aktualizacje wprowadzone przez jedno wystąpienie aplikacji nie zastępują zmian wprowadzonych przez inne wystąpienie.

W zależności od charakteru danych i prawdopodobieństwa kolizji możesz przyjąć jedno z dwóch podejść do współbieżności:

  • Podejście optymistyczne. Bezpośrednio przed zaktualizowaniem danych aplikacja sprawdza, czy dane w pamięci podręcznej uległy zmianie od czasu ich odebrania. Jeżeli dane są nadal takie same, można wprowadzić zmiany. W przeciwnym razie aplikacja musi zdecydować, czy należy zaktualizować dane. (Logika biznesowa, która napędza tę decyzję, będzie specyficzna dla aplikacji). Takie podejście jest odpowiednie w sytuacjach, w których aktualizacje są rzadko dostępne lub w przypadku wystąpienia kolizji jest mało prawdopodobne.
  • Podejście pesymistyczne. Podczas pobierania danych aplikacja blokuje je w pamięci podręcznej, aby zapobiegać ich modyfikacji przez inne wystąpienie. Ten proces gwarantuje, że nie mogą wystąpić kolizje, ale mogą również blokować inne wystąpienia, które muszą przetwarzać te same dane. Współbieżność pesymistyczna może wpływać na skalowalność rozwiązania. Jest zalecana tylko w przypadku operacji krótkotrwałych. Takie podejście może być odpowiednie w sytuacjach, w których wystąpienie kolizji jest bardzo prawdopodobne, zwłaszcza wtedy, gdy aplikacja aktualizuje wiele elementów w pamięci podręcznej i musi upewniać się, że zmiany są stosowane spójnie.

Implementowanie wysokiej dostępności i skalowalności oraz poprawianie wydajności

Unikaj używania pamięci podręcznej jako podstawowego repozytorium danych. Jest to rola oryginalnego magazynu danych, na podstawie którego wypełniono pamięć podręczną. Oryginalny magazyn danych odpowiada za zapewnianie trwałości danych.

Pamiętaj, aby nie wprowadzać do rozwiązań krytycznych zależności od dostępności usługi udostępnionej pamięci podręcznej. Aplikacja powinna mieć możliwość działania w sytuacji, w której usługa zapewniająca udostępnioną pamięć podręczną jest niedostępna. Aplikacja nie powinna odpowiadać ani nie działać podczas oczekiwania na wznowienie usługi pamięci podręcznej.

W związku z tym aplikacja musi być przygotowana na wykrywanie dostępności usługi pamięci podręcznej oraz powrót do oryginalnego magazynu danych, jeśli pamięć podręczna jest niedostępna. Wzorzec wyłącznika przydaje się do obsługi takiego scenariusza. Usługa zapewniająca pamięć podręczną może zostać odzyskana. Gdy ponownie będzie dostępna, pamięć podręczną można ponownie wypełnić, ponieważ dane zostaną odczytane z oryginalnego magazynu danych, zgodnie ze strategią, np. wzorcem z odkładaniem do pamięci podręcznej.

Jednak skalowalność systemu może mieć wpływ, jeśli aplikacja wróci do oryginalnego magazynu danych, gdy pamięć podręczna jest tymczasowo niedostępna. Podczas odzyskiwania magazynu danych może nastąpić zalew żądań danych w oryginalnym magazynie danych, powodujących przekroczenie limitów czasu i awarie połączeń.

Rozważ zaimplementowanie lokalnej, prywatnej pamięci podręcznej w każdym wystąpieniu aplikacji wraz z udostępnioną pamięcią podręczną, do której wszystkie wystąpienia aplikacji mogą uzyskać dostęp. Kiedy aplikacja pobiera element, może najpierw sprawdzić w lokalnej pamięci podręcznej, a następnie w udostępnionej pamięci podręcznej oraz ostatecznie w oryginalnym magazynie danych. Lokalną pamięć podręczną można wypełnić przy użyciu danych z udostępnionej pamięci podręcznej lub z bazy danych, jeśli udostępniona pamięć podręczna jest niedostępna.

To podejście wymaga dokładnej konfiguracji, aby zapobiegać sytuacjom, w którym lokalna pamięć podręczna będzie zbyt przestarzała względem udostępnionej pamięci podręcznej. Jednak lokalna pamięć podręczna będzie działać jako bufor, jeśli udostępniona pamięć podręczna będzie niedostępna. Rysunek 3 przedstawia tę strukturę.

Using a local private cache with a shared cache

Rysunek 3. Używanie lokalnej prywatnej pamięci podręcznej z udostępnioną pamięcią podręczną.

Aby obsługiwać duże pamięci podręczne przechowujące stosunkowo długotrwałe dane, niektóre usługi pamięci podręcznej zapewniają opcję wysokiej dostępności, która implementuje automatyczne przejście w tryb failover, jeśli pamięć podręczna jest niedostępna. To podejście zazwyczaj uwzględnia replikację buforowanych danych przechowywanych na podstawowym serwerze pamięci podręcznej do pomocniczego serwera pamięci podręcznej oraz przełączanie na serwer pomocniczy, jeśli serwer podstawowy ulegnie awarii lub nastąpi utrata połączenia.

Aby zmniejszyć opóźnienia związane z zapisywaniem w wielu miejscach docelowych, replikacja do serwera pomocniczego może występować asynchronicznie, gdy dane są zapisywane w pamięci podręcznej na serwerze podstawowym. Takie podejście prowadzi do możliwości utraty niektórych buforowanych informacji, jeśli wystąpi awaria, ale odsetek tych danych powinien być niewielki w porównaniu z ogólnym rozmiarem pamięci podręcznej.

Jeśli udostępniona pamięć podręczna jest zbyt duża, korzystne może być partycjonowanie danych pamięci podręcznej na wiele węzłów w celu zmniejszenia prawdopodobieństwa rywalizacji i poprawienia skalowalności. Wiele udostępnionych pamięci podręcznych obsługuje możliwość dynamicznego dodawania (i usuwania) węzłów, a także ponownego równoważenia danych w różnych partycjach. Takie podejście może obejmować tworzenie klastrów, w których kolekcja węzłów jest przedstawiana aplikacjom klienckim jako bezproblemowa, pojedyncza pamięć podręczna. Jednak wewnętrznie dane są rozprowadzane pomiędzy węzłami zgodnie ze wstępnie zdefiniowaną strategią dystrybucji, która równomiernie równoważy obciążenie. Aby uzyskać więcej informacji na temat możliwych strategii partycjonowania, zobacz Wskazówki dotyczące partycjonowania danych.

Klastrowanie także może zwiększać dostępność pamięci podręcznej. Jeśli węzeł ulegnie awarii, pozostała część pamięci podręcznej będzie nadal dostępna. Klastrowanie jest często używane w połączeniu z replikacją i trybem failover. Każdy węzeł jest replikowany, a replika może być szybko przeniesiona do trybu online, jeśli węzeł ulegnie awarii.

Wiele operacji odczytu i zapisu może obejmować pojedyncze wartości danych lub obiekty. Jednak czasami może być konieczne szybkie zapisanie lub pobranie większych ilości danych. Przykładowo wstępne wypełnienie pamięci podręcznej może obejmować zapisanie setek lub tysięcy elementów w tej pamięci. Aplikacja może też wymagać pobrania dużej liczby powiązanych elementów z pamięci podręcznej w ramach tego samego żądania.

Wiele pamięci podręcznych działających na dużą skalę zapewnia operacje wsadowe dla tych celów. Umożliwia to aplikacji klienckiej spakowanie dużej liczby elementów w jednym żądaniu oraz redukuje obciążenia związane z wykonywaniem dużej liczby niewielkich żądań.

Buforowanie i spójność ostateczna

Aby wzorzec z odkładaniem do pamięci podręcznej mógł działać, aplikacja wypełniająca pamięć podręczną musi mieć dostęp do najnowszej i spójnej wersji danych. W systemie, który implementuje spójność ostateczną (np. replikowany magazyn danych), może być inaczej.

Jedno wystąpienie aplikacji może zmodyfikować element danych i unieważnić zbuforowaną wersję tego elementu. Kolejne wystąpienie aplikacji może podjąć próbę odczytania tego elementu z pamięci podręcznej, co spowoduje chybienie pamięci podręcznej, więc wystąpienie odczyta dane z magazynu danych i doda je do pamięci podręcznej. Jeśli jednak magazyn danych nie został w pełni zsynchronizowany z innymi replikami, wystąpienie aplikacji może odczytywać i wypełniać pamięć podręczną starą wartością.

Aby uzyskać więcej informacji na temat obsługiwania spójności danych, zobacz Data consistency primer (Podstawy spójności danych).

Ochrona danych w pamięci podręcznej

Niezależnie od używanej usługi pamięci podręcznej należy rozważyć sposób zabezpieczania danych przechowywanych w pamięci podręcznej przed nieautoryzowanym dostępem. Istnieją dwie główne kwestie:

  • Prywatność danych w pamięci podręcznej.
  • Prywatność danych podczas ich przepływu pomiędzy pamięcią podręczną i aplikacją, która używa tej pamięci podręcznej.

Aby chronić dane w pamięci podręcznej, usługa pamięci podręcznej może zaimplementować mechanizm uwierzytelniania, który wymaga określenia przez aplikacje następujących elementów:

  • Tożsamości, które mogą uzyskiwać dostęp do danych w pamięci podręcznej.
  • Operacje (odczytu i zapisu), które mogą wykonywać te tożsamości.

Aby zmniejszyć obciążenie powiązane z odczytywaniem i zapisywaniem danych, po uzyskaniu dostępu do zapisu i/lub odczytu w pamięci podręcznej przez tożsamość może ona używać dowolnych danych w pamięci podręcznej.

Jeśli musisz ograniczyć dostęp do podzestawów danych w pamięci podręcznej, możesz wykonać jedną z następujących czynności:

  • Podziel pamięć podręczną na partycje (korzystając z różnych serwerów pamięci podręcznej) i udziel dostępu do partycji tylko tym tożsamościom, które mogą ich używać.
  • Zaszyfruj dane w każdym podzestawie przy użyciu innych kluczy, a następnie podaj klucze szyfrowania tylko tym tożsamościom, które powinny mieć dostęp do danego podzestawu. Aplikacja kliencka będzie nadal mogła pobrać wszystkie dane z pamięci podręcznej, ale nie będzie w stanie odszyfrować danych, dla których nie ma kluczy.

Musisz również chronić dane podczas ich przepływu do i z pamięci podręcznej. W tej kwestii polegasz na funkcjach zabezpieczeń zapewnianych przez infrastrukturę sieciową, z której aplikacje klienckie korzystają do nawiązywania połączenia z pamięcią podręczną. Jeśli pamięć podręczna jest implementowana przy użyciu serwera lokalnego w tej samej organizacji, który hostuje aplikacje klienckie, izolacja samej sieci może nie wymagać podejmowania żadnych dodatkowych kroków. Jeśli pamięć podręczna znajduje się w lokalizacji zdalnej i wymaga połączenia za pośrednictwem protokołu TCP lub HTTP i sieci publicznej (np. przez Internet), rozważ zaimplementowanie protokołu SSL.

Zagadnienia dotyczące implementowania buforowania na platformie Azure

Azure Cache for Redis to implementacja pamięci podręcznej Redis typu open source, która działa jako usługa w centrum danych platformy Azure. Zapewnia ona usługę buforowania, do której można uzyskać dostęp z dowolnej aplikacji platformy Azure, niezależnie od tego, czy aplikacja została zaimplementowana jako usługa w chmurze, jako witryna internetowa, czy wewnątrz maszyny wirtualnej platformy Azure. Pamięci podręczne mogą być współdzielone przez aplikacje klienckie, które mają odpowiedni klucz dostępu.

Usługa Azure Cache for Redis to rozwiązanie do buforowania o wysokiej wydajności, które zapewnia dostępność, skalowalność i zabezpieczenia. Zwykle rozwiązanie jest uruchamiane jako usługa na jednej lub wielu maszynach dedykowanych. Usługa stara się przechować możliwie wiele informacji w pamięci, aby zapewnić szybki dostęp. Ta architektura ma na celu zapewnianie małych opóźnień i wysokiej przepustowości poprzez zredukowanie potrzeby wykonywania powolnych operacji we/wy.

Usługa Azure Cache for Redis jest zgodna z wieloma różnymi interfejsami API używanymi przez aplikacje klienckie. Jeśli masz istniejące aplikacje, które już korzystają z usługi Azure Cache for Redis działającej lokalnie, usługa Azure Cache for Redis udostępnia szybką ścieżkę migracji do buforowania w chmurze.

Funkcje usługi Redis

Redis to coś więcej niż prosty serwer pamięci podręcznej. Zapewnia on rozproszoną bazę danych w pamięci z szeroką gamą poleceń, które obsługują wiele typowych scenariuszy. Zostały one opisane w dalszej części tego dokumentu w sekcji Używanie buforowania usługi Redis. Sekcja ta podsumowuje wybrane kluczowe funkcje zapewniane przez usługę Redis.

Redis jako baza danych w pamięci

Usługa Redis obsługuje operacje odczytu i zapisu. W usłudze Redis zapisy mogą być chronione przed awarią systemu poprzez okresowe zapisywanie w lokalnym pliku migawki lub w pliku dziennika tylko do dołączania. Taka sytuacja nie jest taka w przypadku wielu pamięci podręcznych, które należy traktować jako przejściowe magazyny danych.

Wszystkie operacje zapisu są asynchroniczne i nie blokują klientom odczytywania i zapisywania danych. Po uruchomieniu usługi Redis odczytuje on dane z pliku migawki lub dziennika, a następnie używa ich do utworzenia pamięci podręcznej w pamięci. Aby uzyskać więcej informacji, zobacz Redis persistence (Trwałość w usłudze Redis) w witrynie internetowej usługi Redis.

Uwaga

Usługa Redis nie gwarantuje, że wszystkie zapisy zostaną zapisane, jeśli wystąpi katastrofalna awaria, ale w najgorszym przypadku może utracić tylko kilka sekund wartości danych. Należy pamiętać, że pamięć podręczna nie jest przeznaczona do działania jako autorytatywne źródło danych i odpowiada za aplikacje korzystające z pamięci podręcznej w celu zapewnienia pomyślnego zapisania krytycznych danych w odpowiednim magazynie danych. Aby uzyskać więcej informacji, zobacz Wzorzec odkładania do pamięci podręcznej.

typach danych usługi redis

Redis jest magazynem wartości kluczy, w którym wartości mogą zawierać proste lub złożone struktury danych, np. skróty, listy i zestawy. Magazyn obsługuje zestaw operacji niepodzielnych wykonywanych na tych typach danych. Klucze mogą być stałe lub oznaczone jako klucze o ograniczonym czasie życia, po upłynięciu którego klucz i jego odpowiednia wartość zostaną automatycznie usunięte z pamięci podręcznej. Aby uzyskać więcej informacji o kluczach i wartościach usługi Redis, zobacz An introduction to Redis data types and abstractions (Wprowadzenie do typów danych i abstrakcji w usłudze Redis) w witrynie internetowej usługi Redis.

Replikacja i klastrowanie w usłudze Redis

Usługa Redis obsługuje replikację podstawową/podrzędną w celu zapewnienia dostępności i utrzymania przepływności. Operacje zapisu w węźle podstawowym usługi Redis są replikowane do co najmniej jednego węzła podrzędnego. Operacje odczytu mogą być obsługiwane przez element podstawowy lub dowolny z podwładnych.

Jeśli masz partycję sieciową, podwładni mogą nadal obsługiwać dane, a następnie w sposób przezroczysty ponownie synchronizować dane z podstawowymi elementami po ponownym utworzeniu połączenia. Aby uzyskać szczegółowe informacje, odwiedź stronę Replication (Replikacja) w witrynie internetowej usługi Redis.

Ponadto usługa Redis zapewnia klastrowanie, które umożliwia niewidoczne partycjonowanie danych na fragmenty na różnych serwerach oraz rozłożenie obciążenia. Ta funkcja poprawia skalowalność, ponieważ w miarę zwiększania pamięci podręcznej można dodawać nowe serwery Redis i ponownie partycjonować dane.

Ponadto każdy serwer w klastrze można replikować przy użyciu replikacji podstawowej/podrzędnej. Zapewnia to dostępność w każdym węźle w klastrze. Aby uzyskać więcej informacji o klastrowaniu i fragmentowaniu, odwiedź stronę Redis cluster tutorial (Samouczek dotyczący klastra Redis) w witrynie internetowej usługi Redis.

Użycie pamięci przez usługę Redis

Pamięć podręczna Redis Cache ma skończony rozmiar, który zależy od zasobów dostępnych na komputerze hosta. Podczas konfigurowania serwera Redis możesz określić maksymalną ilość pamięci, z jakiej może on korzystać. Możesz również skonfigurować klucz w pamięci podręcznej Redis, aby mieć czas wygaśnięcia, po którym zostanie automatycznie usunięty z pamięci podręcznej. Ta funkcja pomaga zapobiegać wypełnianiu pamięci podręcznej w pamięci starymi lub nieaktualnymi danymi.

Po zapełnieniu pamięci usługa Redis może automatycznie eksmitować klucze i ich wartości zgodnie z wieloma zasadami. Wartość domyślna to LRU (co najmniej ostatnio używane), ale można również wybrać inne zasady, takie jak eksmitowanie kluczy losowo lub całkowite wyłączenie eksmisji (w tym przypadku próby dodania elementów do pamięci podręcznej kończą się niepowodzeniem, jeśli jest pełna). Strona Using Redis as an LRU Cache (Używanie usługi Redis jako pamięci podręcznej LRU) zawiera więcej informacji.

Transakcje i partie w usłudze Redis

Usługa Redis umożliwia aplikacji klienckiej przesyłanie serii operacji, które będą odczytywać i zapisywać dane w pamięci podręcznej, jako transakcji niepodzielnej. Wszystkie polecenia w transakcji będą uruchamiane po kolei, a pomiędzy nimi nie zostaną uruchomione polecenia wydane przez innych, konkurencyjnych klientów.

Jednak nie są to prawdziwe transakcje, ponieważ relacyjna baza danych je wykona. Przetwarzanie transakcji obejmuje dwa etapy — pierwszy to umieszczenie poleceń w kolejce, a drugi to uruchomienie poleceń. Na etapie ustalania kolejki poleceń polecenia składające się na transakcję są przesyłane przez klienta. Jeśli w tym momencie wystąpi jakiś błąd (np. błąd składniowy lub nieprawidłowa liczba parametrów), usługa Redis odmówi przetworzenia całej transakcji i odrzuci ją.

Na etapie uruchamiania usługa Redis będzie wykonywać wszystkie polecenia w kolejności. Jeśli polecenie zakończy się niepowodzeniem w tej fazie, usługa Redis będzie kontynuować wykonywanie następnego polecenia w kolejce i nie wycofa efektów żadnych poleceń, które zostały już uruchomione. Ta uproszczona forma transakcji pomaga w zapewnieniu wydajności i unikaniu problemów z wydajnością powodowanych przez rywalizację.

Usługa Redis implementuje formę optymistycznego blokowania, aby wspierać zachowywanie spójności. Aby uzyskać szczegółowe informacje na temat transakcji i blokowania przy użyciu usługi Redis, odwiedź stronę Transactions (Transakcje) w witrynie internetowej usługi Redis.

Usługa Redis obsługuje również nietransakcyjne przetwarzanie wsadowe żądań. Protokół Redis, którego klienci używają do wysyłania poleceń do serwera Redis, umożliwia wysyłanie serii operacji w ramach tego samego żądania. Może to pomóc w zredukowaniu fragmentacji pakietu w sieci. Podczas przetwarzania partii każde polecenie jest wykonywane. Jeśli którekolwiek z tych poleceń jest źle sformułowane, zostaną odrzucone (co nie nastąpi z transakcją), ale pozostałe polecenia zostaną wykonane. Nie ma również gwarancji co do kolejności przetwarzania poleceń w partii.

Zabezpieczenia usługi Redis

Usługa Redis skupia się wyłącznie na zapewnianiu szybkiego dostępu do danych. Jest zaprojektowana z myślą o uruchamianiu w zaufanym środowisku, do którego mogą uzyskiwać dostęp tylko zaufani klienci. Usługa Redis obsługuje model ograniczonych zabezpieczeń oparty na uwierzytelnianiu za pomocą hasła. (Istnieje możliwość całkowitego usunięcia uwierzytelniania, chociaż nie zalecamy tego).

Wszyscy uwierzytelnieni klienci współdzielą to samo hasło globalne i mają dostęp do tych samych zasobów. Jeśli potrzebne są bardziej kompleksowe zabezpieczenia logowania, musisz zaimplementować własną warstwę zabezpieczeń przed serwerem Redis. Wszystkie żądania klientów powinny przechodzić przez tę dodatkową warstwę. Usługa Redis nie powinna być bezpośrednio widoczna dla niezaufanych lub nieuwierzytelnionych klientów.

Możesz ograniczyć dostęp do poleceń, wyłączając je lub zmieniając ich nazwy (i podając nowe nazwy tylko uprzywilejowanym klientom).

Usługa Redis nie obsługuje bezpośrednio żadnej formy szyfrowania danych, więc wszystkie kodowanie musi być wykonywane przez aplikacje klienckie. Ponadto usługa Redis nie zapewnia żadnej formy zabezpieczeń transportu. Jeśli musisz chronić dane podczas ich przepływu w sieci, zalecamy zaimplementowanie serwera proxy protokołu SSL.

Aby uzyskać więcej informacji, odwiedź stronę Redis security (Zabezpieczenia usługi Redis) w witrynie internetowej usługi Redis.

Uwaga

Usługa Azure Cache for Redis udostępnia własną warstwę zabezpieczeń, za pośrednictwem której klienci nawiązują połączenie. Podstawowe serwery Redis nie są widoczne w sieci publicznej.

Azure Redis Cache

Usługa Azure Cache for Redis zapewnia dostęp do serwerów Redis hostowanych w centrum danych platformy Azure. Działa jako fasada zapewniająca kontrolę dostępu i zabezpieczenia. Pamięć podręczną możesz aprowizować przy użyciu witryny Azure Portal.

Portal zapewnia wiele wstępnie zdefiniowanych konfiguracji. Obejmuje to rozwiązania z zakresu od 53 GB pamięci podręcznej uruchomionej jako dedykowana usługa obsługująca komunikację SSL (zapewniającą prywatność) i replikację typu nadrzędny/podrzędny z umową SLA gwarantującą dostępność na poziomie 99,9% po 250 MB pamięci podręcznej bez replikacji (brak gwarancji dostępności) uruchomionej na współdzielonym sprzęcie.

Przy użyciu witryny Azure Portal możesz też skonfigurować zasady eksmisji pamięci podręcznej oraz kontrolować dostęp do pamięci podręcznej poprzez dodawanie użytkowników do określonych ról. Te role, które definiują operacje wykonywane przez członków, obejmują rolę właściciela, współautora i czytelnika. Przykładowo członkowie roli Właściciel mają kompletną kontrolę nad pamięcią podręczną (w tym nad zabezpieczeniami) oraz jej zawartością, członkowie roli Współautor mogą odczytywać i zapisywać informacje w pamięci podręcznej, a członkowie roli Czytelnik mogą tylko pobierać dane z pamięci podręcznej.

Większość zadań administracyjnych jest wykonywanych za pośrednictwem witryny Azure Portal. Z tego powodu wiele poleceń administracyjnych dostępnych w standardowej wersji usługi Redis nie jest dostępnych, w tym możliwość programowego modyfikowania konfiguracji, zamykania serwera Redis, konfigurowania dodatkowych podwładnych lub wymuszania zapisywania danych na dysku.

Witryna Azure Portal obejmuje wygodny graficzny widok, który umożliwia monitorowanie wydajności pamięci podręcznej. Na przykład możesz wyświetlić liczbę wykonanych połączeń, liczbę wykonanych żądań, objętość operacji odczytu i zapisu oraz liczbę trafień pamięci podręcznej w porównaniu do chybień pamięci podręcznej. Przy użyciu tych informacji możesz określić efektywność pamięci podręcznej oraz, w razie potrzeby, przełączyć się na inną konfigurację lub zmienić zasady eksmisji.

Ponadto możesz utworzyć alerty wysyłające wiadomości e-mail do administratora, jeśli co najmniej jedna metryka o krytycznym znaczeniu wykroczy poza oczekiwany zakres. Na przykład możesz wysłać alert do administratora, jeśli liczba chybień pamięci podręcznej przekroczy określoną wartość w ciągu ostatniej godziny, ponieważ oznacza to, że pamięć podręczna może być zbyt mała lub dane mogą być eksmitowane zbyt szybko.

Możesz również monitorować użycie procesora CPU, pamięci i sieci dla pamięci podręcznej.

Aby uzyskać więcej informacji i przykładów pokazujących, jak utworzyć i skonfigurować usługę Azure Cache for Redis, odwiedź stronę Lap around Azure Cache for Redis (Okrążenie usługi Azure Cache for Redis) w blogu platformy Azure.

Stan sesji buforowania i dane wyjściowe HTML

Jeśli tworzysz ASP.NET aplikacje internetowe uruchamiane przy użyciu ról internetowych platformy Azure, możesz zapisać informacje o stanie sesji i dane wyjściowe HTML w usłudze Azure Cache for Redis. Dostawca stanu sesji dla usługi Azure Cache for Redis umożliwia udostępnianie informacji o sesji między różnymi wystąpieniami aplikacji internetowej ASP.NET i jest bardzo przydatny w sytuacjach farmy internetowej, w których koligacja klient-serwer nie jest dostępna, a buforowanie danych sesji w pamięci nie byłoby odpowiednie.

Korzystanie z dostawcy stanu sesji z usługą Azure Cache for Redis zapewnia kilka korzyści, w tym:

  • Udostępnianie stanu sesji wielu wystąpieniom aplikacji internetowej ASP.NET.
  • Zapewnianie zwiększonej skalowalności.
  • Obsługa kontrolowanego, równoczesnego dostępu do tych samych danych stanu sesji dla wielu elementów odczytujących i jednego elementu zapisującego.
  • Użycie kompresji w celu oszczędzania pamięci i poprawiania wydajności sieci.

Aby uzyskać więcej informacji, zobacz dostawca stanu sesji ASP.NET dla usługi Azure Cache for Redis.

Uwaga

Nie używaj dostawcy stanu sesji dla usługi Azure Cache for Redis z aplikacjami ASP.NET uruchomionymi poza środowiskiem platformy Azure. Opóźnienie uzyskiwania dostępu do pamięci podręcznej spoza platformy Azure może wyeliminować korzyści związane z wydajnością buforowania danych.

Podobnie dostawca wyjściowej pamięci podręcznej dla usługi Azure Cache for Redis umożliwia zapisywanie odpowiedzi HTTP generowanych przez aplikację internetową ASP.NET. Użycie dostawcy wyjściowej pamięci podręcznej z usługą Azure Cache for Redis może poprawić czas odpowiedzi aplikacji renderujących złożone dane wyjściowe HTML. Wystąpienia aplikacji generujące podobne odpowiedzi mogą używać udostępnionych fragmentów danych wyjściowych w pamięci podręcznej, a nie generowania tych danych wyjściowych HTML na nowo. Aby uzyskać więcej informacji, zobacz dostawca ASP.NET wyjściowej pamięci podręcznej dla usługi Azure Cache for Redis.

Tworzenie niestandardowej pamięci podręcznej Redis Cache

Usługa Azure Cache for Redis działa jako fasada bazowych serwerów Redis. Jeśli potrzebujesz zaawansowanej konfiguracji, która nie jest objęta pamięcią podręczną Azure Redis Cache (np. pamięcią podręczną większą niż 53 GB), możesz skompilować i hostować własne serwery Redis przy użyciu maszyn wirtualnych platformy Azure.

Jest to potencjalnie złożony proces, ponieważ może być konieczne utworzenie kilku maszyn wirtualnych do działania jako węzły podstawowe i podrzędne, jeśli chcesz zaimplementować replikację. Ponadto jeśli chcesz utworzyć klaster, potrzebujesz wielu prawyborów i serwerów podrzędnych. Minimalna topologia replikacji klastrowanej zapewniająca wysoki stopień dostępności i skalowalności obejmuje co najmniej sześć maszyn wirtualnych zorganizowanych jako trzy pary serwerów podstawowych/podrzędnych (klaster musi zawierać co najmniej trzy węzły podstawowe).

Każda para podstawowa/podrzędna powinna znajdować się blisko siebie, aby zminimalizować opóźnienie. Niemniej jednak każdy zestaw par może być uruchomiony w innym centrum danych platformy Azure w innym regionie, jeśli chcesz zlokalizować zbuforowane dane blisko aplikacji, które najprawdopodobniej będą ich używać. Aby uzyskać przykład tworzenia i konfigurowania węzła Redis uruchomionego jako maszyna wirtualna platformy Azure, zobacz Running Redis on a CentOS Linux VM in Azure (Uruchamianie węzła Redis na maszynie wirtualnej z systemem CentOS Linux na platformie Azure).

Uwaga

Jeśli w ten sposób zaimplementujesz własną pamięć podręczną Redis Cache, odpowiadasz za monitorowanie i zabezpieczanie usługi oraz zarządzanie nią.

Partycjonowanie pamięci podręcznej Redis Cache

Partycjonowanie pamięci podręcznej obejmuje dzielenie pamięci podręcznej na wiele komputerów. Ta struktura zapewnia kilka korzyści w porównaniu do korzystania z jednego serwera pamięci podręcznej, w tym:

  • Możliwość utworzenia pamięci podręcznej, która będzie znacznie większa od pamięci przechowywanej na jednym serwerze.
  • Rozproszenie danych na kilka serwerów poprawia dostępność. Jeśli jeden z serwerów ulegnie awarii lub stanie się niedostępny, dane na nim przechowywane będą niedostępne, ale nadal będzie można uzyskać dostęp do danych na pozostałych serwerach. W przypadku pamięci podręcznej nie ma to kluczowego znaczenia, ponieważ buforowane dane są tylko przejściową kopią danych przechowywanych w bazie danych. Dane w pamięci podręcznej na serwerze, który stał się niedostępny, można zbuforować na innym serwerze.
  • Rozłożenie obciążenia na serwerach, a więc poprawa wydajności i skalowalności.
  • Geograficzne umiejscowienie danych w pobliżu użytkowników, którzy uzyskują do nich dostęp, co umożliwia zredukowanie opóźnień.

W przypadku pamięci podręcznej najpopularniejszą formą partycjonowania jest fragmentowanie. W tej strategii każda partycja (fragment) stanowi samodzielną pamięć podręczną Redis Cache. Dane są przekierowywane do konkretnej partycji przy użyciu logiki fragmentowania, która może używać różnych podejść na potrzeby dystrybucji danych. Artykuł Wzorzec fragmentowania zawiera więcej informacji o implementowaniu fragmentowania.

Aby zaimplementować partycjonowanie w pamięci podręcznej Redis Cache, możesz przyjąć jedno z następujących podejść:

  • Routing zapytania po stronie serwera. W tej metodzie aplikacja kliencka wysyła żądanie do dowolnego serwera Redis wchodzącego w skład pamięci podręcznej (prawdopodobnie najbliższego serwera). Na każdym serwerze Redis przechowywane są metadane opisujące znajdującą się na nim partycję, a także informacje o partycjach znajdujących się na pozostałych serwerach. Serwer Redis sprawdza żądanie klienta. Jeśli serwer może obsłużyć żądanie lokalnie, wykonuje żądaną operację. W przeciwnym razie przekaże żądanie do właściwego serwera. Ten model jest implementowany przez klastrowanie Redis i został bardziej szczegółowo opisany na stronie Redis cluster tutorial (Samouczek dotyczący klastra Redis) w witrynie internetowej usługi Redis. Klastrowanie Redis jest przezroczyste dla aplikacji klienckich. Ponadto serwery Redis można dodawać do klastra (oraz ponownie partycjonować dane) bez potrzeby ponownej konfiguracji klientów.
  • Partycjonowanie po stronie klienta. W tym modelu aplikacja kliencka zawiera logikę (prawdopodobnie w formie biblioteki), która przekierowuje żądania do odpowiedniego serwera Redis. Tego podejścia można użyć z usługą Azure Cache for Redis. Utwórz wiele usługi Azure Cache for Redis (po jednej dla każdej partycji danych) i zaimplementuj logikę po stronie klienta, która kieruje żądania do poprawnej pamięci podręcznej. Jeśli schemat partycjonowania ulegnie zmianie (jeśli na przykład zostanie utworzona dodatkowa usługa Azure Cache for Redis), aplikacje klienckie mogą wymagać ponownej konfiguracji.
  • Partycjonowanie ze wsparciem serwera proxy. W tym schemacie aplikacje klienckie wysyłają żądania do pośredniczącej usługi proxy, która rozumie sposób partycjonowania danych i następnie przekierowuje żądanie do odpowiedniego serwera Redis. Tego podejścia można również użyć z usługą Azure Cache for Redis; usługę proxy można zaimplementować jako usługę w chmurze platformy Azure. To podejście cechuje się dodatkowym poziomem złożoności związanym z implementowaniem usługi, a wykonanie żądań może zająć dłużej niż w przypadku użycia partycjonowania po stronie klienta.

Strona Partitioning: how to split data among multiple Redis instances (Partycjonowanie: jak dzielić dane pomiędzy wystąpienia Redis) w witrynie internetowej usługi Redis zawiera więcej informacji o implementowaniu partycjonowania przy użyciu usługi Redis.

Implementowanie aplikacji klienckich pamięci podręcznej Redis Cache

Usługa Redis obsługuje aplikacje klienckie pisane w wielu językach programowania. Jeśli tworzysz nowe aplikacje przy użyciu programu .NET Framework, zalecamy użycie biblioteki klienta StackExchange.Redis. Ta biblioteka zawiera model obiektu .NET Framework zapewniający abstrakcję szczegółów dotyczących nawiązywania połączenia z serwerem Redis, wysyłania poleceń i odbierania odpowiedzi. Jest ona dostępna w programie Visual Studio jako pakiet NuGet. Tej samej biblioteki można używać do nawiązywania połączenia z usługą Azure Cache for Redis lub niestandardową pamięcią podręczną Redis hostowaną na maszynie wirtualnej.

Aby połączyć się z serwerem Redis, używa się statycznej metody Connect klasy ConnectionMultiplexer. Połączenie utworzone za pomocą tej metody jest zaprojektowane z myślą o stosowaniu w całym cyklu życia aplikacji klienckiej. Z tego samego połączenia może korzystać wiele wątków jednocześnie. Nie należy ponownie łączyć się i rozłączać za każdym razem, gdy wykonujesz operację redis, ponieważ może to obniżyć wydajność.

Możesz określić parametry połączenia, np. adres hosta Redis i hasło. Jeśli używasz usługi Azure Cache for Redis, hasło jest kluczem podstawowym lub pomocniczym generowanym dla usługi Azure Cache for Redis przy użyciu witryny Azure Portal.

Po nawiązaniu połączenia z serwerem Redis możesz uzyskać dojście do bazy danych Redis, która działa jako pamięć podręczna. Połączenie Redis zapewnia w tym celu metodę GetDatabase. Następnie możesz pobierać elementy z pamięci podręcznej i zapisywać dane w pamięci podręcznej przy użyciu metod StringGet i StringSet. Te metody oczekują klucza jako parametru i zwracają element z pamięci podręcznej zawierającej pasującą wartość (StringGet) lub dodają element do pamięci podręcznej z tym kluczem (StringSet).

W zależności od lokalizacji serwera Redis wiele operacji może powodować opóźnienia, gdy żądanie jest przekazywane do serwera, a odpowiedź jest zwracana do klienta. Biblioteka StackExchange zawiera asynchroniczne wersje wielu metod, dzięki którym aplikacje klienckie mogą zapewniać odpowiedni czas reakcji. Te metody obsługują wzorzec asynchroniczny oparty na zadaniach w programie .NET Framework.

Poniższy fragment kodu przedstawia metodę o nazwie RetrieveItem. Przykład ilustruje implementację wzorca z odkładaniem do pamięci podręcznej korzystającą z usługi Redis i biblioteki StackExchange. Metoda przyjmuje wartość klucza ciągu i próbuje pobrać odpowiedni element z pamięci podręcznej Redis Cache poprzez wywołanie metody StringGetAsync (asynchroniczna wersja metody StringGet).

Jeśli element nie zostanie znaleziony, zostanie pobrany z bazowego źródła danych przy użyciu GetItemFromDataSourceAsync metody (która jest metodą lokalną, a nie częścią biblioteki StackExchange). Następnie element zostanie dodany do pamięci podręcznej przy użyciu metody StringSetAsync. Dzięki temu będzie można go pobrać szybciej następnym razem.

// Connect to the Azure Redis cache
ConfigurationOptions config = new ConfigurationOptions();
config.EndPoints.Add("<your DNS name>.redis.cache.windows.net");
config.Password = "<Redis cache key from management portal>";
ConnectionMultiplexer redisHostConnection = ConnectionMultiplexer.Connect(config);
IDatabase cache = redisHostConnection.GetDatabase();
...
private async Task<string> RetrieveItem(string itemKey)
{
    // Attempt to retrieve the item from the Redis cache
    string itemValue = await cache.StringGetAsync(itemKey);

    // If the value returned is null, the item was not found in the cache
    // So retrieve the item from the data source and add it to the cache
    if (itemValue == null)
    {
        itemValue = await GetItemFromDataSourceAsync(itemKey);
        await cache.StringSetAsync(itemKey, itemValue);
    }

    // Return the item
    return itemValue;
}

Metody StringGet i StringSet nie są ograniczone do pobierania ani przechowywania wartości ciągów. Mogą przyjąć dowolny element, który jest serializowany jako tablica bajtów. Jeśli musisz zapisać obiekt .NET, możesz serializować go jako strumień bajtów i użyć metody StringSet, aby zapisać go w pamięci podręcznej.

Podobnie możesz odczytać obiekt z pamięci podręcznej przy użyciu metody StringGet i deserializować go jako obiekt .NET. Poniższy kod przedstawia zestaw metod rozszerzenia interfejsu IDatabase (metoda GetDatabase połączenia Redis zwraca obiekt IDatabase) oraz przykładowy kod, który używa tych metod do odczytu i zapisu obiektu BlogPost w pamięci podręcznej:

public static class RedisCacheExtensions
{
    public static async Task<T> GetAsync<T>(this IDatabase cache, string key)
    {
        return Deserialize<T>(await cache.StringGetAsync(key));
    }

    public static async Task<object> GetAsync(this IDatabase cache, string key)
    {
        return Deserialize<object>(await cache.StringGetAsync(key));
    }

    public static async Task SetAsync(this IDatabase cache, string key, object value)
    {
        await cache.StringSetAsync(key, Serialize(value));
    }

    static byte[] Serialize(object o)
    {
        byte[] objectDataAsStream = null;

        if (o != null)
        {
            var jsonString = JsonSerializer.Serialize(o);
            objectDataAsStream = Encoding.ASCII.GetBytes(jsonString);
        }

        return objectDataAsStream;
    }

    static T Deserialize<T>(byte[] stream)
    {
        T result = default(T);

        if (stream != null)
        {
            var jsonString = Encoding.ASCII.GetString(stream);
            result = JsonSerializer.Deserialize<T>(jsonString);
        }

        return result;
    }
}

Poniższy kod ilustruje metodę o nazwie RetrieveBlogPost, która używa tych metod rozszerzenia do odczytywania i zapisywania obiektu BlogPost z możliwością serializacji do pamięci podręcznej zgodnie ze wzorcem odkładania do pamięci podręcznej:

// The BlogPost type
public class BlogPost
{
    private HashSet<string> tags;

    public BlogPost(int id, string title, int score, IEnumerable<string> tags)
    {
        this.Id = id;
        this.Title = title;
        this.Score = score;
        this.tags = new HashSet<string>(tags);
    }

    public int Id { get; set; }
    public string Title { get; set; }
    public int Score { get; set; }
    public ICollection<string> Tags => this.tags;
}
...
private async Task<BlogPost> RetrieveBlogPost(string blogPostKey)
{
    BlogPost blogPost = await cache.GetAsync<BlogPost>(blogPostKey);
    if (blogPost == null)
    {
        blogPost = await GetBlogPostFromDataSourceAsync(blogPostKey);
        await cache.SetAsync(blogPostKey, blogPost);
    }

    return blogPost;
}

Usługa Redis obsługuje przetwarzanie potokowe poleceń, jeśli aplikacja kliencka wysyła wiele żądań asynchronicznych. Usługa Redis może multipleksować żądania używające tego samego połączenia, zamiast odbierać polecenia i odpowiadać na nie w dokładnej kolejności.

Takie podejście pomaga redukować opóźnienia poprzez bardziej wydajne wykorzystanie sieci. Poniższy fragment kodu przedstawia przykład, który odbiera szczegóły dwóch klientów jednocześnie. Kod przesyła dwa żądania, a następnie wykonuje operacje przetwarzania (nie są pokazane) przed oczekiwaniem na odebranie wyników. Metoda Wait obiektu pamięci podręcznej jest podobna do metody Task.Wait programu .NET Framework:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
var task1 = cache.StringGetAsync("customer:1");
var task2 = cache.StringGetAsync("customer:2");
...
var customer1 = cache.Wait(task1);
var customer2 = cache.Wait(task2);

Aby uzyskać dodatkowe informacje na temat pisania aplikacji klienckich, które mogą używać usługi Azure Cache for Redis, zobacz dokumentację usługi Azure Cache for Redis. Więcej informacji można również znaleźć na stronie StackExchange.Redis.

Strona Pipelines and multiplexers (Potoki i multipleksery) w tej samej witrynie internetowej zawiera więcej informacji o operacjach asynchronicznych oraz tworzeniu potoków przy użyciu usługi Redis i biblioteki StackExchange.

Używanie buforowania Redis

Najprostsze użycie usługi Redis na potrzeby buforowania obejmuje pary klucz-wartość, w których wartość jest niezinterpretowanym ciągiem dowolnej długości mogącym zawierać dowolne dane binarne. (Zasadniczo jest to tablica bajtów, które mogą być traktowane jako ciąg). Ten scenariusz został przedstawiony w sekcji Implementowanie aplikacji klienckich pamięci podręcznej Redis Cache wcześniej w tym artykule.

Pamiętaj, że klucze również zawierają niezinterpretowane dane, więc możesz użyć dowolnych informacji binarnych jako klucza. Jednak im dłuższy jest klucz, tym więcej miejsca zużyje jego przechowywanie oraz tym dłużej będzie trwało wykonywanie operacji wyszukiwania. Aby zapewnić użyteczność i prostotę konserwacji, zaprojektuj przestrzeń kluczy uważnie i użyj znaczących (ale nie pełnych) kluczy.

Przykładowo użyj kluczy strukturalnych, takich jak „klient:100” do przedstawienia klucza klienta o identyfikatorze 100, zamiast stosować klucz „100”. Taki schemat umożliwia łatwe rozróżnienie wartości przechowujących różne typy danych. Przykładowo możesz też użyć klucza „zamówienia:100”, aby przedstawić klucz zamówienia o identyfikatorze 100.

Oprócz jednowymiarowych ciągów binarnych wartość w parze klucz-wartość usługi Redis może też przechowywać bardziej ustrukturyzowane dane, w tym listy, zestawy (sortowane i niesortowane) oraz skróty. Usługa Redis zapewnia kompleksowy zestaw poleceń do manipulowania tymi typami. Wiele z tych poleceń jest dostępnych w aplikacjach .NET Framework za pośrednictwem biblioteki klienta, np. StackExchange. Strona An introduction to Redis data types and abstractions (Wprowadzenie do typów danych i abstrakcji w usłudze Redis) w witrynie internetowej usługi Redis zapewnia bardziej szczegółowe informacje o tych typach oraz poleceniach, których można użyć do manipulowania nimi.

W tej sekcji zawarto podsumowanie niektórych typowych przypadków użycia dotyczących tych typów danych i poleceń.

Wykonywanie operacji niepodzielnych i wsadowych

Usługa Redis obsługuje szereg niepodzielnych operacji pobierania i ustawiania wykonywanych względem wartości ciągu. Operacje te usuwają potencjalne zagrożenia związane z wyścigiem, które mogą wystąpić podczas używania oddzielnych poleceń GET i SET. Dostępne operacje obejmują:

  • INCR, INCRBY, DECR i DECRBY, które wykonują niepodzielne operacje inkrementacji i dekrementacji na liczbowych wartościach danych w postaci liczb całkowitych. Biblioteka StackExchange zapewnia przeciążone wersje metod IDatabase.StringIncrementAsync i IDatabase.StringDecrementAsync do wykonywania tych operacji i zwracania wartości wynikowej przechowywanej w pamięci podręcznej. Poniższy fragment kodu ilustruje sposób użycia tych metod:

    ConnectionMultiplexer redisHostConnection = ...;
    IDatabase cache = redisHostConnection.GetDatabase();
    ...
    await cache.StringSetAsync("data:counter", 99);
    ...
    long oldValue = await cache.StringIncrementAsync("data:counter");
    // Increment by 1 (the default)
    // oldValue should be 100
    
    long newValue = await cache.StringDecrementAsync("data:counter", 50);
    // Decrement by 50
    // newValue should be 50
    
  • GETSET, która pobiera wartość skojarzoną z kluczem i zmienia ją na nową wartość. Biblioteka StackExchange zapewnia dostępność tej operacji za pośrednictwem metody IDatabase.StringGetSetAsync. Poniższy fragment kodu przedstawia przykład tej metody. Ten kod zwraca bieżącą wartość skojarzoną z kluczem „data:counter” z poprzedniego przykładu. Następnie resetuje wartość tego klucza z powrotem do zera w ramach tej samej operacji:

    ConnectionMultiplexer redisHostConnection = ...;
    IDatabase cache = redisHostConnection.GetDatabase();
    ...
    string oldValue = await cache.StringGetSetAsync("data:counter", 0);
    
  • MGET i MSET, które mogą zwrócić lub zmienić zestaw wartości ciągów w ramach jednej operacji. Metody IDatabase.StringGetAsync i IDatabase.StringSetAsync są przeciążone, aby obsługiwać tę funkcję, jak pokazano na poniższym przykładzie:

    ConnectionMultiplexer redisHostConnection = ...;
    IDatabase cache = redisHostConnection.GetDatabase();
    ...
    // Create a list of key-value pairs
    var keysAndValues =
        new List<KeyValuePair<RedisKey, RedisValue>>()
        {
            new KeyValuePair<RedisKey, RedisValue>("data:key1", "value1"),
            new KeyValuePair<RedisKey, RedisValue>("data:key99", "value2"),
            new KeyValuePair<RedisKey, RedisValue>("data:key322", "value3")
        };
    
    // Store the list of key-value pairs in the cache
    cache.StringSet(keysAndValues.ToArray());
    ...
    // Find all values that match a list of keys
    RedisKey[] keys = { "data:key1", "data:key99", "data:key322"};
    // values should contain { "value1", "value2", "value3" }
    RedisValue[] values = cache.StringGet(keys);
    
    

Ponadto możesz połączyć wiele operacji w jedną transakcję Redis, jak opisano w sekcji Transakcje i partie w usłudze Redis wcześniej w tym artykule. Biblioteka StackExchange zapewnia obsługę transakcji za pośrednictwem interfejsu ITransaction.

Obiekt ITransaction możesz utworzyć przy użyciu metody IDatabase.CreateTransaction. Polecenia transakcji wywołuje się przy użyciu metod dostarczonych przez obiekt ITransaction.

Interfejs ITransaction zapewnia dostęp do zestawu metod podobnych do metod, do których interfejs IDatabase uzyskuje dostęp, z wyjątkiem tego, że wszystkie metody są asynchroniczne. Oznacza to, że są wykonywane tylko po wywołaniu ITransaction.Execute metody. Wartość zwrócona przez metodę ITransaction.Execute wskazuje, czy transakcja została utworzona pomyślnie (true), czy też utworzenie nie powiodło się (false).

Poniższy fragment kodu przedstawia przykład inkrementacji i dekrementacji dwóch liczników w ramach tej samej transakcji:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
ITransaction transaction = cache.CreateTransaction();
var tx1 = transaction.StringIncrementAsync("data:counter1");
var tx2 = transaction.StringDecrementAsync("data:counter2");
bool result = transaction.Execute();
Console.WriteLine("Transaction {0}", result ? "succeeded" : "failed");
Console.WriteLine("Result of increment: {0}", tx1.Result);
Console.WriteLine("Result of decrement: {0}", tx2.Result);

Pamiętaj, że transakcje Redis nie są takie same jak transakcje w relacyjnych bazach danych. Metoda Execute po prostu kolejkuje wszystkie polecenia do uruchomienia wchodzące w skład transakcji. Jeśli jakiekolwiek polecenie jest błędnie sformułowane, transakcja zostanie zatrzymana. Jeśli wszystkie polecenia zostaną pomyślnie dodane do kolejki, każde polecenie zostanie uruchomione asynchronicznie.

Jeśli którekolwiek z poleceń zakończy się niepowodzeniem, inne nadal będą przetwarzane. Jeśli chcesz sprawdzić, czy polecenie zostało pomyślnie zakończone, musisz pobrać wyniki polecenia przy użyciu właściwości Result odpowiedniego zadania, jak pokazano w powyższym przykładzie. Odczytanie właściwości Result spowoduje zablokowanie wątku wywołującego do momentu zakończenia zadania.

Aby uzyskać więcej informacji, zobacz Transakcje w usłudze Redis.

Podczas wykonywania operacji wsadowych możesz użyć interfejsu IBatch biblioteki StackExchange. Interfejs zapewnia dostęp do zestawu metod podobnych do tych, do których uzyskuje dostęp interfejs IDatabase. Różnica polega na tym, że wszystkie metody są asynchroniczne.

Obiekt IBatch należy utworzyć przy użyciu metody IDatabase.CreateBatch, a następnie uruchomić operację wsadową przy użyciu metody IBatch.Execute, jak pokazano w poniższym przykładzie. Ten kod po prostu ustawia wartość ciągu, przeprowadza inkrementację i dekrementację tych samych liczników, które zostały użyte w poprzednim przykładzie, a następnie wyświetla wyniki:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
IBatch batch = cache.CreateBatch();
batch.StringSetAsync("data:key1", 11);
var t1 = batch.StringIncrementAsync("data:counter1");
var t2 = batch.StringDecrementAsync("data:counter2");
batch.Execute();
Console.WriteLine("{0}", t1.Result);
Console.WriteLine("{0}", t2.Result);

Ważne jest, aby zrozumieć, że w przeciwieństwie do transakcji, jeśli polecenie w partii nie powiedzie się, ponieważ jest źle sformułowane, inne polecenia mogą nadal działać. Metoda IBatch.Execute nie zwraca żadnych wskazówek dotyczących powodzenia ani niepowodzenia.

Wykonywanie operacji typu „uruchom i zapomnij” w pamięci podręcznej

Usługa Redis obsługuje operacje typu „uruchom i zapomnij” przy użyciu flag poleceń. W takiej sytuacji klient po prostu inicjuje operację, ale nie interesuje się wynikiem i nie czeka na ukończenie polecenia. W poniższym przykładzie przedstawiono sposób wykonywania polecenia INCR jako operacji typu „uruchom i zapomnij”:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
await cache.StringSetAsync("data:key1", 99);
...
cache.StringIncrement("data:key1", flags: CommandFlags.FireAndForget);

Określanie automatycznie wygasających kluczy

Gdy zapisujesz element w pamięci podręcznej Redis Cache, możesz określić limit czasu, po upływie którego element zostanie automatycznie usunięty z pamięci podręcznej. Możesz też wysłać zapytanie o czas pozostały do wygaśnięcia klucza przy użyciu polecenia TTL. To polecenie jest dostępne dla aplikacji StackExchange przy użyciu metody IDatabase.KeyTimeToLive.

Poniższy fragment kodu przedstawia sposób ustawienia dla klucza czasu wygaśnięcia wynoszącego 20 sekund oraz wysłania zapytania o pozostały czas istnienia klucza:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
// Add a key with an expiration time of 20 seconds
await cache.StringSetAsync("data:key1", 99, TimeSpan.FromSeconds(20));
...
// Query how much time a key has left to live
// If the key has already expired, the KeyTimeToLive function returns a null
TimeSpan? expiry = cache.KeyTimeToLive("data:key1");

Czas wygaśnięcia możesz też ustawić na konkretną datę i godzinę przy użyciu polecenia EXPIRE, które jest dostępne w bibliotece StackExchange jako metoda KeyExpireAsync:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
// Add a key with an expiration date of midnight on 1st January 2015
await cache.StringSetAsync("data:key1", 99);
await cache.KeyExpireAsync("data:key1",
    new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc));
...

Napiwek

Element z pamięci podręcznej możesz ręcznie usunąć przy użyciu polecenia DEL, które jest dostępne za pośrednictwem biblioteki StackExchange jako metoda IDatabase.KeyDeleteAsync.

Używanie tagów do korelacji krzyżowej elementów pamięci podręcznej

Zestaw Redis to zbiór wielu elementów współdzielących jeden klucz. Zestaw możesz utworzyć przy użyciu polecenia SADD. Elementy w zestawie możesz pobrać przy użyciu polecenia SMEMBERS. Biblioteka StackExchange implementuje polecenie SADD przy użyciu metody IDatabase.SetAddAsync, a polecenie SMEMBERS przy użyciu metody IDatabase.SetMembersAsync.

Ponadto możesz połączyć istniejące zestawy, aby utworzyć nowe zestawy, przy użyciu poleceń SDIFF (różnica zestawów), SINTER (części wspólne zestawów) oraz SUNION (suma zestawów). Biblioteka StackExchange łączy te operacje w metodzie IDatabase.SetCombineAsync. Pierwszy parametr w tej metodzie określa operację zestawu do wykonania.

Poniższe fragmenty kodu przedstawiają, w jaki sposób zestawy mogą być użyteczne do szybkiego zapisywania i pobierania kolekcji powiązanych elementów. Ten kod używa typu BlogPost, który został opisany w sekcji Implementowanie aplikacji klienckich pamięci podręcznej Redis Cache wcześniej w tym artykule.

Obiekt BlogPost zawiera cztery pola — identyfikator, tytuł, wynik klasyfikacji oraz kolekcję tagów. Pierwszy poniższy fragment kodu przedstawia przykładowe dane służące do wypełniania listy obiektów BlogPost w kodzie C#:

List<string[]> tags = new List<string[]>
{
    new[] { "iot","csharp" },
    new[] { "iot","azure","csharp" },
    new[] { "csharp","git","big data" },
    new[] { "iot","git","database" },
    new[] { "database","git" },
    new[] { "csharp","database" },
    new[] { "iot" },
    new[] { "iot","database","git" },
    new[] { "azure","database","big data","git","csharp" },
    new[] { "azure" }
};

List<BlogPost> posts = new List<BlogPost>();
int blogKey = 0;
int numberOfPosts = 20;
Random random = new Random();
for (int i = 0; i < numberOfPosts; i++)
{
    blogKey++;
    posts.Add(new BlogPost(
        blogKey,                  // Blog post ID
        string.Format(CultureInfo.InvariantCulture, "Blog Post #{0}",
            blogKey),             // Blog post title
        random.Next(100, 10000),  // Ranking score
        tags[i % tags.Count]));   // Tags--assigned from a collection
                                  // in the tags list
}

Tagi dla każdego obiektu BlogPost możesz zapisać jako zestaw w pamięci podręcznej Redis Cache i skojarzyć każdy zestaw z identyfikatorem obiektu BlogPost. Dzięki temu aplikacja będzie w stanie szybko znaleźć wszystkie tagi, które należą do określonego wpisu w blogu. Aby umożliwić wyszukiwanie w odwrotnym kierunku i znajdowanie wszystkich wpisów w blogu, które współdzielą określony tag, możesz utworzyć kolejny zestaw, który zawiera wpisy w blogu odwołujące się do identyfikatora tagu w kluczu:

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
// Tags are easily represented as Redis Sets
foreach (BlogPost post in posts)
{
    string redisKey = string.Format(CultureInfo.InvariantCulture,
        "blog:posts:{0}:tags", post.Id);
    // Add tags to the blog post in Redis
    await cache.SetAddAsync(
        redisKey, post.Tags.Select(s => (RedisValue)s).ToArray());

    // Now do the inverse so we can figure out which blog posts have a given tag
    foreach (var tag in post.Tags)
    {
        await cache.SetAddAsync(string.Format(CultureInfo.InvariantCulture,
            "tag:{0}:blog:posts", tag), post.Id);
    }
}

Te struktury umożliwiają bardzo wydajne wykonywanie wielu typowych zapytań. Przykładowo możesz znaleźć i wyświetlić wszystkie tagi dla wpisu w blogu 1 w następujący sposób:

// Show the tags for blog post #1
foreach (var value in await cache.SetMembersAsync("blog:posts:1:tags"))
{
    Console.WriteLine(value);
}

Aby znaleźć wszystkie tagi wspólne dla wpisu w blogu 1 i wpisu w blogu 2, możesz wykonać operację części wspólnych zestawu w następujący sposób:

// Show the tags in common for blog posts #1 and #2
foreach (var value in await cache.SetCombineAsync(SetOperation.Intersect, new RedisKey[]
    { "blog:posts:1:tags", "blog:posts:2:tags" }))
{
    Console.WriteLine(value);
}

Ponadto możesz znaleźć wszystkie wpisy w blogu zawierające określony tag:

// Show the ids of the blog posts that have the tag "iot".
foreach (var value in await cache.SetMembersAsync("tag:iot:blog:posts"))
{
    Console.WriteLine(value);
}

Znajdowanie ostatnio używanych elementów

Typowe zadanie wymagane przez wiele aplikacji to znalezienie ostatnio używanych elementów. Przykładowo witryna z blogami może wyświetlać informacje o ostatnio przeczytanych wpisach w blogu.

Tę funkcję można zaimplementować przy użyciu listy Redis. Lista Redis zawiera wiele elementów, które współdzielą ten sam klucz. Lista działa jak podwójnie zakończona kolejka. Elementy możesz wypychać do dowolnego końca listy przy użyciu poleceń LPUSH (wypchnięcie w lewo) oraz RPUSH (wypchnięcie w prawo). Elementy możesz pobrać z dowolnego końca listy przy użyciu poleceń LPOP i RPOP. Możesz też zwrócić zestaw elementów przy użyciu poleceń LRANGE i RRANGE.

Poniższe fragmenty kodu przedstawiają sposób wykonywania tych operacji przy użyciu biblioteki StackExchange. Ten kod używa typu BlogPost z poprzednich przykładów. Kiedy wpis w blogu zostanie odczytany przez użytkownika, metoda IDatabase.ListLeftPushAsync wypchnie tytuł wpisu w blogu do listy skojarzonej z kluczem „blog:recent_posts” w pamięci podręcznej Redis Cache.

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
string redisKey = "blog:recent_posts";
BlogPost blogPost = ...; // Reference to the blog post that has just been read
await cache.ListLeftPushAsync(
    redisKey, blogPost.Title); // Push the blog post onto the list

Tytuły kolejnych odczytanych wpisów w blogu również zostaną wypchnięte do tej samej listy. Kolejność na liście jest określana zgodnie z sekwencją dodawania tytułów. Ostatnio przeczytane wpisy w blogu znajdują się na lewym końcu listy. (Jeśli ten sam wpis w blogu zostanie odczytany wielokrotnie, będzie mieć wiele wpisów na liście).

Tytuły ostatnio odczytanych wpisów możesz wyświetlić przy użyciu metody IDatabase.ListRange. Ta metoda pobiera klucz zawierający listę, punkt początkowy i punkt końcowy. Poniższy kod pobiera tytuły 10 wpisów w blogu (elementy od 0 do 9) z lewego końca listy:

// Show latest ten posts
foreach (string postTitle in await cache.ListRangeAsync(redisKey, 0, 9))
{
    Console.WriteLine(postTitle);
}

Pamiętaj, że ListRangeAsync metoda nie usuwa elementów z listy. Aby to zrobić, można użyć metod IDatabase.ListLeftPopAsync i IDatabase.ListRightPopAsync.

Aby zapobiegać nieograniczonemu wzrostowi listy, możesz okresowo usuwać elementy i przycinać listę. Poniższy fragment kodu przedstawia sposób usunięcia wszystkich elementów z listy poza pięcioma elementami z lewej strony:

await cache.ListTrimAsync(redisKey, 0, 5);

Implementowanie rankingu

Domyślnie elementy w zestawie nie są przechowywane w żadnej określonej kolejności. Aby utworzyć uporządkowany zestaw, możesz skorzystać z polecenia ZADD (metoda IDatabase.SortedSetAdd w bibliotece StackExchange). Elementy są porządkowane przy użyciu wartości liczbowej nazywanej oceną, która jest podawana jako parametr w poleceniu.

Poniższy fragment kodu dodaje tytuł wpisu w blogu do uporządkowanej listy. W tym przykładzie każdy wpis w blogu ma również pole oceny zawierające klasyfikację wpisu w blogu.

ConnectionMultiplexer redisHostConnection = ...;
IDatabase cache = redisHostConnection.GetDatabase();
...
string redisKey = "blog:post_rankings";
BlogPost blogPost = ...; // Reference to a blog post that has just been rated
await cache.SortedSetAddAsync(redisKey, blogPost.Title, blogPost.Score);

Tytuły i ocenę wpisów w blogu możesz pobrać w kolejności rosnącej oceny przy użyciu metody IDatabase.SortedSetRangeByRankWithScores:

foreach (var post in await cache.SortedSetRangeByRankWithScoresAsync(redisKey))
{
    Console.WriteLine(post);
}

Uwaga

Biblioteka StackExchange udostępnia również metodę IDatabase.SortedSetRangeByRankAsync , która zwraca dane w kolejności oceny, ale nie zwraca wyników.

Elementy możesz też pobrać w kolejności malejącej oceny oraz ograniczyć liczbę zwracanych elementów poprzez podanie dodatkowych parametrów metody IDatabase.SortedSetRangeByRankWithScoresAsync. Kolejny przykład wyświetla tytuły i ocenę 10 wpisów w blogu o najwyższej randze:

foreach (var post in await cache.SortedSetRangeByRankWithScoresAsync(
                               redisKey, 0, 9, Order.Descending))
{
    Console.WriteLine(post);
}

W następnym przykładzie użyto metody IDatabase.SortedSetRangeByScoreWithScoresAsync, dzięki której można ograniczyć liczbę zwracanych elementów do tych, które mieszczą się w danym zakresie oceny:

// Blog posts with scores between 5000 and 100000
foreach (var post in await cache.SortedSetRangeByScoreWithScoresAsync(
                               redisKey, 5000, 100000))
{
    Console.WriteLine(post);
}

Wysyłanie komunikatów przy użyciu kanałów

Oprócz działania w roli pamięci podręcznej serwer Redis zapewnia funkcję wysyłania komunikatów za pośrednictwem wysoce wydajnego mechanizmu wydawca/subskrybent. Aplikacje klienckie mogą subskrybować kanał, a inne aplikacje lub usługi mogą publikować komunikaty w kanale. Następnie subskrybujące aplikacje odbiorą te komunikaty i będą mogły je przetwarzać.

Usługa Redis zapewnia polecenie SUBSCRIBE dla aplikacji klienckich, które umożliwia subskrybowanie kanałów. To polecenie oczekuje nazwy co najmniej jednego kanału, na którym aplikacja będzie akceptować komunikaty. Biblioteka StackExchange obejmuje interfejs ISubscription, który umożliwia aplikacji .NET Framework subskrybowanie kanałów i publikowanie w nich.

Obiekt ISubscription możesz utworzyć przy użyciu metody GetSubscriber połączenia z serwerem Redis. Następnie możesz nasłuchiwać komunikatów na kanale przy użyciu metody SubscribeAsync tego obiektu. Poniższy przykład kodu przedstawia sposób subskrybowania kanału o nazwie „messages:blogPosts”:

ConnectionMultiplexer redisHostConnection = ...;
ISubscriber subscriber = redisHostConnection.GetSubscriber();
...
await subscriber.SubscribeAsync("messages:blogPosts", (channel, message) => Console.WriteLine("Title is: {0}", message));

Pierwszy parametr metody Subscribe jest nazwą kanału. Ta nazwa jest zgodna z tymi samymi konwencjami, które są używane przez klucze w pamięci podręcznej. Nazwa może zawierać dowolne dane binarne, ale zalecamy użycie stosunkowo krótkich, znaczących ciągów, aby zapewnić dobrą wydajność i łatwość konserwacji.

Pamiętaj również, że przestrzeń nazw używana przez kanały jest oddzielna od przestrzeni używanej przez klucze. Oznacza to, że możesz mieć kanały i klucze o tej samej nazwie, chociaż utrudni to utrzymanie kodu aplikacji.

Drugim parametrem jest delegat akcji. Ten delegat jest uruchamiany asynchronicznie za każdym razem, gdy nowy komunikat pojawi się w kanale. Ten przykład po prostu wyświetla komunikat w konsoli (komunikat będzie zawierać tytuł wpisu w blogu).

Aby opublikować w kanale, aplikacja może użyć polecenia Redis PUBLISH. Biblioteka StackExchange zapewnia metodę IServer.PublishAsync, która umożliwia wykonanie tej operacji. Następny fragment kodu przedstawia sposób publikowania komunikatu w kanale „messages:blogPosts”:

ConnectionMultiplexer redisHostConnection = ...;
ISubscriber subscriber = redisHostConnection.GetSubscriber();
...
BlogPost blogPost = ...;
subscriber.PublishAsync("messages:blogPosts", blogPost.Title);

Istnieje kilka kwestii dotyczących mechanizmu publikowania/subskrypcji, które należy zrozumieć:

  • Wielu subskrybentów może subskrybować ten sam kanał i wszyscy otrzymają komunikaty opublikowane w tym kanale.
  • Subskrybenci otrzymują tylko komunikaty, które zostały opublikowane po zasubskrybowaniu. Kanały nie są buforowane, a po opublikowaniu komunikatu infrastruktura redis wypycha komunikat do każdego subskrybenta, a następnie usuwa go.
  • Domyślnie komunikaty są odbierane przez subskrybentów w kolejności, w której są wysyłane. W wysoce aktywnym systemie o dużej liczbie komunikatów oraz wielu subskrybentach i wydawcach gwarantowane sekwencyjne dostarczanie komunikatów może spowolnić działanie systemu. Jeśli każdy komunikat jest niezależny, a ich kolejność nie jest ważna, możesz włączyć równoczesne przetwarzanie w systemie Redis, które poprawi czas reakcji. Ten efekt możesz osiągnąć w kliencie StackExchange, ustawiając atrybut PreserveAsyncOrder połączenia używany przez subskrybenta na wartość false:
ConnectionMultiplexer redisHostConnection = ...;
redisHostConnection.PreserveAsyncOrder = false;
ISubscriber subscriber = redisHostConnection.GetSubscriber();

Zagadnienia dotyczące serializacji

W przypadku wybierania formatu serializacji należy rozważyć wpływ na wydajność, interoperacyjność, przechowywanie wersji, zgodność z innymi systemami, kompresję danych oraz obciążenie pamięci. Podczas oceny wydajności należy pamiętać, że testy porównawcze są bardzo zależne od kontekstu. Mogą nie oddawać faktycznego obciążenia oraz mogą nie uwzględniać nowszych bibliotek lub wersji. Nie ma jednego "najszybszego" serializatora dla wszystkich scenariuszy.

Niektóre opcje do rozważenia obejmują:

  • Protocol Buffers (zwany również protobuf) jest formatem serializacji opracowanym przez firmę Google na potrzeby wydajnego serializowania danych strukturalnych. Używa on silnie typiowanych plików definicji do definiowania struktur komunikatów. Te pliki definicji są następnie kompilowane do kodu właściwego dla języka na potrzeby serializacji i deserializacji komunikatów. Formatu protobuf można użyć za pośrednictwem istniejących mechanizmów RPC lub może on wygenerować usługę RPC.

  • Apache Thrift korzysta z podobnego podejścia z silnie typizowanymi plikami definicji oraz etapem kompilacji do generowania kodu serializacji i usług RPC.

  • Apache Avro zapewnia podobne funkcje do buforów protokołu i thrift, ale nie ma kroku kompilacji. Zamiast tego dane serializowane zawsze obejmują schemat opisujący strukturę.

  • JSON to otwarty standard używający pól tekstowych zrozumiałych dla użytkownika. Zapewnia szeroką obsługę wielu platform. Kod JSON nie używa schematów komunikatów. Będąc formatem opartym na tekście, nie jest bardzo wydajny za pośrednictwem przewodu. Jednak w niektórych przypadkach możesz zwracać elementy pamięci podręcznej bezpośrednio do klienta za pośrednictwem protokołu HTTP. W takiej sytuacji przechowywanie pliku JSON może zmniejszyć koszty związane z deserializacją z innego formatu oraz serializacją do formatu JSON.

  • BSON to binarny format serializacji używający struktury podobnej do formatu JSON. Format BSON został zaprojektowany tak, aby zapewniać większą prostotę, łatwość skanowania oraz szybkość serializowania i deserializowania względem formatu JSON. Ładunki mają porównywalny rozmiar do formatu JSON. W zależności od danych ładunek BSON może być mniejszy lub większy od ładunku JSON. Format BSON zawiera kilka dodatkowych typów danych, które nie są dostępne w formacie JSON, w szczególności BinData (dla tablic bajtowych) i Date.

  • MessagePack to binarny format serializacji zaprojektowany z myślą o kompaktowym rozwiązaniu do przesyłania przez sieć. Nie obejmuje schematów komunikatów ani kontroli typów komunikatów.

  • Bond to międzyplatformowa struktura, która umożliwia pracę z danymi objętymi schematem. Obsługuje ona serializację i deserializację między językami. Istotne różnice w porównaniu do innych systemów wymienionych w tym artykule to dziedziczenie, aliasy typów oraz typy ogólne.

  • gRPC to system RPC typu open source opracowany przez firmę Google. Domyślnie system używa formatu Protocol Buffers jako języka definicji oraz podstawowego formatu wymiany komunikatów.

Następne kroki

Następujące wzorce mogą być również istotne dla danego scenariusza podczas implementowania buforowania w aplikacjach:

  • Wzorzec z odkładaniem do pamięci podręcznej: ten wzorzec opisuje sposób ładowania danych na żądanie do pamięci podręcznej z magazynu danych. Ponadto ten wzorzec pomaga w utrzymaniu spójności pomiędzy danymi przechowywanymi w pamięci podręcznej i danymi w oryginalnym magazynie danych.

  • Wzorzec fragmentowania zapewnia informacje o implementowaniu partycjonowania poziomego w celu poprawy skalowalności podczas przechowywania i uzyskiwania dostępu do dużych ilości danych.