Podczas pisania aplikacji w Pythonie ważne jest buforowanie. Używanie pamięci podręcznej w celu uniknięcia ponownego obliczania danych lub uzyskiwania dostępu do wolnej bazy danych może zapewnić ogromny wzrost wydajności.
Python oferuje wbudowane możliwości buforowania, od prostego słownika do bardziej kompletnej struktury danych, takiej jak functools.lru_cache
. Ten ostatni może buforować dowolny element przy użyciu algorytmu ostatnio używanego, aby ograniczyć rozmiar pamięci podręcznej.
Te struktury danych są jednak z definicji lokalne do twojego procesu Pythona. Gdy kilka kopii aplikacji działa na dużej platformie, użycie struktury danych w pamięci uniemożliwia udostępnianie zawartości z pamięci podręcznej. Może to stanowić problem w przypadku aplikacji na dużą skalę i rozproszonych.
Dlatego też, gdy system jest rozproszony w sieci, potrzebuje również pamięci podręcznej, która jest rozproszona w sieci. Obecnie istnieje wiele serwerów sieciowych, które oferują możliwość buforowania — omówiliśmy już, jak używać Redis do buforowania w Django.
Jak zobaczysz w tym samouczku, memcached to kolejna świetna opcja do rozproszonego buforowania. Po krótkim wprowadzeniu do podstawowego korzystania z pamięci podręcznej memcached, poznasz zaawansowane wzorce, takie jak „buforuj i ustaw” oraz używanie rezerwowych pamięci podręcznych, aby uniknąć problemów z wydajnością zimnej pamięci podręcznej.
Instalowanie memcached
Memcached jest dostępny na wiele platform:
- Jeśli używasz Linuksa , możesz go zainstalować za pomocą
apt-get install memcached
lubyum install memcached
. To zainstaluje memcached z gotowego pakietu, ale możesz też zbudować memcached ze źródła, jak wyjaśniono tutaj. - Dla macOS , użycie Homebrew jest najprostszą opcją. Po prostu uruchom
brew install memcached
po zainstalowaniu menedżera pakietów Homebrew. - W Windowsie , musiałbyś sam skompilować memcached lub znaleźć wstępnie skompilowane pliki binarne.
Po zainstalowaniu memcached można po prostu uruchomić, wywołując memcached
polecenie:
$ memcached
Zanim będziesz mógł wchodzić w interakcję z memcached z Python-land, musisz zainstalować memcached klient biblioteka. Zobaczysz, jak to zrobić w następnej sekcji, wraz z kilkoma podstawowymi operacjami dostępu do pamięci podręcznej.
Przechowywanie i pobieranie wartości z pamięci podręcznej za pomocą Pythona
Jeśli nigdy nie używałeś memcached , jest to dość łatwe do zrozumienia. Zasadniczo zapewnia gigantyczny słownik dostępny w sieci. Słownik ten ma kilka właściwości, które różnią się od klasycznego słownika Pythona, głównie:
- Klucze i wartości muszą być bajtami
- Klucze i wartości są automatycznie usuwane po upływie określonego czasu
Dlatego dwie podstawowe operacje interakcji z memcached są set
i get
. Jak można się domyślić, służą one odpowiednio do przypisania wartości do klucza lub pobrania wartości z klucza.
Moja preferowana biblioteka Pythona do interakcji z memcached to pymemcache
— Polecam go używać. Możesz go po prostu zainstalować za pomocą pip:
$ pip install pymemcache
Poniższy kod pokazuje, jak możesz połączyć się z memcached i użyj go jako rozproszonej w sieci pamięci podręcznej w aplikacjach Pythona:
>>> from pymemcache.client import base
# Don't forget to run `memcached' before running this next line:
>>> client = base.Client(('localhost', 11211))
# Once the client is instantiated, you can access the cache:
>>> client.set('some_key', 'some value')
# Retrieve previously set data again:
>>> client.get('some_key')
'some value'
w pamięci podręcznej protokół sieciowy jest naprawdę prosty, a jego implementacja niezwykle szybka, co czyni go przydatnym do przechowywania danych, których pobieranie z kanonicznego źródła danych lub ponowne obliczanie byłoby przydatne:
Chociaż jest to dość proste, ten przykład umożliwia przechowywanie krotek klucz/wartość w sieci i uzyskiwanie do nich dostępu za pośrednictwem wielu rozproszonych, działających kopii aplikacji. To jest proste, ale potężne. I to świetny pierwszy krok w kierunku optymalizacji aplikacji.
Automatyczne wygasanie danych w pamięci podręcznej
Podczas przechowywania danych w memcached , możesz ustawić czas wygaśnięcia — maksymalną liczbę sekund dla memcached zachować klucz i wartość. Po tym opóźnieniu memcached automatycznie usuwa klucz z pamięci podręcznej.
Na co ustawić ten czas pamięci podręcznej? Nie ma magicznej liczby dla tego opóźnienia i będzie to całkowicie zależeć od rodzaju danych i aplikacji, z którą pracujesz. Może to potrwać kilka sekund lub kilka godzin.
Unieważnienie pamięci podręcznej , który określa, kiedy usunąć pamięć podręczną, ponieważ nie jest zsynchronizowana z bieżącymi danymi, jest również czymś, z czym Twoja aplikacja będzie musiała sobie poradzić. Zwłaszcza jeśli przedstawiasz dane, które są zbyt stare lub nieaktualne należy unikać.
Tutaj znowu nie ma magicznej recepty; zależy to od typu tworzonej aplikacji. Istnieje jednak kilka odległych spraw, którymi należy się zająć – których jeszcze nie omówiliśmy w powyższym przykładzie.
Serwer buforujący nie może rosnąć w nieskończoność — pamięć to ograniczony zasób. Dlatego klucze zostaną usunięte przez serwer pamięci podręcznej, gdy tylko będzie potrzebować więcej miejsca do przechowywania innych rzeczy.
Niektóre klucze mogą również utracić ważność, ponieważ upłynął ich czas wygaśnięcia (czasami nazywany „czasem wygaśnięcia” lub TTL). W takich przypadkach dane są tracone, a kanoniczne źródło danych musi zostać ponownie przeszukane.
Brzmi to bardziej skomplikowanie, niż jest w rzeczywistości. Podczas pracy z memcached w Pythonie możesz ogólnie pracować z następującym wzorcem:
from pymemcache.client import base
def do_some_query():
# Replace with actual querying code to a database,
# a remote REST API, etc.
return 42
# Don't forget to run `memcached' before running this code
client = base.Client(('localhost', 11211))
result = client.get('some_key')
if result is None:
# The cache is empty, need to get the value
# from the canonical source:
result = do_some_query()
# Cache the result for next time:
client.set('some_key', result)
# Whether we needed to update the cache or not,
# at this point you can work with the data
# stored in the `result` variable:
print(result)
Uwaga: Obsługa brakujących kluczy jest obowiązkowa ze względu na normalne operacje spłukiwania. Obowiązkowe jest również radzenie sobie ze scenariuszem zimnej pamięci podręcznej, tj. gdy memcached właśnie się rozpoczął. W takim przypadku pamięć podręczna będzie całkowicie pusta, a pamięć podręczna musi zostać całkowicie zapełniona, jedno żądanie na raz.
Oznacza to, że należy przeglądać wszelkie dane z pamięci podręcznej jako efemeryczne. I nigdy nie powinieneś oczekiwać, że pamięć podręczna będzie zawierać wartość, którą wcześniej w niej zapisałeś.
Rozgrzewanie zimnej pamięci podręcznej
Niektórym scenariuszom zimnej pamięci podręcznej nie można zapobiec, na przykład memcached rozbić się. Ale niektóre mogą, na przykład migrować do nowego memcached serwer.
Kiedy można przewidzieć, że zdarzy się scenariusz zimnej pamięci podręcznej, lepiej tego unikać. Pamięć podręczna, która wymaga ponownego napełnienia, oznacza, że nagle kanoniczne przechowywanie danych w pamięci podręcznej zostanie masowo uderzone przez wszystkich użytkowników pamięci podręcznej, którym brakuje danych w pamięci podręcznej (znany również jako problem z piorunującym stadem).
pymemcache udostępnia klasę o nazwie FallbackClient
który pomaga w realizacji tego scenariusza, jak pokazano tutaj:
from pymemcache.client import base
from pymemcache import fallback
def do_some_query():
# Replace with actual querying code to a database,
# a remote REST API, etc.
return 42
# Set `ignore_exc=True` so it is possible to shut down
# the old cache before removing its usage from
# the program, if ever necessary.
old_cache = base.Client(('localhost', 11211), ignore_exc=True)
new_cache = base.Client(('localhost', 11212))
client = fallback.FallbackClient((new_cache, old_cache))
result = client.get('some_key')
if result is None:
# The cache is empty, need to get the value
# from the canonical source:
result = do_some_query()
# Cache the result for next time:
client.set('some_key', result)
print(result)
FallbackClient
odpytuje starą pamięć podręczną przekazaną do jej konstruktora, przestrzegając kolejności. W takim przypadku nowy serwer pamięci podręcznej zawsze będzie odpytywany jako pierwszy, a w przypadku braku pamięci podręcznej, odpytywany będzie stary, co pozwoli uniknąć ewentualnej podróży powrotnej do głównego źródła danych.
Jeśli jakikolwiek klucz jest ustawiony, zostanie ustawiony tylko na nową pamięć podręczną. Po pewnym czasie stara pamięć podręczna może zostać zlikwidowana, a FallbackClient
może być zastąpiony przez new_cache
klienta.
Sprawdź i ustaw
Podczas komunikacji ze zdalną pamięcią podręczną powraca zwykły problem ze współbieżnością:może być kilku klientów próbujących uzyskać dostęp do tego samego klucza w tym samym czasie. w pamięci podręcznej zapewnia sprawdzenie i ustawienie operacja, skrócona do CAS , co pomaga rozwiązać ten problem.
Najprostszym przykładem jest aplikacja, która chce policzyć liczbę posiadanych użytkowników. Za każdym razem, gdy odwiedzający się łączy, licznik jest zwiększany o 1. Używając memcached , prosta implementacja to:
def on_visit(client):
result = client.get('visitors')
if result is None:
result = 1
else:
result += 1
client.set('visitors', result)
Co się jednak stanie, jeśli dwie instancje aplikacji spróbują zaktualizować ten licznik w tym samym czasie?
Pierwsze wywołanie client.get('visitors')
zwróci tę samą liczbę odwiedzających dla obu, powiedzmy, że jest to 42. Następnie obaj dodają 1, obliczą 43 i ustawią liczbę odwiedzających na 43. Ta liczba jest błędna, a wynik powinien wynosić 44, czyli 42 + 1 + 1.
Aby rozwiązać ten problem ze współbieżnością, operacja CAS memcached jest przydatny. Poniższy fragment kodu implementuje poprawne rozwiązanie:
def on_visit(client):
while True:
result, cas = client.gets('visitors')
if result is None:
result = 1
else:
result += 1
if client.cas('visitors', result, cas):
break
gets
metoda zwraca wartość, podobnie jak get
metoda, ale zwraca również wartość CAS .
To, co znajduje się w tej wartości, nie ma znaczenia, ale jest używane w następnej metodzie cas
połączenie. Ta metoda jest równoważna z set
operacja, z wyjątkiem tego, że kończy się niepowodzeniem, jeśli wartość zmieniła się od czasu gets
operacja. W przypadku sukcesu pętla zostaje przerwana. W przeciwnym razie operacja zostanie wznowiona od początku.
W scenariuszu, w którym dwie instancje aplikacji próbują zaktualizować licznik w tym samym czasie, tylko jednej udaje się przenieść licznik z 42 na 43. Drugie wystąpienie otrzymuje False
wartość zwracana przez client.cas
zadzwoń i musisz ponowić pętlę. Tym razem pobierze 43 jako wartość, zwiększy ją do 44, a jego cas
połączenie się powiedzie, rozwiązując w ten sposób nasz problem.
Inkrementacja licznika jest interesującym przykładem wyjaśnienia działania CAS, ponieważ jest to uproszczenie. Jednak memcached zapewnia również incr
i decr
metody inkrementacji lub dekrementacji liczby całkowitej w jednym żądaniu, zamiast wykonywania wielu gets
/cas
wzywa. W rzeczywistych aplikacjach gets
i cas
są używane do bardziej złożonych typów danych lub operacji
Większość serwerów zdalnego buforowania i magazynu danych zapewnia taki mechanizm, aby zapobiec problemom ze współbieżnością. Bardzo ważne jest, aby być świadomym tych przypadków, aby właściwie wykorzystać ich funkcje.
Poza buforowaniem
Proste techniki zilustrowane w tym artykule pokazały, jak łatwo jest wykorzystać memcached aby przyspieszyć działanie aplikacji Pythona.
Wystarczy użyć dwóch podstawowych operacji „ustaw” i „pobierz”, aby często przyspieszyć pobieranie danych lub uniknąć ciągłego przeliczania wyników. Dzięki memcached możesz udostępniać pamięć podręczną w dużej liczbie rozproszonych węzłów.
Inne, bardziej zaawansowane wzorce, które widziałeś w tym samouczku, takie jak Sprawdź i ustaw (CAS) Operacja umożliwia jednoczesne aktualizowanie danych przechowywanych w pamięci podręcznej w wielu wątkach lub procesach Pythona, unikając jednocześnie uszkodzenia danych.
Jeśli chcesz dowiedzieć się więcej o zaawansowanych technikach pisania szybszych i bardziej skalowalnych aplikacji w języku Python, zapoznaj się z tematem Skalowanie Pythona. Obejmuje wiele zaawansowanych tematów, takich jak dystrybucja sieciowa, systemy kolejkowania, rozproszone mieszanie i profilowanie kodu.