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:
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:Jeśli to Cię interesuje to zapraszam również na swoje social media.
Jak i do ewentualnego postawienia mi kawy :)
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.
Mateusz Mazurek