Zbiory i hashe w Redis
Tematem tego artykułu są zbiory i hashe w Redis. Są to dwa z najbardziej użytecznych typów danych w Redis, a tak często lekceważonych przez programistów. Ważne jest, aby zrozumieć podstawowe koncepty, które za nimi stoją, ponieważ dobór odpowiedniego typu danych pomoże w późniejszym skalowaniu aplikacji.
W tym artykule używam Redis-Py, aby zaprezentować pewne funkcje. Koncept, który za tym stoi jest taki sam, niezależnie od języka programowania, którego używasz. Redis-Py używa tej samej nazwy do wywołania funkcji, co oryginalna komenda, z kilkoma drobnymi wyjątkami. Pełną listę obsługiwanych języków znajdziesz tutaj.
Instalacja
Upewnij się, że zainstalowałeś Redis na swoim lokalnym komputerze. Lokalny komputer musi zawierać dystrybucje Linuksa z minimalnymi ustawieniami konfiguracji.
Samodzielna kompilacja
Możesz pobrać i skompilować go na swoim komputerze za pomocą następujących poleceń (wersja 6.0.8 w momencie pisania tego tekstu):
wget http://download.redis.io/releases/redis-6.0.8.tar.gz
tar xzf redis-6.0.8.tar.gz
cd redis-6.0.8
make
Powinieneś zobaczyć folder src
po zakończeniu. Zmień katalog roboczy na folder src
.
Ubuntu
Dla użytkowników Ubuntu zainstaluj jak poniżej:
sudo apt-get update
sudo apt-get install redis-server
Uruchomienie serwera Redis
Kiedy już to zrobisz, uruchom go w ten sposób:
redis-server
Chcąc sprawdzić, czy serwer działa, uruchom interfejs wiersza poleceń:
redis-cli
Wyślij polecenie ping
, jeśli jesteś już w wierszu poleceń:
redis> ping
Serwer Redis działa poprawnie, jeśli zwraca PONG
.
Redis-Py
Możesz łatwo zainstalować Redis-Py poprzez pip install
. Przed dalszymi krokami zaleca się utworzenie środowiska wirtualnego.
pip install redis
Musisz go zaimportować i przekazać odpowiednie konfiguracje, aby móc z niego skorzystać.
import redis
r = redis.Redis(host="localhost", password='password', port=6379, db=0)
Zbiory
Zbiory w Redis są niczym innym jak kolekcjami stringów sklasyfikowanych jako uporządkowane i nieuporządkowane. W tym artykule zajmiemy się jedynie zbiorami nieuporządkowanymi. Każdy element wewnątrz zbioru nazywany jest member
. Jedną z głównych cech zbiorów jest to, że nie pozwalają one na powtarzanie się elementów. Dodanie tego samego elementu do zbioru nie działa. Dlatego też nie ma potrzeby sprawdzania, czy dany element istnieje w zbiorze przed dodaniem go do zbioru.
Ponadto, można wykonywać większość operacji przeznaczonych dla zbiorów, takich jak część wspólna (intersection) i suma zbiorów (union). Jest to niezwykle przydatne, gdy trzeba obliczyć lub scalić setki tysięcy danych. Załóżmy przykładowo, że zebrałeś wszystkie tagi używane przez użytkowników na platformie A i B. Możesz uruchomić funkcję intersect podczas zapytania danych. Jest to rozwiązanie o wiele czystsze i bardziej zoptymalizowane niż zapytanie o wszystko i wykonanie funkcji intersection w aplikacji.
Zbiory są idealnym rozwiązaniem, gdy mamy do czynienia z dużą liczbą jednoznacznie identyfikowanych punktów danych, takich jak numery identyfikacyjne do uwierzytelniania czy etykiety produktów w handlu elektronicznym. Zamiast deklarować wiele kluczy dla każdego ID, możesz modelować wszystkie numery ID, jako elementy w pojedynczym kluczu.
Inicjalizacja
Przetestujmy to, inicjalizując następujące zmienne. Pierwsza zmienna reprezentuje listę identyfikatorów użytkowników, natomiast druga zmienna zawiera identyfikatory administratorów. Użytkownik może być również administratorem, na co wskazuje ID 1001
. Możesz użyć zbioru Pythona zamiast listy, w zależności od swoich preferencji.
user_id = ["1001", "1002", "1003", "1004", "1005"]
admin_id = ["1001", "1006", "1007"]
Dodaj nowy klucz lub element
Dodanie nowego klucza lub elementu do już istniejącego jest bardzo proste. Wystarczy wywołać funkcję sadd
:
for key in user_id:
r.sadd("user", key)
for key in admin_id:
r.sadd("admin", key)
Sprawdź, czy klucz/element istnieje
Ogólnie rzecz biorąc, powinieneś użyć exists()
, aby sprawdzić, czy klucz istnieje w bazie danych Redis. Dla zbiorów Redis mamy coś takiego jak sismember()
. Może być użyte do określenia czy dany element istnieje w zbiorze, czy też nie. Jeśli istnieje, zwraca 1
, a jeśli nie, zwraca 0
. Zwróci także 0
, jeśli klucz nie istnieje. Dla większej wygody Redis-Py automatycznie mapuje zwrócony wynik jako typ logiczny:
r.sismember("user", "1004")
# True
r.sismember("admin", "1004")
# False
Pobierz wszystkie elementy
Jeśli szukasz szybkiej metody, aby pobrać wszystkie elementy zbioru, smembers()
jest właściwym wyborem:
r.smembers("user")
# {b'1001', b'1004', b'1002', b'1005', b'1003'}
Uzyskaj liczbę elementów
Logicznie rzecz biorąc, możesz wyciągnąć wszystkie elementy, używając smembers()
i oszacować liczbę elementów poprzez len()
w Pythonie. Ta metoda nie sprawdza się, jednak jeśli chcesz poznać jedynie całkowitą liczbę elementów. Na szczęście Redis zapewnia specjalną funkcję scard()
dla takich przypadków użycia.
r.scard("user")
# 5
Różnica
Jak wcześniej wspomniałem, podczas query można wykonać kilka operacji przeznaczonych dla zbiorów bezpośrednio wewnątrz Redisa. Przykładowo, różnice pomiędzy zbiorem user
a zbiorem admin
można znaleźć w poniższym fragmencie kodu:
r.sdiff("user", "admin")
# {b'1003', b'1004', b'1002', b'1005'}
Intersection
Możesz również rozpoznać wspólne elementy pomiędzy dwoma zbiorami, używając sinter()
. Poniższy przykład ilustruje, w jaki sposób można znaleźć użytkownika, który jest jednocześnie administratorem:
r.sinter("user", "admin")
# {b'1001'}
Union
Inną popularną operacją w zbiorze Redis jest sunion()
, która łączy wszystkie elementy wewnątrz dwóch zbiorów. Zwrócony wynik nie będzie zawierał żadnych powtórzonych elementów:
r.sunion("user", "admin")
# {b'1001', b'1004', b'1002', b'1005', b'1007', b'1003', b'1006'}
Hashe
W przeciwieństwie do zbiorów hashe w Redis są z przeznaczeniem do przechowywania złożonych danych. Hashe są traktowane jako mapy pomiędzy polem a wartością stringa. Z tego powodu są one idealnym typem danych do przechowywania obiektów, takich jak słownik w Pythonie.
Na przykład obiekt User
z polami nazwy i wieku. Zamiast używać różnych kluczy dla nazwy i wieku, o wiele bardziej efektywne jest użycie jednego hasha, który zawiera wszystkie wymagane pola. Zamiast inicjalizować różne klucze ciągu znaków <key>:<id>:<field>
, takie jak userhash:1001:name
, możesz po prostu utworzyć je, używając {"userhash:1001": {"name": “Alice”}}
.
W rzeczywistości oficjalna dokumentacja zaleca używanie hashy zawsze, gdy jest to możliwe, ponieważ małe hashe są zakodowane w bardzo małej przestrzeni. Hashe zdolne są do przechowywania wielu elementów i mogą być również używane do przechowywania innych instancji, innych niż obiekty.
Inicjalizacja
Utwórzmy nowy słownik Pythona jako część danych do naszego testu. Aby nie komplikować, zainicjalizuję go tylko polem name:
hash_data = {"userhash:1001": {"name": "Alice"}, "userhash:1002": {"name": "Bonnie"}, "userhash:1003": {"name": "Cassie"}, "userhash:1004": {"name": "Dolores"}, "userhash:1005": {"name": "Erika"}}
Dodaj nowy hash
Redis posiada dwie różne wbudowane funkcje do ustawiania pary klucz-wartość w hashu:
-
hset
— Zapisz pole (klucz) w hashu z wartością. Jeśli hash nie istnieje, zostanie utworzony nowy hash. Jeśli pole (klucz) już istnieje w istniejącym hashu, zostanie ono nadpisane przez nową wartość. -
hmset
— podobny dohset
, ale pozwala na wiele parametrów wejściowych w jednym poleceniu. Ta funkcja jest przestarzała i od tej pory powinieneś używaćhset
dla jednego lub wielu parametrów wejściowych.
W momencie pisania tego tekstu Redis-py jest dostarczany z uaktualnionym hset
o następującej strukturze:
def hset(self, name, key=None, value=None, mapping=None)
-
name
— identyfikator hasha. -
key
— nazwa pola wewnątrz hasha. -
value
— wartość dla poszczególnego pola. -
mapping
— przyjmuje słownik jako input. Używane jako zamiennik dlahmget
do analizy składniowej słownika do odpowiednich par klucz-wartość.
W rezultacie możesz użyć tego:
r.hset("userhash:1001", None, None, {"name": "Alice"})
lub tego:
r.hset("userhash:1001", mapping={"name": "Alice"})
aby dodać nowy hash do twojego serwera Redis.
Uruchom poniższe polecenie, aby zapętlić nasze dane i dodać nowe hashe:
for key in hash_data:
r.hset(key, mapping=hash_data[key])
Sprawdź, czy hash/pole istnieją
Aby sprawdzić obecność istniejącego hasha lub pól w hashu, powinieneś użyć hexists()
:
r.hexists("userhash:1001", "name")
# True
Uzyskaj wartość określonego pola
hget()
jest jedną z najważniejszych funkcji w hashu, służącą do pobierania wartości odpowiedniego pola.
r.hget("userhash:1001", "name")
# b'Alice'
Pobierz wszystkie pola
Jeśli szukasz szybkiego i łatwego sposobu, aby uzyskać wszystkie nazwy pól wewnątrz hasha, hkeys()
to najlepsza opcja w takim przypadku. W Redis-Py zwróci listę zawierającą wszystkie pola:
r.hkeys("userhash:1001")
# [b'name']
Pobierz wszystkie pola i wartości
Możesz łatwo uzyskać wszystkie pola i ich odpowiednie wartości dla pojedynczego hasha poprzez funkcję hgetall()
.
r.hgetall("userhash:1001")
# {b'name': b'Alice'}
Obsługa zagnieżdżonych struktur danych
W momencie pisania tego tekstu Redis nie obsługuje zagnieżdżonych struktur danych. Jeśli twoje dane zawierają zagnieżdżone słowniki, najlepszym sposobem jest serializacja za pomocą json.dumps
i przechowywanie go jako ciąg znaków. Następnie, po pobraniu z Redis, możesz go po prostu zdeserializować za pomocą json.loads
.
Możesz również użyć pickle
, ale należy zachować ostrożność podczas przechowywania danych wejściowych użytkownika, ponieważ jest podatny na ataki w zdalnym wykonywaniu kodu.
Bonus
Oto kilka przydatnych poleceń, których możesz użyć w Redis.
Pobierz wszystkie klucze z serwera Redis
W najnowszej wersji Redis posiada funkcję scan_iter()
, która jest zalecanym wyborem w stosunku do keys()
. keys()
powinno być używane tylko w developmencie do celów debugowania. W Redis-Py możesz pobrać i wydrukować wszystkie klucze z bazy danych za pomocą następującego fragmentu kodu:
for key in r.scan_iter():
print(key)
Wyczyść bazę danych
Jeśli chcesz wyczyścić bazę danych w Redis możesz użyć dwóch funkcji.
-
flushdb
— czyści i usuwa wszystkie klucze z bieżącej bazy danych. -
flushall
— czyści i usuwa wszystkie klucze z wszystkich baz danych na serwerze.
Wnioski
Podsumujmy, czego się dziś dowiedzieliśmy.
Rozpoczęliśmy od prostego wyjaśnienia dwóch typów danych, czyli zbiorów i hashy w Redis. Następnie wykonaliśmy niezbędne kroki w celu skonfigurowania i zainstalowania serwera Redis.
Przeszliśmy dalej i dogłębnie zbadaliśmy podstawowe koncepty, które mówią nam czym są zbiory w Redis. Przetestowaliśmy kilka przydatnych funkcji, takich jak zarządzanie i pobieranie danych ze zbiorów.
Następnie pobawiliśmy się trochę hashami Redisa, które są idealnym wyborem do przechowywania obiektów. Takich jak słowniki w Pythonie. Zaleca się, aby używać hashy zamiast typu danych string, w celu zwiększenia wydajności.
Dzięki za przeczytanie tego artykułu. Mam nadzieję, że zobaczymy się ponownie w następnym!
Oryginał tekstu w języku angielskim przeczytasz tutaj.