Obsah
1. RabbitMQ – jedna z nejúspěšnějších implementací brokera
2. Rozhraní mezi nástrojem RabbitMQ a programovacími jazyky
3. Protokoly podporované systémem RabbitMQ
4. Ukázky základních způsobů použití nástroje RabbitMQ
5. Směrování zpráv před jejich vložením do fronty
6. Instalace RabbitMQ a konfigurace jeho síťového rozhraní
7. Spuštění serveru ve funkci démona/služby
8. Instalace knihovny Pika s rozhraním mezi RabbitMQ a Pythonem
9. První příklad: nejjednodušší možná implementace systému producent+konzument
11. Přenos společných prvků producenta a konzumenta do jednoho zdrojového kódu
12. Pomalá práce konzumenta a způsob dělby práce mezi větší množství workerů
13. Potvrzování zpracování zprávy, chování systému ve chvíli, kdy zpráva není zpracována
14. Vylepšené chování RabbitMQ při „dělbě práce“ mezi workery
15. Konzument zpráv naprogramovaný v jazyku Clojure
16. Konfigurace projektu využívajícího knihovnu Langohr
17. Realizace konzumenta pro příjem zpráv
18. Spuštění konzumenta a příjem zpráv
19. Repositář s demonstračními příklady
1. RabbitMQ – jedna z nejúspěšnějších implementací brokera
Třetím nástrojem implementujícím fronty zpráv a popř. i asynchronní fronty úloh, s nímž se na stránkách Rootu seznámíme, je nástroj nazvaný RabbitMQ. Původně se jednalo o klasického message brokera (de facto o implementaci fronty zpráv) a taktéž o jednu z implementací protokolu AMQP (Advanced Message Queuing Protocol), ovšem později byly možnosti nástroje RabbitMQ rozšířeny, takže dnes jsou podporovány i protokoly MQTT (Message Queuing Telemetry Transport, mimochodem se jedná o standard ISO) a STOMP neboli Streaming Text Oriented Messaging Protocol. RabbitMQ je poměrně široce konfigurovatelný a flexibilní, což administrátorům umožňuje zvolit si takové vlastnosti, které nejlépe vyhovují potřebám implementované služby. Například je možné RabbitMQ nastavit do režimu s velkou propustností, ovšem bez zapnutých mechanismů pro persistenci zpráv, nebo naopak do režimu, v němž zprávy přežijí restart jak workerů, tak i samotného serveru Rabbitu (dokonce lze zaručit vysokou dostupnost samotných front).
Na systému RabbitMQ je zajímavý i fakt, že je vyvinut v programovacím jazyce Erlang s využitím sady knihoven a nástrojů Open Telecom Platform (OTP). Volba tohoto programovacího jazyka pro systém zajišťující kooperaci mezi obecně velkým množstvím procesů a služeb je velmi dobrá, protože tento jazyk byl navržen právě s ohledem na tvorbu distribuovaných aplikací, které jsou do značné míry odolné vůči selhání.
V dnešním článku si ukážeme, jakým způsobem je možné s využitím systému RabbitMQ nastavit a naprogramovat jednoduchou architekturu producent-konzument. Řekneme si, jak se řídí dělba práce mezi jednotlivými workery (které implementují algoritmus konzumenta), jakým způsobem se dají sledovat jednotlivé fronty použité pro posílání zpráv a konečně si ukážeme způsob potvrzení dokončení práce workerem. To je ovšem jen velmi malá část funkcionality nabízené tímto systémem; další podrobnosti si proto popíšeme v navazujícím článku.
2. Rozhraní mezi nástrojem RabbitMQ a programovacími jazyky
K nástroji RabbitMQ byla vytvořena rozhraní (různé knihovny) umožňující práci s tímto systémem (posílání a příjem zpráv) z mnoha programovacích jazyků:
Jazyk/platforma |
---|
Java |
JavaScript (Node.js) |
Python |
Ruby |
PHP |
C# |
Go |
Elixir |
(Java) Spring AMQP |
Swift |
Objective-C |
Clojure |
U některých výše zmíněných jazyků je k dispozici dokonce větší množství knihoven. V dnešním článku se zaměříme především na programovací jazyk Python, pro nějž existuje mj. i knihovna nazvaná Pika, která je samozřejmě instalovatelná přes PyPi (https://pypi.org/project/pika/). Abychom se ovšem nedrželi pouze čistého mainstreamu, ukážeme si v závěru článku i volání funkcí systému RabbitMQ z programovacího jazyka Clojure s využitím knihovny Langohr.
3. Protokoly podporované systémem RabbitMQ
Se systémem RabbitMQ mohou další části aplikace komunikovat s využitím několika protokolů, které se od sebe odlišují svými vlastnostmi (binární vs. textový protokol), složitostí implementace, sémantikou příkazů atd.
Popis podporovaných protokolů musíme začít zmínkou o podpoře AMQP (Advanced Message Queuing Protocol) verze 0–9–1, 0–9 a 0–8, protože RabbitMQ původně vznikl právě z toho důvodu, aby podporoval tyto standardy AMQP. Jedná se o binární protokol, v němž je velká váha kladena nejen na precizní specifikaci formátu dat, ale i na popis sémantiky operací prováděných službami, které AMQP implementují (či možná lépe řečeno akceptují). Jak uvidíme dále, jsou termíny používané v dokumentaci RabbitMQ odvozeny právě od terminologie převzaté ze specifikace AMQP. Specifikace pro verzi 0–9–1 je dostupná na stránce https://www.rabbitmq.com/resources/specs/amqp0–9–1.pdf.
Kromě verzí 0–9–1, 0–9 a 0–8 existuje ještě protokol AMQP verze 1.0. Jedná se o protokol, který je i přes shodné jméno značně odlišný od předchozích verzí. RabbitMQ sice ve své základní konfiguraci tento protokol nepodporuje, ovšem je možné si nainstalovat přídavný modul (plugin), který podporu do Rabbitu přidává. Další informace o tomto modulu naleznete v jeho repositáři na adrese https://github.com/rabbitmq/rabbitmq-amqp1.0 (v článku ani v demonstračních příkladech ho nepoužijeme).
Třetím protokolem, který lze použít pro komunikaci s frontami Rabbita, je protokol nazvaný STOMP neboli Streaming Text Oriented Messaging Protocol. Jedná se o relativně jednoduchý protokol založený – jak jeho název napovídá – na příkazech posílaných v textovém formátu se syntaxí, která se podobá protokolu HTTP. Předností je snadná implementace klientů a v případě potřeby (ladění, simulace útoků atd.) je dokonce možné namísto dedikovaného klienta použít běžný telnet.
Posledním protokolem, o němž se zmíníme a který je podporován přes plugin (stejně jako AMQP 1.0), je protokol nazvaný MQTT, který je mj. určený pro dnes populární IoT, tj. obecně pro zařízení s relativně malým výkonem popř. omezenými systémovými zdroji (a většinou i omezenou odolností proti útokům :).
4. Ukázky základních způsobů použití nástroje RabbitMQ
Samotný RabbitMQ je možné použít mnoha různými způsoby. Na nejnižší úrovni se na tento systém můžeme dívat jako na klasickou a jednoduchou frontu zpráv (message queue). Tuto frontu je přitom možné nakonfigurovat takovým způsobem, aby byla zachována její robustnost, tj. aby zprávy ve frontě přežily restart serveru RabbitMQ a aby bylo zaručeno doručení zprávy (příjemce ovšem musí potvrdit, že zprávu převzal a nějakým způsobem zpracoval, což si ukážeme na praktických příkladech). Tuto konfiguraci můžete použít kdykoli je nutné propojit několik komponent do složitější architektury, přičemž jedna z komponent bude produkovat zprávy (data, příkazy) a další komponenta/komponenty zprávy přijímat a asynchronně je zpracovávat:
Obrázek 1: RabbitMQ je možné použít jako jednoduchou frontu zpráv, přičemž zpráva může vznikat na jiném počítači, než na kterém běží samotná fronta. I příjemce může být provozován na samostatném počítači (počítačích). Počet front, které jsou pojmenovány, není žádným drastickým způsobem omezen – v praxi se může jednat o stovky front.
RabbitMQ se dále poměrně často používá v roli brokera i pro plánování a asynchronní spouštění úloh. Ostatně přesně v této roli je RabbitMQ použit i systémem Celery, s nímž jsme se seznámili v předchozích dvou článcích [1] [].
Obrázek 2: RabbitMQ jako klasický broker využívaný dalšími systémy.
V případě, že se RabbitMQ využívá v roli brokera, umožňuje připojení různých konzumentů a nastavení strategií pro směrování zpráv mezi producenty a konzumenty. Samozřejmě je implementováno i rozložení zátěže mezi větší množství konzumentů v případě, že všichni tito konzumenti implementují stejnou logiku (službu). S tímto systémem jsme se ostatně již setkali při popisu Redis Queue i Celery, v nichž bylo relativně snadné spustit větší množství workerů a celou aplikaci tak škálovat podle potřeby:
Obrázek 3: Počet konzumentů není v podstatě nijak omezen.
Dále je možné nakonfigurovat rozvětvení (fanout) zpráv, tj. každá zpráva může být doručena většímu množství příjemců. Příkladem může být vzájemná distribuce textových zpráv mezi několika servery implementující internetová „kecátka“ (Slack, Mattermost, Jabber/XMPP…). Každý server přitom může současně vystupovat v roli příjemce zpráv i producenta zpráv.
5. Směrování zpráv před jejich vložením do fronty
Interní struktura nástroje RabbitMQ je ve skutečnosti mnohem složitější, než by se mohlo na první pohled zdát. Ústředním prvkem jsou samozřejmě fronty se zprávami, ovšem před samotnými frontami je umístěn takzvaný exchange sloužící pro směrování zpráv do jednotlivých front:
Obrázek 4: Interní konfigurovatelná struktura systému RabbitMQ.
Samotný broker, v tomto případě RabbitMQ, podporuje několik strategií pro rozhodnutí, do jaké fronty či do jakých front se má přijatá zpráva vložit. Každá z těchto strategií je samozřejmě plně konfigurovatelná a má své jméno, které je dobré znát při hledání podrobnějších informací v dokumentaci:
- Nejjednodušší strategie se jmenuje direct. Tato strategie je založena na tom, že samotná zpráva obsahuje klíč (key), který slouží pro výběr správné fronty. Pokud budeme mít k dispozici jedinou frontu a budeme používat jeden klíč, celý broker se nám vlastně zúží na „obyčejnou“ frontu zpráv podporující různé protokoly a nabízejí řešení s vysokou dostupností. Klíč je reprezentován jako běžný řetězec.
- Další strategie se nazývá topic. Jedná se o složitější formu navázání zprávy na frontu, v němž se opět používá klíč uložený ve zprávě. Tento klíč se porovnává s regulárními výrazy specifikovanými v konfiguraci směrovače. Ve chvíli, kdy klíč odpovídá nějakému regulárnímu výrazu, je zpráva přesměrována do příslušné fronty. Můžeme tak například velmi snadno směrovat zprávy do různých front (a tím pádem i do různých workerů) na základě doménového jména serveru, kde zpráva vznikla apod.
- Třetí strategie používá hlavičky (headers) připojené ke zprávě. To umožňuje detailnější konfiguraci směrování; podrobnosti si popíšeme v navazujícím článku.
- A konečně čtvrtá strategie se nazývá fanout. Při použití této strategie se přijatá zpráva přenese (zduplikuje) do několika nakonfigurovaných front, což znamená, že bude přijata a zpracována několikrát. V praxi se například může jednat o přeposlání zprávy napsané klientem na různé servery implementující nějakou internetovou komunikační službu (Slack atd.).
V dnešním článku se budeme převážně zabývat první strategií, tj. přímým přeposláním přijaté zprávy do fronty určené klíčem ve zprávě. Trošku předběhneme, ovšem asi je vhodné si ukázat, jak se vlastně zpráva s klíčem pošle. Ve chvíli, kdy má klient nakonfigurovaný a otevřený komunikační kanál na brokera, může zprávu s klíčem poslat takto:
channel.basic_publish(exchange='', routing_key='test', body='Hello World!')
Povšimněte si, že se u zprávy specifikuje jak její hodnota/tělo (v našem případě se jedná o pouhopouhý řetězec), tak i klíč a popř. i jméno tzv. exchange, která souvisí s vybranou strategií. V případě, že strategie není specifikována, což je ostatně náš případ, bude RabbitMQ server předpokládat výchozí volbu exchange (viz též podrobnější popis na stránce http://www.rabbitmq.com/amqp-0–9–1-reference.html#basic.publish).
6. Instalace RabbitMQ a konfigurace jeho síťového rozhraní
Instalace systému RabbitMQ je na mainstreamových distribucích velmi snadná, pokud se ovšem spokojíme s tím, že může být použita poněkud starší verze Rabbitu. Balíček nazvaný rabbitmq-server je dostupný ve standardních repositářích Debianu, Ubuntu, RHELu, CentOSu i Fedory. Vyzkoušejme si instalaci na Fedoře, v níž byla v době instalace dostupná verze 3.6.16 (nejnovější verze Rabbitu je přitom 3.7.9). Instalaci provedeme ze standardního repositáře přičemž se společně s Rabbitem nainstaluje i běhové prostředí Erlangu:
# dnf install rabbitmq-server Last metadata expiration check: 0:22:16 ago on Fri 16 Nov 2018 10:14:57 AM EST. Dependencies resolved. ========================================================================================================================================================= Package Arch Version Repository Size ========================================================================================================================================================= Installing: rabbitmq-server noarch 3.6.16-3.fc29 beaker-Fedora-Everything 4.7 M Installing dependencies: erlang-asn1 x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 742 k erlang-compiler x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 1.1 M erlang-crypto x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 124 k erlang-eldap x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 111 k erlang-erts x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 2.0 M erlang-hipe x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 2.8 M erlang-inets x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 781 k erlang-kernel x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 1.1 M erlang-mnesia x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 767 k erlang-os_mon x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 110 k erlang-otp_mibs x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 27 k erlang-public_key x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 604 k erlang-runtime_tools x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 208 k erlang-sasl x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 290 k erlang-sd_notify x86_64 1.0-8.fc29 beaker-Fedora-Everything 14 k erlang-snmp x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 1.7 M erlang-ssl x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 859 k erlang-stdlib x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 2.9 M erlang-syntax_tools x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 408 k erlang-tools x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 638 k erlang-xmerl x86_64 20.3.8.9-2.fc29 beaker-Fedora-Everything 995 k lksctp-tools x86_64 1.0.16-10.fc29 beaker-Fedora-Everything 92 k logrotate x86_64 3.14.0-4.fc29 beaker-Fedora-Everything 68 k xemacs-filesystem noarch 21.5.34-30.20171230hg92757c2b8239.fc29 beaker-Fedora-Everything 10 k Transaction Summary ========================================================================================================================================================= Install 25 Packages Total download size: 23 M Installed size: 36 M Is this ok [y/N] Downloading Packages: (1/25): erlang-crypto-20.3.8.9-2.fc29.x86_64.rpm 1.4 MB/s | 124 kB 00:00 (2/25): erlang-asn1-20.3.8.9-2.fc29.x86_64.rpm 5.9 MB/s | 742 kB 00:00 (24/25): xemacs-filesystem-21.5.34-30.20171230hg92757c2b8239.fc29.noarch.rpm 218 kB/s | 10 kB 00:00 (25/25): rabbitmq-server-3.6.16-3.fc29.noarch.rpm 21 MB/s | 4.7 MB 00:00 -------------------------------------------------------------------------------------------------------------------------------------------------------------- Total 27 MB/s | 23 MB 00:00 Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Installed: rabbitmq-server-3.6.16-3.fc29.noarch erlang-asn1-20.3.8.9-2.fc29.x86_64 erlang-compiler-20.3.8.9-2.fc29.x86_64 erlang-crypto-20.3.8.9-2.fc29.x86_64 erlang-eldap-20.3.8.9-2.fc29.x86_64 erlang-erts-20.3.8.9-2.fc29.x86_64 erlang-hipe-20.3.8.9-2.fc29.x86_64 erlang-inets-20.3.8.9-2.fc29.x86_64 erlang-kernel-20.3.8.9-2.fc29.x86_64 erlang-mnesia-20.3.8.9-2.fc29.x86_64 erlang-os_mon-20.3.8.9-2.fc29.x86_64 erlang-otp_mibs-20.3.8.9-2.fc29.x86_64 erlang-public_key-20.3.8.9-2.fc29.x86_64 erlang-runtime_tools-20.3.8.9-2.fc29.x86_64 erlang-sasl-20.3.8.9-2.fc29.x86_64 erlang-sd_notify-1.0-8.fc29.x86_64 erlang-snmp-20.3.8.9-2.fc29.x86_64 erlang-ssl-20.3.8.9-2.fc29.x86_64 erlang-stdlib-20.3.8.9-2.fc29.x86_64 erlang-syntax_tools-20.3.8.9-2.fc29.x86_64 erlang-tools-20.3.8.9-2.fc29.x86_64 erlang-xmerl-20.3.8.9-2.fc29.x86_64 lksctp-tools-1.0.16-10.fc29.x86_64 logrotate-3.14.0-4.fc29.x86_64 xemacs-filesystem-21.5.34-30.20171230hg92757c2b8239.fc29.noarch Complete!
Po úspěšné instalaci si můžeme vyzkoušet server přímo spustit. V praxi se sice přímé spouštění neprovádí (lepší je RabbitMQ spustit jako démona), ale pro první odzkoušení, zda jsou dostupné všechny knihovny, je to pravděpodobně nejjednodušší cesta:
$ su - # rabbitmq-server RabbitMQ 3.6.16. Copyright (C) 2007-2018 Pivotal Software, Inc. ## ## Licensed under the MPL. See http://www.rabbitmq.com/ ## ## ########## Logs: /var/log/rabbitmq/rabbit@intel-sugarbay-dh-02.log ###### ## /var/log/rabbitmq/rabbit@intel-sugarbay-dh-02-sasl.log ########## Starting broker... completed with 0 plugins.
Pokud se spuštění podařilo, server ukončíme stiskem klávesové zkratky Ctrl+C a před dalšími pokusy omezíme možnosti síťového připojení takovým způsobem, aby se k serveru mohly připojit pouze lokální uživatelé a lokální služby. V souboru /etc/rabbitmq/rabbitmq.config je nutné provést následující změny, kterými se zajistí, že server bude přijímat pouze lokální požadavky:
[ {rabbit, [ {tcp_listeners, [{"127.0.0.1", 5672}, {"::1", 5672}]} ] ... ... ... }]
7. Spuštění serveru ve funkci démona/služby
Většinou se RabbitMQ spouští ve funkci démona, resp. služby. Pro operační systémy s klasickým Init systémem postačuje provést:
# chkconfig rabbitmq-server on # service rabbitmq-server start
Pro systému používající Systemd lze použít buď předchozí příkazy (ty jsou transformovány na příkazy nové), nebo:
# systemctl enable rabbitmq-server.service # systemctl start rabbitmq-server.service
S kontrolou, zda se spuštění služby podařilo:
# systemctl status rabbitmq-server.service ● rabbitmq-server.service - RabbitMQ broker Loaded: loaded (/usr/lib/systemd/system/rabbitmq-server.service; enabled; ven Active: active (running) since Wed 2018-12-19 14:39:45 CET; 33s ago Main PID: 20495 (beam.smp) Status: "Initialized" Tasks: 143 (limit: 4915) CGroup: /system.slice/rabbitmq-server.service ├─20495 /usr/lib64/erlang/erts-8.3.5.4/bin/beam.smp -W w -A 128 -P 10 ├─20774 erl_child_setup 1024 ├─20790 inet_gethost 4 └─20791 inet_gethost 4 pro 19 14:39:43 localhost.localdomain systemd[1]: Starting RabbitMQ broker... pro 19 14:39:44 localhost.localdomain rabbitmq-server[20495]: Rabb pro 19 14:39:44 localhost.localdomain rabbitmq-server[20495]: ## ## Lice pro 19 14:39:44 localhost.localdomain rabbitmq-server[20495]: ## ## pro 19 14:39:44 localhost.localdomain rabbitmq-server[20495]: ########## Logs pro 19 14:39:44 localhost.localdomain rabbitmq-server[20495]: ###### ## pro 19 14:39:44 localhost.localdomain rabbitmq-server[20495]: ########## pro 19 14:39:44 localhost.localdomain rabbitmq-server[20495]: Star pro 19 14:39:45 localhost.localdomain systemd[1]: Started RabbitMQ broker. pro 19 14:39:45 localhost.localdomain rabbitmq-server[20495]: completed with 0
8. Instalace knihovny Pika s rozhraním mezi RabbitMQ a Pythonem
Posledním přípravným krokem je instalace knihovny Pika, která programátorům nabízí rozhraní mezi systémem RabbitMQ na straně jedné a Pythonem (resp. skripty napsanými v Pythonu) na straně druhé. Instalaci provedeme přes nástroj pip3, přičemž budeme specifikovat, že se má instalace provést pouze pro aktivního uživatele, aby se nezasahovalo do systémových knihoven a modulů:
$ pip3 install --user pika Collecting pika Using cached https://files.pythonhosted.org/packages/bf/48/72de47f63ba353bacd74b76bb65bc63620b0706d8b0471798087cd5a4916/pika-0.12.0-py2.py3-none-any.whl Installing collected packages: pika Successfully installed pika-0.12.0
9. První příklad: nejjednodušší možná implementace systému producent+konzument
V této chvíli je náš testovací počítač připraven na příjem zpráv od producentů a na jejich následné zpracování konzumenty. Nejprve si ukažme, jak může vypadat producent. Jedná se o velmi krátký skript naprogramovaný v Pythonu, který se nejdříve připojí k serveru RabbitMQ, následně požádá o vytvoření či o zpřístupnění fronty nazvané „test“, pošle do fronty zprávu (nepřímo přes exchange) a následně zavře připojení k serveru:
import pika # připojení k serveru RabbitMQ a vytvoření komunikačního kanálu connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # žádost o vytvoření či o použití fronty „test“ channel.queue_declare(queue='test') # poslání zprávy se strategií direct channel.basic_publish(exchange='', routing_key='test', body='Hello World!') print("Sent 'Hello World!'") # uzavření komunikace connection.close()
Zajímavý je řádek:
channel.queue_declare(queue='test')
V případě, že fronta nazvaná „test“ neexistuje, je vytvořena. Pokud však fronta s tímto jménem existuje, nic se neděje – nedojde k jejímu vymazání ani k vyprázdnění (operace je idempotentní).
Dále si povšimněte, že používáme shodné jméno klíče a fronty. To je v pořádku, protože ve výchozím nastavení se využívá strategie direct, která přesměruje zprávu do fronty, jejíž název odpovídá klíči.
Pokud nyní výše uvedený skript spustíme:
$ python3 enqueue_work.py
… měla by se do fronty „test“ vložit jediná zpráva. O tom se můžeme snadno přesvědčit s využitím nástroje rabbitmqctl:
$ sudo rabbitmqctl list_queues Listing queues test 1
Samozřejmě je možné do fronty poslat větší počet zpráv, a to mírně upraveným producentem:
#!/usr/bin/env python import pika connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() channel.queue_declare(queue='test') for i in range(1, 11): channel.basic_publish(exchange='', routing_key='test', body='Hello World! #{i}'.format(i=i)) print("Sent 'Hello World!' ten times") connection.close()
Nyní by se mělo ve frontě nacházet jedenáct zpráv – jedna původní a deset nových:
$ sudo rabbitmqctl list_queues Listing queues test 11
10. Konzument zpráv
Implementace konzumenta (příjemce) zpráv je nepatrně složitější, protože je nutné deklarovat callback funkci automaticky zavolanou ve chvíli, kdy je zpráva z fronty přijata. Pro tento účel se používá metoda:
channel.basic_consume(on_receive, queue='test', no_ack=True)
Této metodě se předá reference na již zmíněnou callback funkci, dále jméno fronty, ze které se mají zprávy přijímat a konečně jako nepovinný parametr lze specifikovat, zda bude konzument aktivně informovat systém RabbitMQ o tom, že zpráva byla zpracována. Prozatím potvrzování o zpracování zpráv nebudeme potřebovat, takže parametr no_ack nastavíme na True.
Celá implementace konzumenta zpráv bude vypadat takto:
#!/usr/bin/env python import pika # připojení k serveru RabbitMQ a vytvoření komunikačního kanálu connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # pokus o nové vytvoření fronty ve skutečnosti neovlivní již existující frontu # pokud samozřejmě existuje channel.queue_declare(queue='test') # callback funkce volaná při přijetí zprávy def on_receive(ch, method, properties, body): print("Received %r" % body) # přihlášení se k odebírání zpráv z fronty "test" channel.basic_consume(on_receive, queue='test', no_ack=True) print('Waiting for messages. To exit press CTRL+C') print("...") # smyčka s odebíráním zpráv channel.start_consuming()
V případě, že konzumenta spustíme, zpracuje všechny zbývající zprávy z fronty „test“. Pokud jste v předchozí kapitole postupovali podle návodu, mělo by se jednat o jedenáct zpráv:
$ python3 consumer.py Waiting for messages. To exit press CTRL+C ... Received b'Hello World!' Received b'Hello World! #1' Received b'Hello World! #2' Received b'Hello World! #3' Received b'Hello World! #4' Received b'Hello World! #5' Received b'Hello World! #6' Received b'Hello World! #7' Received b'Hello World! #8' Received b'Hello World! #9' Received b'Hello World! #10'
11. Přenos společných prvků producenta a konzumenta do jednoho zdrojového kódu
Před dalšími pokusy nepatrně upravíme kód producenta i konzumenta (spotřebitele), protože jsme mohli vidět, že samotné připojení k serveru RabbitMQ je realizováno shodným kódem. Z tohoto kódu je tedy možné vytvořit samostatný modul, který může vypadat následovně:
import pika def connect(where='localhost', queue_name='test'): # připojení k serveru RabbitMQ a vytvoření komunikačního kanálu connection = pika.BlockingConnection(pika.ConnectionParameters(where)) channel = connection.channel() # pokus o nové vytvoření fronty ve skutečnosti neovlivní již existující frontu # pokud samozřejmě existuje channel.queue_declare(queue=queue_name) return connection, channel
Skript, který po svém spuštění vytvoří jednu zprávu a vloží ji do fronty „test“, se zjednoduší:
#!/usr/bin/env python import pika connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) connection, channel = connect() channel.basic_publish(exchange='', routing_key='test', body='Hello World!') print("Sent 'Hello World!'") connection.close()
Podobným způsobem se zjednoduší i skript pro vytvoření deseti zpráv:
#!/usr/bin/env python from rabbitmq_connect import connect connection, channel = connect() for i in range(1, 11): channel.basic_publish(exchange='', routing_key='test', body='Hello World! #{i}'.format(i=i)) print("Sent 'Hello World!' ten times") connection.close()
Prakticky stejná úprava se samozřejmě dotkne i konzumenta zpráv:
#!/usr/bin/env python from rabbitmq_connect import connect connection, channel = connect() def on_receive(ch, method, properties, body): print("Received %r" % body) channel.basic_consume(on_receive, queue='test', no_ack=True) print('Waiting for messages. To exit press CTRL+C') print("...") channel.start_consuming()
12. Pomalá práce příjemce zpráv a způsob dělby práce mezi větší množství workerů
Pro další testování nepatrně upravíme kód konzumenta takovým způsobem, aby simuloval (sic!) složitější a déletrvající práci. Pro naše účely bude postačovat, když bude konzument v callback funkci zavolané při příjmu nové zprávy čekat zhruba pět sekund:
def on_receive(ch, method, properties, body): print("Received %r" % body) sleep(5) print("Done processing %r" % body)
Pokud nyní spustíme dva konzumenty, každý v jiném terminálu, budeme moci vidět, jak se dělí o práci.
První konzument:
$ python3 consumer.py Waiting for messages. To exit press CTRL+C ... Received b'Hello World! #1' Done processing b'Hello World! #1' Received b'Hello World! #3' Done processing b'Hello World! #3' Received b'Hello World! #5' Done processing b'Hello World! #5' Received b'Hello World! #7' Done processing b'Hello World! #7' Received b'Hello World! #9' Done processing b'Hello World! #9'
Druhý konzument:
$ python3 consumer.py Waiting for messages. To exit press CTRL+C ... Received b'Hello World! #2' Done processing b'Hello World! #2' Received b'Hello World! #4' Done processing b'Hello World! #4' Received b'Hello World! #6' Done processing b'Hello World! #6' Received b'Hello World! #8' Done processing b'Hello World! #8' Received b'Hello World! #10' Done processing b'Hello World! #10'
Toto rozdělování práce ve skutečnosti provádí přímo RabbitMQ a nazývá se „round robin“. V dalším textu poznáme, že výchozí chování není ideální ve chvíli, kdy se konzumenti (workeři) připojují a odpojují dynamicky.
13. Potvrzování zpracování zprávy, chování systému ve chvíli, kdy zpráva není zpracována
V předchozích příkladech jste si mohli povšimnout, že se konzument připojoval k RabbitMQ následovně:
channel.basic_consume(on_receive, queue='test', no_ack=True)
Posledním pojmenovaným parametrem jsme specifikovali, že jakmile konzument zprávu z fronty odebere, je zpráva považována za doručenou a RabbitMQ ji z fronty odstraní a úspěšně na ni zapomene. Ovšem v praxi se může stát (a na produkci zcela jistě tato situace nastane!), že konzument z nějakého důvodu zhavaruje nebo nebude připraven zpracování zprávy dokončit. Pokud budeme chtít, aby se v takovém případě zpráva vrátila zpět do fronty (a byla například zpracována jiným strojem), musíme připojení nepatrně upravit:
channel.basic_consume(on_receive, queue='test', no_ack=False)
V tomto případě se konzument zavazuje k tomu, že (ideálně) až na konci zpracování potvrdí zpracování zprávy:
ch.basic_ack(delivery_tag = method.delivery_tag)
V případě, že RabbitMQ nedostane informaci o zpracování zprávy, vloží ji do k tomu určené fronty (podrobnosti, jak tyto fronty nakonfigurovat, si řekneme příště).
Vylepšený kód konzumenta tedy může vypadat následovně:
#!/usr/bin/env python from time import sleep from rabbitmq_connect import connect connection, channel = connect() def on_receive(ch, method, properties, body): print("Received %r" % body) sleep(5) print("Done processing %r" % body) ch.basic_ack(delivery_tag = method.delivery_tag) channel.basic_consume(on_receive, queue='test', no_ack=False) print('Waiting for messages. To exit press CTRL+C') print("...") channel.start_consuming()
14. Vylepšené chování RabbitMQ při „dělbě práce“ mezi workery
Ve dvanácté kapitole jsme si ukázali, že v případě, že jsou spuštěni dva konzumenti (nebo pochopitelně větší množství konzumentů), budou se spravedlivě dělit o práci, tj. o zpracování zpráv přidaných do zvolené fronty. Ovšem samotná dělba práce není ve výchozím nastavení založena na tom, jak jsou konzumenti (resp. workeři, kteří konzumenty implementují) vytíženi, protože o tom, jaký konzument zprávu zpracuje, je rozhodnuto už ve chvíli jejího přijetí do RabbitMQ. Toto chování není příliš vhodné, protože systém jako celek nedokáže zareagovat například na přidání nového konzumenta do systému. Toto chování si ostatně sami můžete otestovat ve chvíli, kdy nějaký konzument již zpracovává zprávy – přidání (spuštění) nového konzumenta nemá vliv na to, že původní konzument má svoji práci naplánovanou.
Aby RabbitMQ dokázal práci rozdělovat spravedlivěji a dynamicky podle aktuálního zatížení, musíte v konzumentovi provést jednoduchou změnu:
channel.basic_qos(prefetch_count=1) channel.basic_consume(on_receive, queue='test', no_ack=False)
Nyní je možné konzumenty přidávat, popř. vypínat a systém by měl stále spravedlivě rozdělovat práci a reagovat i na to, že některá úloha trvá delší dobu.
Opět si pro úplnost ještě jednou vypišme celý kód konzumenta:
#!/usr/bin/env python from time import sleep from rabbitmq_connect import connect connection, channel = connect() def on_receive(ch, method, properties, body): print("Received %r" % body) sleep(5) print("Done processing %r" % body) ch.basic_ack(delivery_tag = method.delivery_tag) channel.basic_qos(prefetch_count=1) channel.basic_consume(on_receive, queue='test', no_ack=False) print('Waiting for messages. To exit press CTRL+C') print("...") channel.start_consuming()
15. Konzument zpráv naprogramovaný v jazyku Clojure
V závěrečné části článku si ukážeme, jakým způsobem lze se systémem RabbitMQ komunikovat z programovacího jazyka Clojure. Pro tento jazyk vzniklo hned několik knihoven, které rozhraní k RabbitMQ realizují. Většina těchto knihoven je postavena na tzv. Java interop (interoperabilita mezi Javou a Clojure). Rozdíly mezi knihovnami spočívají v tom, zda se skutečně jedná o pouhou úzkou vrstvičku mezi Javou a Clojure či zda knihovna realizuje vlastní složitější (a abstraktnější) framework. Protože se zabýváme především RabbitMQ a nikoli nad ním postavenými systémy, použijeme knihovnu Langohr, která nám nebude poskytovat příliš abstraktní operace, což je dobře, protože jediné, co budeme potřebovat, je získávání zpráv z fronty s jejich dalším zpracováním.
16. Konfigurace projektu využívajícího knihovnu Langohr
Na úplném začátku si připravíme kostru projektu, který bude představovat konzumenta. Pro vytvoření této kostry použijeme Leiningen. Kostra projektu se při použití Leiningenu vytvoří příkazem:
$ lein new app example-01 Generating a project called example-01 based on the 'app' template.
Výsledkem tohoto příkazu by měla být následující adresářová struktura:
. ├── CHANGELOG.md ├── doc │ └── intro.md ├── LICENSE ├── project.clj ├── README.md ├── src │ └── example_01 │ └── core.clj └── test └── example_01 └── core_test.clj
V dalším kroku přistoupíme k úpravám projektového souboru project.clj. Po vytvoření nového projektu by projektový soubor měl vypadat přibližně takto (pouze si pro jistotu zkontrolujte verzi interpretru jazyka Clojure; minimální požadovaná verze je 1.8.0):
(defproject example-01 "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.8.0"]] :main ^:skip-aot example-01.core :target-path "target/%s" :profiles {:uberjar {:aot :all}})
Úprava projektového souboru spočívá v přidání informace o tom, že se v projektu bude používat knihovna langohr verze 5.0.0:
(defproject example-01 "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.8.0"] [com.novemberain/langohr "5.0.0"]] :main ^:skip-aot example-01.core :target-path "target/%s" :profiles {:uberjar {:aot :all}})
Posledním krokem konfigurace projektu je spuštění příkazu:
$ lein deps
Tento příkaz zajistí, že se do adresáře ~/.m2/ stáhnou všechny potřebné knihovny, tj. jak langohr, tak i její závislosti.
17. Realizace konzumenta pro příjem zpráv
Samotná implementace konzumenta zpráv (vzniklá úpravou getting started příkladu) je při použití programovacího jazyka Clojure nepatrně delší, než je tomu v případě Pythonu. Je tomu tak především proto, že knihovna Langohr je rozdělena na víc částí a budeme muset provést import čtyř konkrétních jmenných prostorů:
(require '[langohr.core :as rabbit-mq]) (require '[langohr.channel :as l-channel]) (require '[langohr.queue :as l-queue]) (require '[langohr.consumers :as l-consumers])
Dále je v konzumentovi deklarována callback funkce zavolaná při příjmu každé zprávy. Povšimněte si, že tělo zprávy (poslední parametr) je typu bytes, ovšem v těle callback funkce ze sekvence bajtů vytvoříme řetězec. Zajímavý je i destructuring [1] použitý u druhého parametru. Jedná se o specialitu nabízenou některými Lispovskými jazyky ve chvíli, kdy se do funkcí předávají sekvence, vektory nebo mapy (slovníky):
(defn message-handler [ch {:keys [content-type delivery-tag type] :as meta} ^bytes payload] (println (format "Received a message: %s" (String. payload "UTF-8"))))
Zbývá nám provést připojení k RabbitMQ a vytvoření komunikačního kanálu:
(let [conn (rabbit-mq/connect) ch (l-channel/open conn)]
Další postup je prakticky totožný s kódem naprogramovaným v Pythonu: deklarace fronty, s níž se pracuje, přihlášení k příjmu zpráv s registrací callback funkce a na konci aplikace úklid – uzavření komunikačního kanálu a uzavření připojení k RabbitMQ:
(l-queue/declare ch "test" {:exclusive false :auto-delete false}) (l-consumers/subscribe ch "test" message-handler {:auto-ack true}) (println (format "Connected to channel id: %d" (.getChannelNumber ch))) (Thread/sleep 10000) (println "Disconnecting...") (rabbit-mq/close ch) (rabbit-mq/close conn)))
Výsledný kód realizující celého konzumenta vypadá následovně:
(ns example-01.core (:gen-class)) (require '[langohr.core :as rabbit-mq]) (require '[langohr.channel :as l-channel]) (require '[langohr.queue :as l-queue]) (require '[langohr.consumers :as l-consumers]) (defn message-handler [ch {:keys [content-type delivery-tag type] :as meta} ^bytes payload] (println (format "Received a message: %s" (String. payload "UTF-8")))) (defn -main [& args] (let [conn (rabbit-mq/connect) ch (l-channel/open conn)] (l-queue/declare ch "test" {:exclusive false :auto-delete false}) (l-consumers/subscribe ch "test" message-handler {:auto-ack true}) (println (format "Connected to channel id: %d" (.getChannelNumber ch))) (Thread/sleep 10000) (println "Disconnecting...") (rabbit-mq/close ch) (rabbit-mq/close conn)))
18. Spuštění konzumenta a příjem zpráv
Před spuštěním konzumenta přidáme do fronty „test“ několik zpráv. Můžeme přitom použít skript, s nímž jsme se seznámili v první polovině článku. Tento skript vloží do fronty deset zpráv, každou s jiným tělem:
$ cd message-queues-examples/rabbit-mq/python/example01/ $ python3 enqueue_more_work.py Sent 'Hello World!' ten times
Přesvědčíme se, že fronta existuje a není prázdná:
$ sudo rabbitmqctl list_queues Listing queues test 10
Následně se již můžeme přepnout do adresáře s projektem naprogramovaným v Clojure a spustit konzumenta:
$ cd message-queues-examples/rabbit-mq/clojure/example01/ $ lein run Connected to channel id: 1 Received a message: Hello World! #1 Received a message: Hello World! #2 Received a message: Hello World! #3 Received a message: Hello World! #4 Received a message: Hello World! #5 Received a message: Hello World! #6 Received a message: Hello World! #7 Received a message: Hello World! #8 Received a message: Hello World! #9 Received a message: Hello World! #10 Disconnecting...
Vidíme, že konzument bez problémů přečetl všechny zprávy a příslušným způsobem je zpracoval.
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes popsaných demonstračních příkladů naprogramovaných v Pythonu a jednoho příkladu v Clojure byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/message-queues-examples (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má doslova několik kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce.
Příklady v Pythonu:
Poslední příklad je naprogramován v jazyce Clojure. Proto se jedná o ucelený projekt s běžnou strukturou vyžadovanou nástrojem Leiningen. Tento projekt naleznete na adrese https://github.com/tisnik/message-queues-examples/tree/master/rabbit-mq/clojure/example01.
20. Odkazy na Internetu
- Advanced Message Queuing Protocol
https://www.amqp.org/ - Advanced Message Queuing Protocol na Wikipedii
https://en.wikipedia.org/wiki/Advanced_Message_Queuing_Protocol - RabbitMQ
https://www.rabbitmq.com/ - RabbitMQ Tutorials
https://www.rabbitmq.com/getstarted.html - RabbitMQ: Clients and Developer Tools
https://www.rabbitmq.com/devtools.html - RabbitMQ na Wikipedii
https://en.wikipedia.org/wiki/RabbitMQ - Streaming Text Oriented Messaging Protocol
https://en.wikipedia.org/wiki/Streaming_Text_Oriented_Messaging_Protocol - Message Queuing Telemetry Transport
https://en.wikipedia.org/wiki/MQTT - Erlang
http://www.erlang.org/ - pika 0.12.0 na PyPi
https://pypi.org/project/pika/ - Introduction to Pika
https://pika.readthedocs.io/en/stable/ - Langohr: An idiomatic Clojure client for RabbitMQ that embraces the AMQP 0.9.1 model
http://clojurerabbitmq.info/ - AMQP 0–9–1 Model Explained
http://www.rabbitmq.com/tutorials/amqp-concepts.html - Part 1: RabbitMQ for beginners – What is RabbitMQ?
https://www.cloudamqp.com/blog/2015–05–18-part1-rabbitmq-for-beginners-what-is-rabbitmq.html - Downloading and Installing RabbitMQ
https://www.rabbitmq.com/download.html - celery na PyPi
https://pypi.org/project/celery/ - Databáze Redis (nejenom) pro vývojáře používající Python
https://www.root.cz/clanky/databaze-redis-nejenom-pro-vyvojare-pouzivajici-python/ - Databáze Redis (nejenom) pro vývojáře používající Python (dokončení)
https://www.root.cz/clanky/databaze-redis-nejenom-pro-vyvojare-pouzivajici-python-dokonceni/ - Použití nástroje RQ (Redis Queue) pro správu úloh zpracovávaných na pozadí
https://www.root.cz/clanky/pouziti-nastroje-rq-redis-queue-pro-spravu-uloh-zpracovavanych-na-pozadi/ - Redis Queue (RQ)
https://www.fullstackpython.com/redis-queue-rq.html - Python Celery & RabbitMQ Tutorial
https://tests4geeks.com/python-celery-rabbitmq-tutorial/ - Flower: Real-time Celery web-monitor
http://docs.celeryproject.org/en/latest/userguide/monitoring.html#flower-real-time-celery-web-monitor - Asynchronous Tasks With Django and Celery
https://realpython.com/asynchronous-tasks-with-django-and-celery/ - First Steps with Celery
http://docs.celeryproject.org/en/latest/getting-started/first-steps-with-celery.html - node-celery
https://github.com/mher/node-celery - Full Stack Python: web development
https://www.fullstackpython.com/web-development.html - Introducing RQ
https://nvie.com/posts/introducing-rq/ - Asynchronous Tasks with Flask and Redis Queue
https://testdriven.io/asynchronous-tasks-with-flask-and-redis-queue - rq-dashboard
https://github.com/eoranged/rq-dashboard - Stránky projektu Redis
https://redis.io/ - Introduction to Redis
https://redis.io/topics/introduction - Try Redis
http://try.redis.io/ - Redis tutorial, April 2010 (starší, ale pěkně udělaný)
https://static.simonwillison.net/static/2010/redis-tutorial/ - Python Redis
https://redislabs.com/lp/python-redis/ - Redis: key-value databáze v paměti i na disku
https://www.zdrojak.cz/clanky/redis-key-value-databaze-v-pameti-i-na-disku/ - Praktický úvod do Redis (1): vaše distribuovaná NoSQL cache
http://www.cloudsvet.cz/?p=253 - Praktický úvod do Redis (2): transakce
http://www.cloudsvet.cz/?p=256 - Praktický úvod do Redis (3): cluster
http://www.cloudsvet.cz/?p=258 - Connection pool
https://en.wikipedia.org/wiki/Connection_pool - Instant Redis Sentinel Setup
https://github.com/ServiceStack/redis-config - How to install REDIS in LInux
https://linuxtechlab.com/how-install-redis-server-linux/ - Redis RDB Dump File Format
https://github.com/sripathikrishnan/redis-rdb-tools/wiki/Redis-RDB-Dump-File-Format - Lempel–Ziv–Welch
https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch - Redis Persistence
https://redis.io/topics/persistence - Redis persistence demystified
http://oldblog.antirez.com/post/redis-persistence-demystified.html - Redis reliable queues with Lua scripting
http://oldblog.antirez.com/post/250 - Ost (knihovna)
https://github.com/soveran/ost - NoSQL
https://en.wikipedia.org/wiki/NoSQL - Shard (database architecture)
https://en.wikipedia.org/wiki/Shard_%28database_architecture%29 - What is sharding and why is it important?
https://stackoverflow.com/questions/992988/what-is-sharding-and-why-is-it-important - What Is Sharding?
https://btcmanager.com/what-sharding/ - Redis clients
https://redis.io/clients - Category:Lua-scriptable software
https://en.wikipedia.org/wiki/Category:Lua-scriptable_software - Seriál Programovací jazyk Lua
https://www.root.cz/serialy/programovaci-jazyk-lua/ - Redis memory usage
http://nosql.mypopescu.com/post/1010844204/redis-memory-usage - Ukázka konfigurace Redisu pro lokální testování
https://github.com/tisnik/presentations/blob/master/redis/redis.conf - Resque
https://github.com/resque/resque - Nested transaction
https://en.wikipedia.org/wiki/Nested_transaction - Publish–subscribe pattern
https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern - Messaging pattern
https://en.wikipedia.org/wiki/Messaging_pattern - Using pipelining to speedup Redis queries
https://redis.io/topics/pipelining - Pub/Sub
https://redis.io/topics/pubsub - ZeroMQ distributed messaging
http://zeromq.org/ - ZeroMQ: Modern & Fast Networking Stack
https://www.igvita.com/2010/09/03/zeromq-modern-fast-networking-stack/ - Publish/Subscribe paradigm: Why must message classes not know about their subscribers?
https://stackoverflow.com/questions/2908872/publish-subscribe-paradigm-why-must-message-classes-not-know-about-their-subscr - Python & Redis PUB/SUB
https://medium.com/@johngrant/python-redis-pub-sub-6e26b483b3f7 - Message broker
https://en.wikipedia.org/wiki/Message_broker - RESP Arrays
https://redis.io/topics/protocol#array-reply - Redis Protocol specification
https://redis.io/topics/protocol - Redis Pub/Sub: Intro Guide
https://www.redisgreen.net/blog/pubsub-intro/ - Redis Pub/Sub: Howto Guide
https://www.redisgreen.net/blog/pubsub-howto/ - Comparing Publish-Subscribe Messaging and Message Queuing
https://dzone.com/articles/comparing-publish-subscribe-messaging-and-message - Apache Kafka
https://kafka.apache.org/ - Iron
http://www.iron.io/mq - kue (založeno na Redisu, určeno pro node.js)
https://github.com/Automattic/kue - Cloud Pub/Sub
https://cloud.google.com/pubsub/ - Introduction to Redis Streams
https://redis.io/topics/streams-intro - glob (programming)
https://en.wikipedia.org/wiki/Glob_(programming) - Why and how Pricing Assistant migrated from Celery to RQ – Paris.py
https://www.slideshare.net/sylvinus/why-and-how-pricing-assistant-migrated-from-celery-to-rq-parispy-2 - Enqueueing internals
http://python-rq.org/contrib/ - queue — A synchronized queue class
https://docs.python.org/3/library/queue.html - Queues
http://queues.io/ - Windows Subsystem for Linux Documentation
https://docs.microsoft.com/en-us/windows/wsl/about - RestMQ
http://restmq.com/ - ActiveMQ
http://activemq.apache.org/ - Amazon MQ
https://aws.amazon.com/amazon-mq/ - Amazon Simple Queue Service
https://aws.amazon.com/sqs/ - Celery: Distributed Task Queue
http://www.celeryproject.org/ - Disque, an in-memory, distributed job queue
https://github.com/antirez/disque - rq-dashboard
https://github.com/eoranged/rq-dashboard - Projekt RQ na PyPi
https://pypi.org/project/rq/ - rq-dashboard 0.3.12
https://pypi.org/project/rq-dashboard/ - Job queue
https://en.wikipedia.org/wiki/Job_queue - Why we moved from Celery to RQ
https://frappe.io/blog/technology/why-we-moved-from-celery-to-rq - Running multiple workers using Celery
https://serverfault.com/questions/655387/running-multiple-workers-using-celery - celery — Distributed processing
http://docs.celeryproject.org/en/latest/reference/celery.html - Chains
https://celery.readthedocs.io/en/latest/userguide/canvas.html#chains - Routing
http://docs.celeryproject.org/en/latest/userguide/routing.html#automatic-routing - Celery Distributed Task Queue in Go
https://github.com/gocelery/gocelery/ - Python Decorators
https://wiki.python.org/moin/PythonDecorators - Periodic Tasks
http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html - celery.schedules
http://docs.celeryproject.org/en/latest/reference/celery.schedules.html#celery.schedules.crontab - Pros and cons to use Celery vs. RQ
https://stackoverflow.com/questions/13440875/pros-and-cons-to-use-celery-vs-rq