Mateusz Mazurek – programista z pasją

Python, architektura, ciekawostki ze świata IT

Algorytmika Programowanie Programowanie webowe

Redis i Python – dobrze dobrana para #8

Cześć.

Tak jak pisałem w poprzednim artykule z tej serii, podstawowe zagadnienia związane z Redisem i jego użyciem z poziomu Pythona mamy za sobą. Ten wpis, jak i dwa ostatnie, to już tematy które przekraczają magiczną barierę podpisaną „podstawy” wgłębiając się w nieco bardziej zaawansowane zagadnienia. Dzisiejszy blog post będzie traktował o skryptach które można napisać w języku Lua. Takie skrypty można wysłać jako komendę do Redisa i wykonać. To tak w dużym skrócie. Ciekawe? No to zapraszam na dalszą część wpisu. Ale, ale! Nim przejdziesz dalej, upewnij się że nic Ci nie uciekło po drodze, bo to już 8ma część serii!

Czym jest Lua?

Lua to skryptowy język programowania. Pierwotnie projektowany był w celu rozszerzenia funkcjonalności różnych aplikacji, jednak jest często używany jako samodzielny język. Składnia tego języka jest prosta i przypomina składnię większości języków programowania. W odróżnieniu od Pythona, Lua jest daleki od podejścia „batteries included”, co sprawia, że pełny interpreter tego języka to zaledwie 247 kB. A to natomiast powoduje, że w pełni pasuje do tego do czego był tworzony – do rozszerzania możliwości innych aplikacji. I dokładnie w takim kontekście wykorzystuje go Redis.

Przykład

Bo przykład powie więcej niż 1000 słów.

1
2
3
4
5
6
7
8
9
10
from redis import Redis

redis_connection = Redis(decode_responses=True, db=0)


script = """
return "test"
"""


print(redis_connection.eval(script, 0))

Powyższy kod wykonuję komendę EVAL, przekazując w niej po prostu ciało skryptu napisanego w LUA, Redis odbiera ten kod, wykonuje go i zwraca rezultat. Efektem wykonania się tego kodu jest wyświetlenie słowa „test”.

Drugi argument w poleceniu EVAL to 0 i określa on ilość argumentów które można przekazać do skryptu. Jeśli podamy tam wartość „n” to kolejne „n” argumentów które przekażemy do funkcji będą przekazane również do skryptu. W skrypcie będą one dostępne w tabeli KEYS (uwaga – Lua indeksuje tabele od 1!). Wszystko przekazane po określonej liczbie „n”, zostanie przekazane do tabeli ARGV.

Zerknij na kod poniżej:

1
2
3
4
5
6
7
8
9
10
from redis import Redis

redis_connection = Redis(decode_responses=True, db=0)


script = """
return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}
"""


print(redis_connection.eval(script, 2, 1, 2, 'first', 'second'))

Określamy tutaj że dwa pierwsze parametry będą dostępne w skrypcie w tabeli KEYS a kolejne – w tabeli ARGV. Sam skrypt odczytuje przekazane dane i zwraca je jako tablicę. Wynik tego kawałka kodu to oczywiście:

['1', '2', 'first', 'second']

Oczywiście Lua umie wszystko co inne języki programowania, więc nic nie stoi na przeszkodzie by np. wygenerować sobie w nim listę 10 liczb:

1
2
3
4
5
6
7
8
9
10
11
12
13
from redis import Redis

redis_connection = Redis(decode_responses=True, db=0)

script = """
local arr = {}
for i = 0, 10 do
    arr[i] = i
end
return arr
"""


print(redis_connection.eval(script, 0))# lista od 1 do 10

albo pracować z formatem danych JSON:

1
2
3
4
5
6
7
8
9
10
11
12
from redis import Redis
from json import dumps

redis_connection = Redis(decode_responses=True, db=0)

script = """
local json_data = KEYS[1]
local decoded_data = cjson.decode(json_data)
return decoded_data['a'] + decoded_data['b']
"""


print(redis_connection.eval(script, 1, dumps({'a': 1, 'b': 6})))  # wynik to 7

No dobra, ale po co to?

Skrypty w Redisie pojawiły się po to by rozwiązać problem sytuacji kiedy potrzebujemy coś pobrać z Redisa, przetworzyć i zapisać wynik z powrotem lub co gorsza, rozpropagować go po większej ilości kluczy. Celem ogólnym jest zmniejszenie ilości interakcji po sieci przez przeniesienie części logiki właśnie do Redisa.


Czekaj, stop!

Podoba Ci się to co tworzę? Jeśli tak to zapraszam Cię do zapisania się na newsletter:
a w ramach prezentu otrzymasz całkowicie za darmo, dwa dokumenty PDF „6 (nie zawsze oczywistych) błędów popełnianych podczas nauki programowania” który jest jednym z efektów ponad siedmioletniej pracy i obserwacji rozwoju niejednego programisty oraz „Wstęp do testowania w Pythonie”, będący wprowadzeniem do biblioteki PyTest.
Jeśli to Cię interesuje to zapraszam również na swoje social media.

Jak i do ewentualnego postawienia mi kawy :)
Postaw mi kawę na buycoffee.to

Żeby można było tego dokonać, trzeba umieć wykonywać komendy Redisowe ze skryptów Lua. I znów, najłatwiej będzie na przykładzie:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from redis import Redis

redis_connection = Redis(decode_responses=True, db=0)

redis_connection.set("key1", 10)

script = """
local arg1 = redis.call('get','key1')
redis.call('set', 'key2', arg1 + KEYS[1])
return nil
"""


print(redis_connection.eval(script, 1, 5))  # None, ze względu na "return nil"
print(redis_connection.get("key2"))  # 15, bo 10 + 5 = 15 ;)

Powyższy kod kazał Redisowi wykonać skrypt w którym odczytuję wartość z pod klucza „key1”, dodaję do niej wartość przekazaną jako argument i zapisuję nową wartość w kluczu o nazwie „key2”. Poza „redis.call” istnieje jeszcze „redis.pcall”, pozwalający na inne podejście do ewentualnej obsługi błędów, po więcej szczegółów odsyłam jednak do dokumentacji.

Bardziej realny przykład

Przeanalizuj taki kod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from redis import Redis

redis_connection = Redis(decode_responses=True, db=0)

permission = 'ADD_BOOKING'

redis_connection.sadd("users_group:2", *list(range(0, 50)))

redis_connection.sadd('permissions', permission)

# dotąd jest przygotowanie danych

add_permission_script = """
local is_valid_permission = redis.call('sismember', 'permissions', KEYS[2])
if is_valid_permission == 1 then
    local users = redis.call('smembers','users_group:'..KEYS[1])
    for _, user in ipairs(users) do
        redis.call('sadd', 'user_permissions:'..user, KEYS[2])
    end
    return true
else
    return false
end
"""



print(redis_connection.eval(add_permission_script, 2, 2, permission))

Ten kod dodaje konkretne uprawnienie wszystkim użytkownikom którzy są w zadanej grupie. Początek tego snippetu to przygotowanie danych, określamy nazwę uprawnienia, wypełniamy grupę użytkownikami (ich ID oczywiście) i dodajemy uprawnienie do listy przechowującej wszystkie możliwe uprawnienia. Jako argumenty do skryptu Lua przekazujemy ID grupy i uprawnienie które chcemy rozpropagować. Sam skrypt pierw sprawdza czy uprawnienie jest poprawne (znajduje się w zbiorze poprawnych) i jeśli tak to dla każdego użytkownika który należy do przekazanej grupy, dodaje uprawnienie. Ewentualna duplikację uprawnień załatwiamy odpowiednim typem danych, klucz „user_permissions:ID” jest SETtem, więc nie pozwala na duplikacje.

Podobną logikę można zrealizować w Pythonie oczywiście, ale zwiększa to ilość danych które są przesyłane przez sieć, bo rozbiło by nam to tę sytuację na minimum 3 odpytania Redisa. I to zakładając, że ktoś ostatni wpis rzetelnie przeczytał. A i skrypty są w pełni atomowe!

Ale czekaj.. Przecież ten skrypt jest długi. A pewnie szybko i dłuższe byłyby konieczne. To co z tym oszczędzaniem na przesyłaniu danych?

Cache skryptów

Przesyłanie za każdym razem ciała skryptu nie dawałoby oszczędności na ilości danych przesyłanych przez sieć. Redis pozwala na zapisanie go u siebie i posługiwanie się skrótem SHA w celu jego uruchomienia. Skrypt zapisujemy komendą SCRIPT LOAD gdzie jako argument podajemy skrypt a zwraca on nam skrót, którego możemy użyć w celu uruchomienia naszego kawałka kodu LUA:

1
2
sha = redis_connection.script_load(add_permission_script)
print(redis_connection.evalsha(sha, 2, 2, permission))

Zamieniając EVAL na EVALSHA.

Podsumowując

Przeszliśmy sobie przez jedną z ciekawszych opcji jakie oferuje Redis. Skrypty to potężne narzędzie, które pozwala na grupowanie kodu i oszczędność w kontekście ilości danych przesyłanych przez sieć.

Dzięki za wizytę,
Mateusz Mazurek

A może wolisz nowości na mail?

Subskrybuj
Powiadom o
guest

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.

1 Komentarz
Inline Feedbacks
View all comments