Mateusz Mazurek – programista z pasją

Python, architektura, ciekawostki ze świata IT

Algorytmika Inżynieria oprogramowania Programowanie Programowanie webowe

Redis i Python – dobrze dobrana para #5

Witam ponownie.

Poprzedni wpis z tej serii w zasadzie wyczerpał podstawowe typy danych dostępne w Redisie. W tym artykule przyjrzymy się więc mechanizmowi pub-sub. Skrót ten rozwija się do publish-subscribe i jest on implementacją wzorca o tej samej nazwie.

Słowem wstępu

Wzorzec publish-subscribe jest wzorcem wymiany komunikatów. Polega on na tym, że komponenty wysyłające wiadomości nie wysyłają ich do konkretnie określonej listy odbiorców. Można powiedzieć więcej – nie wiedzą nawet czy ktokolwiek wiadomość otrzyma. Podobnie ma się sprawa z komponentami odbierającymi wiadomości – nie wiedzą one kto wysyła daną wiadomość. Obie strony dzieli „kanał komunikatów”. To ten byt na który publikujący wypycha wiadomości i z którego subskrybent owe wiadomości odbiera.

Największą zaletą tego wzorca jest łatwość dodawania kolejnych elementów do już istniejącej układanki. Jest on bardzo odporny na rozbudowę „farmy” zarówno subskrybentów jak i publikujących. Zaleta ta, jeśli spojrzymy na nią pod innym kątem, jest również wadą. Tak silne rozdzielenie komponentów od siebie powoduje, że publikujący mogą wrzucać kolejne wiadomości gdy subskrybenci wcale nie nasłuchują na nie. Brakuje im po prostu wiedzy o tym ilu subskrybentów jest podłączonych.

Mówiąc obrazem, działa to tak:

Wzorzec publikowania/subskrybowania przy użyciu brokera komunikatów

A w naszym przypadku Message Broker to Redis.

Implementacja w Redisie

Kanał komunikatów, wspomniany przeze mnie w poprzednim rozdziale, w redisie jest po prostu pewnym kluczem. Załóżmy, że w naszym przykładzie będzie to klucz o nazwie „testowa_kanal_komunikacyjny”. Napiszmy subskrybenta:

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

redis_connection = Redis(decode_responses=True)

pubsub = redis_connection.pubsub()
pubsub.subscribe("testowa_kanal_komunikacyjny")

for message in pubsub.listen():
    print(message)

Po uruchomieniu tego kawałka kodu nasz subskrybent odbiera pierwszą wiadomość, tzw. powitalną, informującą, że się podłączyliśmy. Wygląda ona tak:

{'type': 'subscribe', 'pattern': None, 'channel': 'testowa_kanal_komunikacyjny', 'data': 1}

po czym blokuje się, w oczekiwaniu na kolejne komunikaty.

Teraz, jeśli skorzystamy z konsoli redis-cli i uruchomimy komendę:

publish testowa_kanal_komunikacyjny testowa_wiadomosc

to dostaniemy w naszym programie od razu wysłana wiadomość:

{'type': 'message', 'pattern': None, 'channel': 'testowa_kanal_komunikacyjny', 'data': 'testowa_wiadomosc'}

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

I co, tylko tyle?

Aż tyle! Pozwala nam to na szybką komunikację międzyprocesową, a jeśli zachodzi potrzeba, to i pomiędzy osobnymi maszynami komunikacja zajdzie. Dodatkowo można się subskrybować nie na konkretny kanał, a na pattern, tzn jeśli w naszej aplikacji mamy kanały:

  • tasks_1
  • tasks_2
  • tasks_3

i chcielibyśmy, korzystając z jednego subskrybenta, podłączyć się do tych kanałów, możemy po prostu podłączyć się pod pattern „tasks_*”:

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

redis_connection = Redis(decode_responses=True)

pubsub = redis_connection.pubsub()
pubsub.psubscribe("tasks_*")

for message in pubsub.listen():
    print(message)

i teraz, jeśli z konsoli wykonamy:

127.0.0.1:6379> publish tasks_1 test
(integer) 1
127.0.0.1:6379> publish tasks_2 test
(integer) 1
127.0.0.1:6379> publish tasks_3 test
(integer) 1

to dostaniemy w programie:

{’type’: 'pmessage’, 'pattern’: 'tasks_*’, 'channel’: 'tasks_1′, 'data’: 'test’}
{’type’: 'pmessage’, 'pattern’: 'tasks_*’, 'channel’: 'tasks_2′, 'data’: 'test’}
{’type’: 'pmessage’, 'pattern’: 'tasks_*’, 'channel’: 'tasks_3′, 'data’: 'test’}

Kilka ważnych cech

  • Jeśli wyślemy wiadomość, gdy subskrybent nie będzie podłączony, to nie otrzyma on jej po podłączeniu (brak kolejkowania jak w przypadku list).
  • Kanały są dzielone pomiędzy bazami, tzn jeśli wypchniemy wiadomość na kanał na jednej bazie, to obserwując kanał na innej, otrzymamy ją.
  • Złożoność obliczeniowa:
    • PUBLISH: O(N+M) where N is the number of clients subscribed to the receiving channel and M is the total number of subscribed patterns (by any client).
    • SUBSCRIBE: O(N) where N is the number of channels to subscribe to.
  • Wydajność rozwiązania jest ładnie opisana tutaj – nie ma sensu powielać, ale jest sens zlinkować.
  • Nie stoi nic na przeszkodzie żeby subskrybent coś wypychał na ten sam lub inny kanał.
  • Publikujący nie dostają żadnej informacji zwrotnej.
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.

4 komentarzy
Inline Feedbacks
View all comments