Hlavní navigace

ØMQ: knihovna pro asynchronní předávání zpráv

Pavel Tišnovský

V šesté části seriálu o nástrojích pro asynchronní posílání a zpracování zpráv se seznámíme se základními vlastnostmi knihovny ØMQ, která je též známá pod jmény 0MQ, ZMQ či ZeroMQ.

Doba čtení: 37 minut

11. Role filtru při výběru zpráv jejich odebíratelem

12. Komunikace typu požadavek-odpověď

13. Uvolnění prostředků (context, socket)

14. Využití knihovny ØMQ v programovacím jazyku C

15. Implementace klienta a serveru se strategií PAIR

16. Oboustranné posílání zpráv mezi klientem a serverem

17. Přidání kontrolních podmínek do příkladů naprogramovaných v jazyku C

18. Repositář s demonstračními příklady

19. Odkazy na předchozí části seriálu

20. Odkazy na Internetu

1. ØMQ: knihovna pro asynchronní předávání zpráv

V dnešním článku se seznámíme se základními vlastnostmi knihovny ØMQ, která je též známá pod jmény 0MQ, ZMQ či ZeroMQ. Jedná se o relativně nízkoúrovňovou knihovnu vyvinutou v programovacím jazyce C++, která vývojářům nabízí implementaci různých tzv. komunikačních strategií. Tyto strategie je možné využít a popř. i vhodně zkombinovat při implementaci aplikací se složitější architekturou, v níž mezi sebou jednotlivé části komunikují s využitím synchronních či asynchronních zpráv, popř. přes takzvané brokery, s nimiž jsme se již seznámili v souvislosti s projekty Redis Queue [1], Celery [2][3] a RabbitMQ [4][5].

Poznámka: ve jménu této knihovny by se měl používat znak Ø a nikoli přeškrtnutá nula (0). To se budu snažit dodržovat zde v textu, ovšem nikoli například ve jménech souborů s demonstračními příklady.

Důležité je, že ØMQ je sice naprogramována v jazyce C++, ovšem rozhraní pro ni existují i pro velké množství dalších programovacích jazyků. Ze zhruba čtyřiceti existujících rozhraní se musíme zmínit především o jazyku C (ten dnes bude použit ve čtyřech jednoduchých demonstračních příkladech), dále pak o jazycích Go, Haskellu, Javě (tím pádem jsou nepřímo podporovány i další jazyky pro JVM), jazycích Julia, Lua, samozřejmě je podporován i Python (i tento jazyk dnes použijeme) či R. Zajímavá je zejména podpora jazyků Julia a R, která znamená, že lze tyto jazyky orientované spíše pro vývojáře, kteří jsou současně profesionály v jiném oboru, relativně snadno propojit s dalšími aplikacemi či službami. Ostatně ØMQ je interně použit v populárním nástroji Jupyter, kde plní přesně tuto roli [6].

2. Základní vlastnosti knihovny ØMQ; rozdíly oproti message brokerům popsaným minule

V předchozích pěti článcích seriálu o systémech pro posílání zpráv (viz též devatenáctou kapitolu) jsme se seznámili s nástroji Redis Queue, Celery a RabbitMQ. Všechny tři zmíněné nástroje můžeme považovat za plnohodnotnou implementací brokera resp. přesněji řečeno message brokera. V praxi to znamená, že tyto systémy postačí nainstalovat a provést jen minimální konfiguraci k tomu, aby bylo možné začít používat plnohodnotné fronty zpráv (message queue) podporující v případě potřeby persistenci zpráv, clusterování, rozesílání zpráv typu fanout, tvorbu pipeline s definovaným tokem zpráv atd. Knihovna ØMQ je v tomto kontextu dosti odlišná, protože se nejedná o přímočarou implementaci brokera, ale o sadu funkcí tvořících základní stavební kameny, s jejichž použitím je možné implementovat různé komunikační strategie – vše v závislosti na tom, jakou architekturu je nutné v daném konkrétním případě navrhnout a použít.

Poznámka: ØMQ je skutečná nativní knihovna, nikoli služba či démon. Proto se nijak nespouští; její funkce jsou volány konkrétními implementacemi serverů a klientů.

Na knihovnu ØMQ se ovšem můžeme dívat i z jiného pohledu, protože se jedná o abstrakci nad klasickými Berkeley sockety, ovšem s mnoha vylepšeními. V ØMQ je totiž možné zprávy odesílat asynchronně; samotné zpracování zpráv je provedeno na pozadí (ve vlastním vláknu), nemusíme se starat o délku zpráv a o jejich případnou fragmentaci a do určité míry jsme odstíněni od toho, jaký konkrétní protokol bude pro komunikaci použit (IPC, TCP, atd.). Toto zjednodušení se ještě více projeví v těch programovacích jazycích, které se mohou postarat o automatické uvolňování prostředků (což je mj. i případ Pythonu, v němž je vytvořeno prvních pět demonstračních příkladů, s nimiž se seznámíme v navazujících kapitolách).

Knihovna ØMQ podporuje čtyři základní komunikační strategie:

  1. PAIR – jednosměrné či obousměrné propojení dvou procesů, z nichž každý může běžet na odlišném počítači. Tato strategie se nejvíce přibližuje běžnému použití klasických Berkeley socketů.
  2. REQ-REP – jedná se o komunikaci typu požadavek-odpověď. Požadavky posílají klienti, odpovědi generuje server, který dokáže obsloužit prakticky libovolné množství klientů.
  3. PUB-SUB – server zde publikuje zprávy, k jejichž odběru se mohou přihlásit různí klienti. Zprávy je možné filtrovat na straně klientů (tato vlastnost se ovšem ve starších verzích ØMQ odlišuje).
  4. PUSH-PULL – rozšíření předchozí strategie PUB-SUB: server či servery vytváří zprávy zpracovávané buď přímo připojenými workery nebo celou kolonou (pipeline) workerů.

Ve skutečnosti je ovšem možné knihovnu ØMQ využít i pro implementaci složitějších strategií, například REQ-ROUTER/DEALER-REP apod. Těmito strategiemi se budeme zabývat v navazujícím článku.

3. Instalace knihovny ØMQ a PyZMQ

Demonstrační příklady, které si dnes ukážeme, jsou naprogramovány v Pythonu a taktéž v programovacím jazyku C. Z tohoto důvodu bude instalace nepatrně složitější, protože budeme muset nainstalovat jak nativní část knihovny ØMQ (libzmq), tak i rozhraní pro Python. Nejprve je nutné nainstalovat samotnou ØMQ, pro níž samozřejmě existují balíčky pro většinu majoritních distribucí. Pokud nenaleznete balíček zeromq a zeromq-devel, můžete si ØMQ přeložit sami, viz též popis dostupný na stránce http://zeromq.org/intro:get-the-software.

Ukažme si instalaci nativní části ØMQ na Fedoře. Nainstalujeme balíček libzmq:

$ sudo dnf install zeromq

Knihovna se nainstaluje do adresáře /usr/lib nebo /usr/lib64.

Poznámka: s poměrně velkou pravděpodobností již budete mít nativní knihovnu libzmq nainstalovanou, protože je používána dalšími aplikacemi.

Následuje instalace balíčku zeromq-devel, který bude nutné použít pro překlad a slinkování demonstračních příkladů naprogramovaných v céčku:

$ sudo dnf install zeromq-devel

Nainstalovat by se měl mj. i hlavičkový soubor zmq.h:

$ whereis zmq.h
 
zmq: /usr/include/zmq.h

Po instalaci nativní části knihovny ØMQ ještě budeme potřebovat nainstalovat rozhraní pro programovací jazyk Python. Toto rozhraní je implementováno v knihovně PyZMQ a nainstalujeme ho standardním způsobem s využitím nástroje pip popř. pip3. V praxi bude pro odzkoušení demonstračních příkladů postačovat instalace pro aktivního uživatele, tj. použijeme přepínač –user:

$ pip3 install --user pyzmq
 
Collecting pyzmq
  Downloading https://files.pythonhosted.org/packages/48/93/59592cb294761aaa40589b544eaa5175446d687ff95beeeb666de60f3274/pyzmq-17.1.2-cp36-cp36m-manylinux1_x86_64.whl (998kB)
    100% |████████████████████████████████| 1.0MB 941kB/s
Installing collected packages: pyzmq
Successfully installed pyzmq-17.1.2

4. Základní testy, zda instalace proběhla korektně

Po (doufejme že úspěšné) instalaci balíčků s ØMQ i PyZMQ si otestujeme korektnost instalace. Nejprve spustíme interpret Pythonu. Jak pro test instalace, tak i pro demonstrační příklady budeme používat Python 3.x, nikoli Python 2.x:

$ python3

Následně provedeme import modulu zmq:

import zmq

V případě, že import proběhl bez chyby (měl by), můžeme se pokusit vypsat jak verzi samotného rozhraní PyZMQ, tak i verzi nainstalované nativní knihovny ØMQ:

print(zmq.pyzmq_version())
17.1.2
 
print(zmq.__version__)
17.1.2
 
print(zmq.zmq_version())
4.2.5

Druhým testem zjistíme, jestli jsou nainstalovány hlavičkové soubory ØMQ a sdílená knihovna, která se má slinkovat se zdrojovým kódem. Vytvoříme tento zdrojový kód a uložíme ho do souboru s názvem test_0mq.c:

#include <stdio.h>
#include <zmq.h>
 
int main()
{
    int major, minor, patch;
    void *context;
    void *socket;
 
    zmq_version (&major, &minor, &patch);
    printf("ØMQ version %d.%d.%d\n", major, minor, patch);
 
    context = zmq_ctx_new();
    socket = zmq_socket(context, ZMQ_PAIR);
 
    printf("%p\n", context);
    printf("%p\n", socket);
 
    zmq_close(socket);
    zmq_ctx_destroy(context);
 
    return 0;
}

Posléze se pokusíme o překlad, slinkování a spuštění:

$ gcc -c -o test_0mq.o test_0mq.c
 
$ gcc -o test_0mq test_0mq.o -lzmq
 
$ ./test_0mq
ØMQ version 4.1.6
0x1620750
0x1623e20

Při překladu by se neměla vypsat žádná chyba o neexistujícím hlavičkovém souboru či o neznámých funkcích. Ani ve fázi linkování by neměla nastat žádná chyba (neznámé symboly atd.).

5. Nejjednodušší strategie komunikace: propojení dvou uzlů s jednosměrným posíláním zpráv

Základní vlastnosti knihovny ØMQ si nejlépe vysvětlíme na několika konkrétních demonstračních příkladech. V prvním příkladu, který bude naprogramován v Pythonu a bude postaven na již výše zmíněném rozhraní PyZMQ, bude využita ta nejjednodušší možná komunikační strategie – bude se jednat o propojení dvou uzlů a přitom se bude využívat pouze jednosměrný přenos zpráv, zde konkrétně od serveru ke klientovi (ovšem stejně by bylo možné role klienta a serveru otočit a posílat zprávy klientem na server).

Jak klient, tak i server nejdříve musí inicializovat takzvaný kontext. Kontext si zjednodušeně řečeno můžeme představit jako kontejner (řekněme seznam – i když je to velmi nepřesné) s jednotlivými sockety, které klient/server otevřel a používá. Pro vytvoření kontextu se použije konstruktor Context z modulu zmq (ten samozřejmě nejdříve musíme naimportovat):

context = zmq.Context()

Jakmile je kontext vytvořen, můžeme se pokusit vytvořit takzvaný socket (nejedná se ovšem o Berkeley socket). Při vytváření popř. konstrukci socketů je nutné určit typ připojení resp. strategii komunikace. V prvním demonstračním příkladu budeme používat strategii nazvanou jednoduše PAIR, tj. propojení dvou uzlů (klienta a serveru). Jak klient, tak i server použijí strategii zmq.PAIR a socket tedy v obou případech zkonstruují následujícím způsobem:

socket = context.socket(zmq.PAIR)

V tomto bodě se ovšem cesty klienta a serveru rozdělí. Mezi oběma typy uzlů totiž existuje jeden podstatný rozdíl – server musí otevřít připojení na určeném portu, zatímco klient se k tomuto portu musí připojit (na jeho straně se v případě TCP/IP samozřejmě taktéž otevře port, ovšem ten nemusíme specifikovat, zvolí se automaticky).

Socket na straně serveru se otevře (spojí s portem) metodou bind, které se musí předat adresa. Na rozdíl od Berkeley socketů je ovšem adresa představována řetězcem s čitelným zápisem adresy, který se skládá ze specifikace protokolu (inproc, ipc, tcp, pgm, epgm) [takzvaný transport], oddělovače „:“ a vlastní adresy, jejíž formát závisí na vybraném protokolu. Pro TCP a při použití portu bude volání bind na straně serveru vypadat takto:

address = "tcp://*:5556"
socket.bind(address)

Klient se bude připojovat k serveru na jím otevřený port. Pro připojení se použije metoda connect a adresa může vypadat (pokud server i klient běží na stejném počítači a komunikují lokálně) následovně:

address = "tcp://localhost:5556"
socket.connect(address)

Na straně serveru můžeme posílat zprávy například metodou send_string. Tato metoda na svém vstupu akceptuje pythonní řetězce, které převede na sekvenci bajtů, přičemž se předpokládá, že kódování řetězce je UTF-8 (lze ho ovšem i explicitně specifikovat):

for i in range(10):
    message = "Message #{i}".format(i=i)
    socket.send_string(message)
    time.sleep(1)

Na straně klienta se řetězce mohou přijímat metodou recv_string():

while True:
    message = socket.recv_string()
    print("Received message '{m}'".format(m=message))
Poznámka: metody send_string() a recv_string() interně volají obecnější metody send() a recv() určené pro poslání popř. pro příjem sekvence bajtů. Kromě toho lze použít i další podobné metody, například send_json(), send_pyobj() apod., ovšem dnes si vystačíme s posíláním čistě textových zpráv. Nicméně je vhodné vědět, že ØMQ většinou zprávy žádným způsobem neinterpretuje (výjimkou je filtrace při použití strategie PUB-SUB).

6. Implementace klienta i serveru využívajících strategii PAIR s jednosměrným posíláním zpráv

Úplná verze klienta komunikujícího se strategií PAIR, bude vypadat následovně:

import zmq
 
 
def connect(port, connection_type):
    """Otevření socketu se specifikovaným typem spojení."""
    context = zmq.Context()
    socket = context.socket(connection_type)
    address = "tcp://localhost:{port}".format(port=port)
    socket.connect(address)
    print("Connected to {a}".format(a=address))
    return socket
 
 
def start_client():
    """Spuštění klienta."""
    socket = connect(5556, zmq.PAIR)
    print("Waiting for messages...")
    while True:
        message = socket.recv_string()
        print("Received message '{m}'".format(m=message))
 
 
start_client()

Implementace serveru je nepatrně odlišná:

import zmq
import time
 
 
def bind(port, connection_type):
    """Otevření socketu se specifikovaným typem spojení."""
    context = zmq.Context()
    socket = context.socket(connection_type)
    address = "tcp://*:{port}".format(port=port)
    socket.bind(address)
    print("Bound to address {a}".format(a=address))
    return socket
 
 
def send_message(socket, message):
    """Poslání zprávy."""
    print("Sending message '{m}'".format(m=message))
    socket.send_string(message)
 
 
def start_server():
    """Spuštění serveru."""
    socket = bind(5556, zmq.PAIR)
    for i in range(10):
        send_message(socket, "Message #{i}".format(i=i))
        time.sleep(1)
 
 
start_server()

Pokud nyní server spustíme, otevře port 5556 a bude očekávat připojení klienta. Po připojení mu začne posílat zprávy (celkem deset zpráv):

$ python3 server.py
 
Bound to address tcp://*:5556
Sending message 'Message #0'
Sending message 'Message #1'
Sending message 'Message #2'
Sending message 'Message #3'
Sending message 'Message #4'
Sending message 'Message #5'
Sending message 'Message #6'
Sending message 'Message #7'
Sending message 'Message #8'
Sending message 'Message #9'

Klient se po svém spuštění připojí na server k jeho portu 5556, přijme prvních deset zpráv a bude čekat v nekonečné smyčce na další zprávy. Ukončit ho můžeme stiskem Ctrl+C:

$ python3 client.py
 
Connected to tcp://localhost:5556
Waiting for messages...
Received message 'Message #0'
Received message 'Message #1'
Received message 'Message #2'
Received message 'Message #3'
Received message 'Message #4'
Received message 'Message #5'
Received message 'Message #6'
Received message 'Message #7'
Received message 'Message #8'
Received message 'Message #9'

7. Obousměrná komunikace mezi klientem a serverem

Jakmile je navázána komunikace mezi klientem a serverem s využitím strategie PAIR, je možné zprávy posílat oboustranně. Jinými slovy: všechny metody Socket.send(), Socket.send_string(), Socket.send_json(), Socket.send_pyobj(), Socket.recv(), Socket.recv_string(), Socket.recv_json() atd. můžeme volat jak na straně klienta, tak i na straně serveru. Jak se tedy v tomto případě klient a server odlišují? Server je ten komunikující uzel, který otevírá port metodou Socket.bind(), zatímco klient se na tento port připojuje metodou Socket.connect(). Další rozdíly jsou již plně konfigurovatelné programátorem.

8. Implementace klienta i serveru s oboustrannou komunikací (potvrzování zpráv)

Vyzkoušejme si nyní, jak je možné naprogramovat nepatrně složitější protokol, v němž klient nejdříve přijme zprávu ze serveru a následně tuto zprávu potvrdí zasláním jiné zprávy začínající na „Ack“. Úplná verze klienta vypadá takto:

import zmq
 
 
def connect(port, connection_type):
    """Otevření socketu se specifikovaným typem spojení."""
    context = zmq.Context()
    socket = context.socket(connection_type)
    address = "tcp://localhost:{port}".format(port=port)
    socket.connect(address)
    print("Connected to {a}".format(a=address))
    return socket
 
 
def send_message(socket, message):
    """Poslání zprávy."""
    print("Sending message '{m}'".format(m=message))
    socket.send_string(message)
 
 
def start_client():
    """Spuštění klienta."""
    socket = connect(5556, zmq.PAIR)
    print("Waiting for messages...")
    while True:
        message = socket.recv_string()
        print(message)
        send_message(socket, "Acknowledge... " + message)
 
 
start_client()

Server pochopitelně musí provádět opačnou činnost – nejdříve pošle svoji zprávu a posléze čeká, zda a kdy ji klient potvrdí zasláním své zprávy:

import zmq
import time
 
 
def bind(port, connection_type):
    """Otevření socketu se specifikovaným typem spojení."""
    context = zmq.Context()
    socket = context.socket(connection_type)
    address = "tcp://*:{port}".format(port=port)
    socket.bind(address)
    print("Bound to address {a}".format(a=address))
    return socket
 
 
def send_message(socket, message):
    """Poslání zprávy."""
    print("Sending message '{m}'".format(m=message))
    socket.send_string(message)
 
 
def receive_response(socket):
    """Zpracování odpovědi klienta."""
    response = socket.recv_string()
    print("Received response from client: '{r}'".format(r=response))
 
 
def start_server():
    """Spuštění serveru."""
    socket = bind(5556, zmq.PAIR)
    for i in range(10):
        send_message(socket, "Message #{i}".format(i=i))
        print("Sent, waiting for response...")
        receive_response(socket)
        time.sleep(1)
        print()
 
 
start_server()

Příklad komunikace z pohledu serveru:

$ python3 server.py
 
Bound to address tcp://*:5556
Sending message 'Message #0'
Sent, waiting for response...
Received response from client: 'Acknowledge... Message #0'
 
Sending message 'Message #1'
Sent, waiting for response...
Received response from client: 'Acknowledge... Message #1'
 
Sending message 'Message #2'
Sent, waiting for response...
Received response from client: 'Acknowledge... Message #2'
 
Sending message 'Message #3'
Sent, waiting for response...
Received response from client: 'Acknowledge... Message #3'
 
Sending message 'Message #4'
Sent, waiting for response...
Received response from client: 'Acknowledge... Message #4'
 
Sending message 'Message #5'
Sent, waiting for response...
Received response from client: 'Acknowledge... Message #5'
 
Sending message 'Message #6'
Sent, waiting for response...
Received response from client: 'Acknowledge... Message #6'
 
Sending message 'Message #7'
Sent, waiting for response...
Received response from client: 'Acknowledge... Message #7'
 
Sending message 'Message #8'
Sent, waiting for response...
Received response from client: 'Acknowledge... Message #8'
 
Sending message 'Message #9'
Sent, waiting for response...
Received response from client: 'Acknowledge... Message #9'

A naopak komunikace z pohledu klienta:

$ python3 client.py
 
Connected to tcp://localhost:5556
Waiting for messages...
Message #0
Message #1
Message #2
Message #3
Message #4
Message #5
Message #6
Message #7
Message #8
Message #9

9. Komunikační strategie publish-subscribe

S komunikační strategií nazvanou publish-subscribe popř. jen zkráceně PUB-SUB či PUB/SUB jsme se již v tomto seriálu několikrát setkali, například při popisu nástroje Redis Queue (RQ), popř. při popisu nástroje RabbitMQ. Připomeňme si, že tato strategie je založena na existenci uzlu (typicky služby), která publikuje nějaké zprávy. Další uzly, jichž může být obecně libovolný počet, se mohou přihlásit k odběru těchto zpráv. Při publikování zpráv se (ve výchozím nastavení) nemusí hlídat, kolik odběratelů zprávy přijalo. Navíc je možné u každého odběratele nastavit filtr (konkrétně prefix zprávy), díky němuž lze přijímat pouze ty zprávy, které odběratele zajímají.

Poznámka: samozřejmě opět platí, že volba formátu zpráv je plně v rukou vývojářů, kteří knihovnu ØMQ použijí. Typicky se však přenáší textové zprávy s nějakým prefixem (například kódem oblasti) a právě na základě tohoto prefixu je možné zprávy filtrovat.

Připojení a vytvoření socketu se nyní bude na serveru (publikujícího zprávy) a klientu (příjemci zpráv) odlišovat. U serveru se použije:

socket = context.socket(zmq.PUB)

Příjemce zpráv naopak použije:

socket = context.socket(zmq.SUB)

Samotné posílání resp. příjem zpráv se lišit nebude, ovšem sémantika bude odlišná.

10. Implementace nástroje pro publikaci zpráv i pro jejich odebírání

Server – publisher – zpráv může v nejjednodušším případě vypadat následovně:

import zmq
import time
 
 
def bind(port, connection_type):
    """Otevření socketu se specifikovaným typem spojení."""
    context = zmq.Context()
    socket = context.socket(connection_type)
    address = "tcp://*:{port}".format(port=port)
    socket.bind(address)
    print("Bound to address {a}".format(a=address))
    return socket
 
 
def send_message(socket, message):
    """Poslání zprávy."""
    print("Publishing message '{m}'".format(m=message))
    socket.send_string(message)
 
 
def start_publisher():
    """Spuštění publisheru."""
    socket = bind(5556, zmq.PUB)
    for i in range(10):
        send_message(socket, "Message #{i}".format(i=i))
        time.sleep(1)
 
 
start_publisher()

Následuje výpis úplného zdrojového kódu příjemce zpráv:

import zmq
 
 
def connect(port, connection_type):
    """Otevření socketu se specifikovaným typem spojení."""
    context = zmq.Context()
    socket = context.socket(connection_type)
    address = "tcp://localhost:{port}".format(port=port)
    socket.connect(address)
    print("Connected to {a}".format(a=address))
    return socket
 
 
def start_subscriber():
    """Spuštění příjemce."""
    socket = connect(5556, zmq.SUB)
    socket.setsockopt_string(zmq.SUBSCRIBE, "")
    print("Waiting for messages...")
    while True:
        message = socket.recv_string()
        print("Received message '{m}'".format(m=message))
 
 
start_subscriber()
Poznámka: význam programového řádku socket.setsockopt_string(zmq­.SUBSCRIBE, ""), který je zde velmi důležitý, si vysvětlíme až v navazujících kapitolách.

Ve chvíli, kdy spustíme server (publishera), začne vytvářet zprávy, bez ohledu na případné příjemce:

$ python3 publisher.py
 
Bound to address tcp://*:5556
Publishing message 'Message #0'
Publishing message 'Message #1'
Publishing message 'Message #2'
Publishing message 'Message #3'
Publishing message 'Message #4'
Publishing message 'Message #5'
Publishing message 'Message #6'
Publishing message 'Message #7'
Publishing message 'Message #8'
Publishing message 'Message #9'

Příjemce po svém spuštění začne zprávy postupně odebírat:

$ python3 subscriber.py
 
Connected to tcp://localhost:5556
Waiting for messages...
Received message 'Message #1'
Received message 'Message #2'
Received message 'Message #3'
Received message 'Message #4'
Received message 'Message #5'
Received message 'Message #6'
Received message 'Message #7'
Received message 'Message #8'
Received message 'Message #9'

11. Role filtru při výběru zpráv jejich odebíratelem

V implementaci předchozího klienta jste si mohli povšimnout tohoto programového řádku:

socket.setsockopt_string(zmq.SUBSCRIBE, "")

Tímto řádkem je specifikován filtr, který umožňuje, aby klient přijímal pouze ty zprávy, které se ho týkají. Typicky se ve filtru zadává prefix zprávy, takže například v případě, kdy budeme mít tři příjemce zpráv z ČR, SR a Polska můžeme v každé zprávě použít prefix „cz.“, „sk.“ a „pl.“. Každý z příjemců následně použije nějaký filtr:

socket.setsockopt_string(zmq.SUBSCRIBE, "cz.")

Pokud ovšem žádný filtr nebudeme specifikovat, nebudou ani žádné zprávy přijímány! To znamená, že filtr, i když prázdný, je většinou nutné nastavit. Ostatně se můžete sami pokusit o spuštění následující dvojice server+klient:

import zmq
import time
 
 
def bind(port, connection_type):
    """Otevření socketu se specifikovaným typem spojení."""
    context = zmq.Context()
    socket = context.socket(connection_type)
    address = "tcp://*:{port}".format(port=port)
    socket.bind(address)
    print("Bound to address {a}".format(a=address))
    return socket
 
 
def send_message(socket, message):
    """Poslání zprávy."""
    print("Publishing message '{m}'".format(m=message))
    socket.send_string(message)
 
 
def start_server():
    """Spuštění serveru."""
    socket = bind(5556, zmq.PUB)
    for i in range(10):
        send_message(socket, "Message #{i}".format(i=i))
        time.sleep(1)
 
 
start_server()
import zmq
 
 
def connect(port, connection_type):
    """Otevření socketu se specifikovaným typem spojení."""
    context = zmq.Context()
    socket = context.socket(connection_type)
    address = "tcp://localhost:{port}".format(port=port)
    socket.connect(address)
    print("Connected to {a}".format(a=address))
    return socket
 
 
def start_client():
    """Spuštění klienta."""
    socket = connect(5556, zmq.SUB)
    print("Waiting for messages...")
    while True:
        message = socket.recv_string()
        print("Received message '{m}'".format(m=message))
 
 
start_client()

Pokud si spustíte serverovou část, bude sice zveřejňovat zprávy, ovšem příjemce je neuvidí a tudíž ani nepřečte!

12. Komunikace typu požadavek-odpověď

Nyní si ukažme další strategii komunikace. Tato strategie se někdy nazývá REQ-REP. Při použití této strategie server přijímá požadavky (request) a odpovídá na ně (response), přičemž je možné, aby požadavky posílalo několik klientů (a jeden klient naopak může posílat požadavky více serverům). Tato velmi asymetrická komunikace se strategií REQ-REP je velmi často používaná, ostatně je na ní založen i protokol HTTP a jeho pozdější varianty.

Nejprve si ukažme, jak vypadá implementace klienta. Ta je velmi jednoduchá, protože postačuje otevřít socket se specifikací strategie REQ, poslat požadavek s využitím Socket.send_string() a přijmout výsledek metodou Socket.recv_string(). Úplný zdrojový kód klienta vypadá takto:

import zmq
 
 
def connect(port, connection_type):
    """Otevření socketu se specifikovaným typem spojení."""
    context = zmq.Context()
    socket = context.socket(connection_type)
    address = "tcp://localhost:{port}".format(port=port)
    socket.connect(address)
    print("Connected to {a}".format(a=address))
    return socket
 
 
def send_request(socket, request):
    """Poslání požadavku."""
    print("Sending request '{r}'".format(r=request))
    socket.send_string(request)
 
 
def start_client():
    """Spuštění klienta."""
    socket = connect(5556, zmq.REQ)
 
    send_request(socket, "1")
    print(socket.recv_string())
    print()
 
    send_request(socket, "10")
    print(socket.recv_string())
    print()
 
    send_request(socket, "xyzzy")
    print(socket.recv_string())
    print()
 
 
start_client()

Server nyní uděláme nepatrně složitější, protože bude mít za úkol poskytovat jednoduchou službu – vypočte faktoriál čísla, které mu pošleme formou řetězce:

import zmq
from math import factorial
 
 
def bind(port, connection_type):
    """Otevření socketu se specifikovaným typem spojení."""
    context = zmq.Context()
    socket = context.socket(connection_type)
    address = "tcp://*:{port}".format(port=port)
    socket.bind(address)
    print("Bound to address {a}".format(a=address))
    return socket
 
 
def send_response(socket, response):
    """Odeslání odpovědi."""
    print("Sending response '{r}'".format(r=response))
    socket.send_string(response)
 
 
def receive_request(socket):
    """Zpracování požadavku klienta."""
    request = socket.recv_string()
    print("Received request from client: '{r}'".format(r=request))
    return request
 
 
def start_server():
    """Spuštění serveru."""
    socket = bind(5556, zmq.REP)
    while True:
        request = receive_request(socket)
        try:
            n = int(request)
            fact = factorial(n)
            send_response(socket, "{n}! = {f}".format(n=n, f=fact))
        except Exception as e:
            send_response(socket, "Wrong input")
 
 
start_server()

Příklad komunikace z pohledu serveru:

$ python3 server.py
 
Bound to address tcp://*:5556
Received request from client: '1'
Sending response '1! = 1'
Received request from client: '1'
Sending response '1! = 1'
Received request from client: '10'
Sending response '10! = 3628800'
Received request from client: 'xyzzy'
Sending response 'Wrong input'
Received request from client: '1'
Sending response '1! = 1'
Received request from client: '10'
Sending response '10! = 3628800'
Received request from client: 'xyzzy'
Sending response 'Wrong input'
Received request from client: '1'
Sending response '1! = 1'
Received request from client: '10'
Sending response '10! = 3628800'
Received request from client: 'xyzzy'
Sending response 'Wrong input'

Příklad komunikace z pohledu klienta:

$ python3 client.py
 
Connected to tcp://localhost:5556
Sending request '1'
1! = 1
 
Sending request '10'
10! = 3628800
 
Sending request 'xyzzy'
Wrong input

13. Uvolnění prostředků (context, socket)

Demonstrační příklady ukázané v předchozích kapitolách, byly ve všech případech velmi jednoduché, čitelné a snadno pochopitelné. Je tomu tak mj. i z toho důvodu, že jsme se v nich vůbec nezabývali uvolňováním prostředků (resources), zejména uzavíráním socketu a zrušením kontextu. Tyto operace je totiž možné provést (z pohledu vývojáře) na pozadí, protože objekty typu context a socket jsou správci kontextu (context manager) (zjednodušeně – můžeme je použít v konstrukci with) a současně uzavírají své prostředky ve svých destruktorech, což sice není příliš korektní, ovšem lepší je uzavřít příslušné prostředky pozdě než nikdy :-). Navíc je možné použít dekorátory @context a @socket pro zcela automatické vytvoření/znovupoužití kontextu a vytvoření socketu, což jsou techniky, které si ukážeme příště.

Nic nám ovšem nebrání přepsat si první demonstrační příklad (komunikace dvou uzlů se strategií PAIR) tak, že oba prostředky budeme uvolňovat explicitně.

Upravená implementace klienta, resp. přesněji řečeno jedna z možných a nutno říci, že nepříliš čitelných implementací:

import zmq
 
 
def connect(port, connection_type):
    """Otevření socketu se specifikovaným typem spojení."""
    context = zmq.Context()
    socket = context.socket(connection_type)
    address = "tcp://localhost:{port}".format(port=port)
    socket.connect(address)
    print("Connected to {a}".format(a=address))
    return context, socket
 
 
def start_client():
    """Spuštění klienta."""
    try:
        context, socket = connect(5556, zmq.PAIR)
        print("Waiting for messages...")
        while True:
            message = socket.recv_string()
            print("Received message '{m}'".format(m=message))
    finally:
        print("Trying to close socket...")
        socket.close()
        print("Trying to destroy context...")
        context.destroy()
 
 
start_client()

Upravená implementace serveru:

import zmq
import time
 
 
def bind(port, connection_type):
    """Otevření socketu se specifikovaným typem spojení."""
    context = zmq.Context()
    socket = context.socket(connection_type)
    address = "tcp://*:{port}".format(port=port)
    socket.bind(address)
    print("Bound to address {a}".format(a=address))
    return context, socket
 
 
def send_message(socket, message):
    """Poslání zprávy."""
    print("Sending message '{m}'".format(m=message))
    socket.send_string(message)
 
 
def start_server():
    """Spuštění serveru."""
    context, socket = bind(5556, zmq.PAIR)
    try:
        for i in range(10):
            send_message(socket, "Message #{i}".format(i=i))
            time.sleep(1)
    finally:
        print("Trying to close socket...")
        socket.close()
        print("Trying to destroy context...")
        context.destroy()
 
 
start_server()

Podobným způsobem je samozřejmě možné upravit i všechny další demonstrační příklady, ovšem tento postup je v praxi možné ještě více zjednodušit s využitím dekorátorů, což si ukážeme v navazujícím článku.

14. Využití knihovny ØMQ v programovacím jazyku C

Z závěrečné části dnešního článku si ukážeme základní způsoby použití knihovny ØMQ z programovacího jazyka C. Kombinace ØMQ+C je v mnoha ohledech vlastně pochopitelná, protože ØMQ je navržena takovým způsobem, aby byla efektivní, a to jak z hlediska využití systémových prostředků (paměť, čas procesoru), tak i z hlediska využití síťového rozhraní. Podobné vlastnosti (až na ono zmíněné síťové rozhraní), samozřejmě nalezneme i u programovacího jazyka C. Zkusme si tedy nejdříve přepsat první demonstrační příklad z Pythonu do jazyka C. Připomeňme si, že se jednalo o příklad, ve kterém se vytvořilo propojení typu PAIR umožňující jednosměrné nebo obousměrné posílání zpráv mezi dvojicí uzlů. Tyto uzly jsme pro jednoduchost nazvali klient a server.

15. Implementace klienta a serveru se strategií PAIR

Nejprve si ukažme céčkovou variantu klienta:

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <zmq.h>
 
#define BUFFER_LENGTH 32
 
int main()
{
    char buffer[BUFFER_LENGTH];
    char *address = "tcp://localhost:5556";
 
    void *context = zmq_ctx_new();
    void *socket = zmq_socket(context, ZMQ_PAIR);
 
    zmq_connect(socket, address);
    printf("Connected to address %s\n", address);
 
    while (1)
    {
        int num = zmq_recv(socket, buffer, BUFFER_LENGTH-1, 0);
        buffer[num] = '\0';
        printf("Received '%s'\n", buffer);
    }
 
    zmq_close(socket);
    zmq_ctx_destroy(context);
 
    return 0;
}

Následuje céčková varianta serveru:

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <zmq.h>
 
#define BUFFER_LENGTH 32
 
int main()
{
    char buffer[BUFFER_LENGTH];
    char *address = "tcp://*:5556";
 
    void *context = zmq_ctx_new();
    void *socket = zmq_socket(context, ZMQ_PAIR);
 
    zmq_bind(socket, address);
    printf("Bound to address %s\n", address);
 
    int i;
    for (i=0; i<10; i++)
    {
        snprintf(buffer, BUFFER_LENGTH, "Message #%d", i+1);
        printf("Sending message '%s'\n", buffer);
        zmq_send(socket, buffer, strlen(buffer), 0);
        sleep(1);
    }
 
    zmq_close(socket);
    zmq_ctx_destroy(context);
 
    return 0;
}

Pro překlad a slinkování jak serveru, tak i klienta použijte soubor Makefile:

CC=gcc
LINKER=gcc
 
LIBS=zmq
 
CFLAGS=-O0 -Wall -std=c99 -pedantic
LFLAGS=-l$(LIBS)
 
.PHONY: clean
 
all:    client server
 
%.o:    %.c
        $(CC) -c -o $@ $(CFLAGS) $<
 
client: client.o
        $(CC) -o $@ $(LFLAGS) $<
 
server: server.o
        $(CC) -o $@ $(LFLAGS) $<
 
clean:
        rm -f client.o \
        rm -f server.o \
        rm -f client \
        rm -f server
Poznámka: v souboru Makefile se odsazení musí provádět znakem Tab, proto si skutečně stáhněte soubor z poskytnutého linku a nekopírujte si ho z článku.

Povšimněte si největšího rozdílu mezi implementací v Pythonu a v C. Jedná se o příjem zprávy, kdy je nutné na straně klienta dopředu alokovat buffer s takovou kapacitou, aby do něho bylo možné uložit maximální povolenou a domluvenou délku zprávy. Dále se kontroluje počet přenesených bajtů (zde se délka zprávy skutečně počítá v bajtech) a před tiskem zprávy pro jistotu za její konec doplníme nulu (tím se nám otevírá možnost použít takového klienta, který nepoužívá ukončující nulu – je to náš klient?). Z důvodu co nejjednodušší implementace navíc nepočítáme s možností, že zpráva nebyla z nějakého důvodu přijata. V takovém případě totiž funkce zmq_recv() vrátí hodnotu –1. V reálných případech tedy budeme muset s touto eventualitou počítat:

int num = zmq_recv(socket, buffer, BUFFER_LENGTH-1, 0);
if (num < 0) {
    perror("zmq_recv() failed");
}
else {
    buffer[num] = '\0';
    printf("Received '%s'\n", buffer);
}

I u posílání zpráv může nastat chyba, na kterou by bylo vhodné reagovat:

int rc = zmq_send(socket, buffer, strlen(buffer), 0);
if (rc < 0) {
    perror("zmq_send() failed");
}

16. Oboustranné posílání zpráv mezi klientem a serverem

Pro zajímavost si ještě ukažme přepis druhého příkladu, v němž byla opět použita strategie PAIR, ovšem komunikace mezi serverem a klientem probíhala oboustranně.

Klientská část s dvojicí bufferů:

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <zmq.h>
 
#define BUFFER_LENGTH 32
#define BUFFER2_LENGTH (32 + sizeof("Acknowledge: "))
 
int main()
{
    char buffer[BUFFER_LENGTH];
    char buffer2[BUFFER_LENGTH];
    char *address = "tcp://localhost:5556";
 
    void *context = zmq_ctx_new();
    void *socket = zmq_socket(context, ZMQ_PAIR);
 
    zmq_connect(socket, address);
    printf("Connected to address %s\n", address);
 
    while (1)
    {
        int num = zmq_recv(socket, buffer, BUFFER_LENGTH-1, 0);
        buffer[num] = '\0';
        printf("Received '%s'\n", buffer);
 
        snprintf(buffer2, BUFFER2_LENGTH, "Acknowledge: %s", buffer);
        zmq_send(socket, buffer2, strlen(buffer2), 0);
    }
 
    zmq_close(socket);
    zmq_ctx_destroy(context);
 
    return 0;
}

Část s implementací serveru, opět využívající dvojici bufferů:

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <zmq.h>
 
#define BUFFER_LENGTH 32
#define BUFFER2_LENGTH (32 + sizeof("Acknowledge: "))
 
int main()
{
    char buffer[BUFFER_LENGTH];
    char buffer2[BUFFER2_LENGTH];
 
    char *address = "tcp://*:5556";
 
    void *context = zmq_ctx_new();
    void *socket = zmq_socket(context, ZMQ_PAIR);
 
    zmq_bind(socket, address);
    printf("Bound to address %s\n", address);
 
    int i;
    for (i=0; i<10; i++)
    {
        snprintf(buffer, BUFFER_LENGTH, "Message #%d", i+1);
        printf("Sending message '%s'\n", buffer);
        zmq_send(socket, buffer, strlen(buffer), 0);
 
        printf("Sent, waiting for response...\n");
        int num = zmq_recv(socket, buffer2, BUFFER2_LENGTH-1, 0);
        buffer2[num] = '\0';
        printf("Received response '%s'\n", buffer2);
 
        sleep(1);
    }
 
    zmq_close(socket);
    zmq_ctx_destroy(context);
 
    return 0;
}

Soubor Makefile nedoznal dalších změn, takže jen pro úplnost:

CC=gcc
LINKER=gcc
 
LIBS=zmq
 
CFLAGS=-O0 -Wall -std=c99 -pedantic
LFLAGS=-l$(LIBS)
 
.PHONY: clean
 
all:    client server
 
%.o:    %.c
        $(CC) -c -o $@ $(CFLAGS) $<
 
client: client.o
        $(CC) -o $@ $(LFLAGS) $<
 
server: server.o
        $(CC) -o $@ $(LFLAGS) $<
 
clean:
        rm -f client.o \
        rm -f server.o \
        rm -f client \
        rm -f server

17. Přidání kontrolních podmínek do příkladů naprogramovaných v jazyku C

Všechny čtyři zdrojové kódy naprogramované v céčku neprováděly žádnou kontrolu, zda se podařilo získat kontext, otevřít připojení s využitím socketu atd. V praxi je samozřejmě nutné tyto kontroly provádět a současně i uvolňovat všechny prostředky, v našem případě socket(y) a context manager. Zdrojový kód se nám ovšem poněkud znepřehlední, což ostatně můžete posoudit sami při porovnání těchto dvou dvojic příkladů:

Uzel Bez kontrol S kontrolami
klient client.c client.c
server server.c server.c

Podrobnosti o tom, jak zpracovávat návratové kódy všech funkcí, si opět uvedeme příště.

18. 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 taktéž v programovacím jazyku C 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říklad Skript/kód Popis Cesta
1 test_0mq.c základní test instalace 0MQ https://github.com/tisnik/message-queues-examples/blob/master/0mq/c/tes­t_0mq/test_0mq.c
1 check.sh základní test instalace 0MQ https://github.com/tisnik/message-queues-examples/blob/master/0mq/c/tes­t_0mq/check.sh
       
2 client.py první příklad v Pythonu, klientská část https://github.com/tisnik/message-queues-examples/blob/master/0mq/pyt­hon/example01/client.py
2 server.py první příklad v Pythonu, část serveru https://github.com/tisnik/message-queues-examples/blob/master/0mq/pyt­hon/example01/server.py
       
3 client.py druhý příklad v Pythonu, klientská část https://github.com/tisnik/message-queues-examples/blob/master/1mq/pyt­hon/example01/client.py
3 server.py druhý příklad v Pythonu, část serveru https://github.com/tisnik/message-queues-examples/blob/master/0mq/pyt­hon/example02/server.py
       
4 publisher.py jednoduchý vydavatel zpráv https://github.com/tisnik/message-queues-examples/blob/master/0mq/pyt­hon/example03/publisher.py
4 subscriber.py příjemce zpráv https://github.com/tisnik/message-queues-examples/blob/master/0mq/pyt­hon/example03/subscriber.py
       
5 publisher.py jednoduchý vydavatel zpráv https://github.com/tisnik/message-queues-examples/blob/master/0mq/pyt­hon/example04/publisher.py
5 subscriber.py příjemce zpráv bez filtrace https://github.com/tisnik/message-queues-examples/blob/master/0mq/pyt­hon/example04/subscriber.py
       
6 client.py pátý příklad v Pythonu, klientská část https://github.com/tisnik/message-queues-examples/blob/master/1mq/pyt­hon/example05/client.py
6 server.py pátý příklad v Pythonu, část serveru https://github.com/tisnik/message-queues-examples/blob/master/0mq/pyt­hon/example05/server.py
       
7 client.py první příklad v Pythonu s uzavíráním prostředků, klientská část https://github.com/tisnik/message-queues-examples/blob/master/0mq/pyt­hon/example01__proper_clo­se/client.py
7 server.py první příklad v Pythonu s uzavíráním prostředků, část serveru https://github.com/tisnik/message-queues-examples/blob/master/0mq/pyt­hon/example01_proper_close/ser­ver.py
       
8 client.c klient naprogramovaný v C https://github.com/tisnik/message-queues-examples/blob/master/0mq/c/e­xample01/client.c
8 server.c server naprogramovaný v C https://github.com/tisnik/message-queues-examples/blob/master/0mq/c/e­xample01/server.c
8 Makefile Makefile pro překlad a slinkování https://github.com/tisnik/message-queues-examples/blob/master/0mq/c/e­xample01/Makefile
       
9 client.c klient naprogramovaný v C https://github.com/tisnik/message-queues-examples/blob/master/0mq/c/e­xample01/client.c
9 server.c server naprogramovaný v C https://github.com/tisnik/message-queues-examples/blob/master/0mq/c/e­xample01/server.c
9 Makefile Makefile pro překlad a slinkování https://github.com/tisnik/message-queues-examples/blob/master/0mq/c/e­xample01/Makefile

19. Odkazy na předchozí části seriálu

Následují odkazy na všech pět předchozích částí seriálu o různých způsobem implementace front zpráv:

  1. 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/
  2. Celery: systém implementující asynchronní fronty úloh pro Python
    https://www.root.cz/clanky/celery-system-implementujici-asynchronni-fronty-uloh-pro-python/
  3. Celery: systém implementující asynchronní fronty úloh pro Python (dokončení)
    https://www.root.cz/clanky/celery-system-implementujici-asynchronni-fronty-uloh-pro-python-dokonceni/
  4. RabbitMQ: jedna z nejúspěšnějších implementací brokera
    https://www.root.cz/clanky/rabbitmq-jedna-z-nejuspesnejsich-implementaci-brokera/
  5. Pokročilejší operace nabízené systémem RabbitMQ
    https://www.root.cz/clanky/po­krocilejsi-operace-nabizene-systemem-rabbitmq/

20. Odkazy na Internetu

  1. ØMQ – Distributed Messaging
    http://zeromq.org/
  2. ØMQ Community
    http://zeromq.org/community
  3. Get The Software
    http://zeromq.org/intro:get-the-software
  4. PyZMQ Documentation
    https://pyzmq.readthedocs­.io/en/latest/
  5. ZeroMQ is the answer, by Ian Barber
    https://vimeo.com/20605470
  6. ZeroMQ RFC
    https://rfc.zeromq.org/
  7. ZeroMQ and Clojure, a brief introduction
    https://antoniogarrote.wor­dpress.com/2010/09/08/zeromq-and-clojure-a-brief-introduction/
  8. zeromq/czmq
    https://github.com/zeromq/czmq
  9. golang wrapper for CZMQ
    https://github.com/zeromq/goczmq
  10. ZeroMQ version reporting in Python
    http://zguide.zeromq.org/py:version
  11. A Go interface to ZeroMQ version 4
    https://github.com/pebbe/zmq4
  12. Learning ØMQ with pyzmq
    https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/
  13. Céčková funkce zmq_ctx_new
    http://api.zeromq.org/4–2:zmq-ctx-new
  14. Céčková funkce zmq_ctx_destroy
    http://api.zeromq.org/4–2:zmq-ctx-destroy
  15. Céčková funkce zmq_bind
    http://api.zeromq.org/4–2:zmq-bind
  16. Céčková funkce zmq_unbind
    http://api.zeromq.org/4–2:zmq-unbind
  17. Céčková C funkce zmq_connect
    http://api.zeromq.org/4–2:zmq-connect
  18. Céčková C funkce zmq_disconnect
    http://api.zeromq.org/4–2:zmq-disconnect
  19. Céčková C funkce zmq_send
    http://api.zeromq.org/4–2:zmq-send
  20. Céčková C funkce zmq_recv
    http://api.zeromq.org/4–2:zmq-recv
  21. Třída Context (Python)
    https://pyzmq.readthedocs­.io/en/latest/api/zmq.html#con­text
  22. Třída Socket (Python)
    https://pyzmq.readthedocs­.io/en/latest/api/zmq.html#soc­ket
  23. Python binding
    http://zeromq.org/bindings:python
  24. Why should I have written ZeroMQ in C, not C++ (part I)
    http://250bpm.com/blog:4
  25. Why should I have written ZeroMQ in C, not C++ (part II)
    http://250bpm.com/blog:8
  26. About Nanomsg
    https://nanomsg.org/
  27. Advanced Message Queuing Protocol
    https://www.amqp.org/
  28. Advanced Message Queuing Protocol na Wikipedii
    https://en.wikipedia.org/wi­ki/Advanced_Message_Queuin­g_Protocol
  29. Dokumentace k příkazu rabbitmqctl
    https://www.rabbitmq.com/rab­bitmqctl.8.html
  30. RabbitMQ
    https://www.rabbitmq.com/
  31. RabbitMQ Tutorials
    https://www.rabbitmq.com/get­started.html
  32. RabbitMQ: Clients and Developer Tools
    https://www.rabbitmq.com/dev­tools.html
  33. RabbitMQ na Wikipedii
    https://en.wikipedia.org/wi­ki/RabbitMQ
  34. Streaming Text Oriented Messaging Protocol
    https://en.wikipedia.org/wi­ki/Streaming_Text_Oriented_Mes­saging_Protocol
  35. Message Queuing Telemetry Transport
    https://en.wikipedia.org/wiki/MQTT
  36. Erlang
    http://www.erlang.org/
  37. pika 0.12.0 na PyPi
    https://pypi.org/project/pika/
  38. Introduction to Pika
    https://pika.readthedocs.i­o/en/stable/
  39. Langohr: An idiomatic Clojure client for RabbitMQ that embraces the AMQP 0.9.1 model
    http://clojurerabbitmq.info/
  40. AMQP 0–9–1 Model Explained
    http://www.rabbitmq.com/tutorials/amqp-concepts.html
  41. Part 1: RabbitMQ for beginners – What is RabbitMQ?
    https://www.cloudamqp.com/blog/2015–05–18-part1-rabbitmq-for-beginners-what-is-rabbitmq.html
  42. Downloading and Installing RabbitMQ
    https://www.rabbitmq.com/dow­nload.html
  43. celery na PyPi
    https://pypi.org/project/celery/
  44. Databáze Redis (nejenom) pro vývojáře používající Python
    https://www.root.cz/clanky/databaze-redis-nejenom-pro-vyvojare-pouzivajici-python/
  45. 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/
  46. Redis Queue (RQ)
    https://www.fullstackpython.com/redis-queue-rq.html
  47. Python Celery & RabbitMQ Tutorial
    https://tests4geeks.com/python-celery-rabbitmq-tutorial/
  48. Flower: Real-time Celery web-monitor
    http://docs.celeryproject­.org/en/latest/userguide/mo­nitoring.html#flower-real-time-celery-web-monitor
  49. Asynchronous Tasks With Django and Celery
    https://realpython.com/asynchronous-tasks-with-django-and-celery/
  50. First Steps with Celery
    http://docs.celeryproject­.org/en/latest/getting-started/first-steps-with-celery.html
  51. node-celery
    https://github.com/mher/node-celery
  52. Full Stack Python: web development
    https://www.fullstackpython.com/web-development.html
  53. Introducing RQ
    https://nvie.com/posts/introducing-rq/
  54. Asynchronous Tasks with Flask and Redis Queue
    https://testdriven.io/asynchronous-tasks-with-flask-and-redis-queue
  55. rq-dashboard
    https://github.com/eoranged/rq-dashboard
  56. Stránky projektu Redis
    https://redis.io/
  57. Introduction to Redis
    https://redis.io/topics/introduction
  58. Try Redis
    http://try.redis.io/
  59. Redis tutorial, April 2010 (starší, ale pěkně udělaný)
    https://static.simonwilli­son.net/static/2010/redis-tutorial/
  60. Python Redis
    https://redislabs.com/lp/python-redis/
  61. 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/
  62. Praktický úvod do Redis (1): vaše distribuovaná NoSQL cache
    http://www.cloudsvet.cz/?p=253
  63. Praktický úvod do Redis (2): transakce
    http://www.cloudsvet.cz/?p=256
  64. Praktický úvod do Redis (3): cluster
    http://www.cloudsvet.cz/?p=258
  65. Connection pool
    https://en.wikipedia.org/wi­ki/Connection_pool
  66. Instant Redis Sentinel Setup
    https://github.com/ServiceStack/redis-config
  67. How to install REDIS in LInux
    https://linuxtechlab.com/how-install-redis-server-linux/
  68. Redis RDB Dump File Format
    https://github.com/sripat­hikrishnan/redis-rdb-tools/wiki/Redis-RDB-Dump-File-Format
  69. Lempel–Ziv–Welch
    https://en.wikipedia.org/wi­ki/Lempel%E2%80%93Ziv%E2%80%93­Welch
  70. Redis Persistence
    https://redis.io/topics/persistence
  71. Redis persistence demystified
    http://oldblog.antirez.com/post/redis-persistence-demystified.html
  72. Redis reliable queues with Lua scripting
    http://oldblog.antirez.com/post/250
  73. Ost (knihovna)
    https://github.com/soveran/ost
  74. NoSQL
    https://en.wikipedia.org/wiki/NoSQL
  75. Shard (database architecture)
    https://en.wikipedia.org/wi­ki/Shard_%28database_archi­tecture%29
  76. What is sharding and why is it important?
    https://stackoverflow.com/qu­estions/992988/what-is-sharding-and-why-is-it-important
  77. What Is Sharding?
    https://btcmanager.com/what-sharding/
  78. Redis clients
    https://redis.io/clients
  79. Category:Lua-scriptable software
    https://en.wikipedia.org/wi­ki/Category:Lua-scriptable_software
  80. Seriál Programovací jazyk Lua
    https://www.root.cz/seria­ly/programovaci-jazyk-lua/
  81. Redis memory usage
    http://nosql.mypopescu.com/pos­t/1010844204/redis-memory-usage
  82. Ukázka konfigurace Redisu pro lokální testování
    https://github.com/tisnik/pre­sentations/blob/master/re­dis/redis.conf
  83. Resque
    https://github.com/resque/resque
  84. Nested transaction
    https://en.wikipedia.org/wi­ki/Nested_transaction
  85. Publish–subscribe pattern
    https://en.wikipedia.org/wi­ki/Publish%E2%80%93subscri­be_pattern
  86. Messaging pattern
    https://en.wikipedia.org/wi­ki/Messaging_pattern
  87. Using pipelining to speedup Redis queries
    https://redis.io/topics/pipelining
  88. Pub/Sub
    https://redis.io/topics/pubsub
  89. ZeroMQ distributed messaging
    http://zeromq.org/
  90. ZeroMQ: Modern & Fast Networking Stack
    https://www.igvita.com/2010/09/03/ze­romq-modern-fast-networking-stack/
  91. Publish/Subscribe paradigm: Why must message classes not know about their subscribers?
    https://stackoverflow.com/qu­estions/2908872/publish-subscribe-paradigm-why-must-message-classes-not-know-about-their-subscr
  92. Python & Redis PUB/SUB
    https://medium.com/@johngrant/python-redis-pub-sub-6e26b483b3f7
  93. Message broker
    https://en.wikipedia.org/wi­ki/Message_broker
  94. RESP Arrays
    https://redis.io/topics/protocol#array-reply
  95. Redis Protocol specification
    https://redis.io/topics/protocol
  96. Redis Pub/Sub: Intro Guide
    https://www.redisgreen.net/blog/pubsub-intro/
  97. Redis Pub/Sub: Howto Guide
    https://www.redisgreen.net/blog/pubsub-howto/
  98. Comparing Publish-Subscribe Messaging and Message Queuing
    https://dzone.com/articles/comparing-publish-subscribe-messaging-and-message
  99. Apache Kafka
    https://kafka.apache.org/
  100. Iron
    http://www.iron.io/mq
  101. kue (založeno na Redisu, určeno pro node.js)
    https://github.com/Automattic/kue
  102. Cloud Pub/Sub
    https://cloud.google.com/pubsub/
  103. Introduction to Redis Streams
    https://redis.io/topics/streams-intro
  104. glob (programming)
    https://en.wikipedia.org/wi­ki/Glob_(programming)
  105. Why and how Pricing Assistant migrated from Celery to RQ – Paris.py
    https://www.slideshare.net/syl­vinus/why-and-how-pricing-assistant-migrated-from-celery-to-rq-parispy-2
  106. Enqueueing internals
    http://python-rq.org/contrib/
  107. queue — A synchronized queue class
    https://docs.python.org/3/li­brary/queue.html
  108. Queues
    http://queues.io/
  109. Windows Subsystem for Linux Documentation
    https://docs.microsoft.com/en-us/windows/wsl/about
  110. RestMQ
    http://restmq.com/
  111. ActiveMQ
    http://activemq.apache.org/
  112. Amazon MQ
    https://aws.amazon.com/amazon-mq/
  113. Amazon Simple Queue Service
    https://aws.amazon.com/sqs/
  114. Celery: Distributed Task Queue
    http://www.celeryproject.org/
  115. Disque, an in-memory, distributed job queue
    https://github.com/antirez/disque
  116. rq-dashboard
    https://github.com/eoranged/rq-dashboard
  117. Projekt RQ na PyPi
    https://pypi.org/project/rq/
  118. rq-dashboard 0.3.12
    https://pypi.org/project/rq-dashboard/
  119. Job queue
    https://en.wikipedia.org/wi­ki/Job_queue
  120. Why we moved from Celery to RQ
    https://frappe.io/blog/technology/why-we-moved-from-celery-to-rq
  121. Running multiple workers using Celery
    https://serverfault.com/qu­estions/655387/running-multiple-workers-using-celery
  122. celery — Distributed processing
    http://docs.celeryproject­.org/en/latest/reference/ce­lery.html
  123. Chains
    https://celery.readthedoc­s.io/en/latest/userguide/can­vas.html#chains
  124. Routing
    http://docs.celeryproject­.org/en/latest/userguide/rou­ting.html#automatic-routing
  125. Celery Distributed Task Queue in Go
    https://github.com/gocelery/gocelery/
  126. Python Decorators
    https://wiki.python.org/mo­in/PythonDecorators
  127. Periodic Tasks
    http://docs.celeryproject­.org/en/latest/userguide/pe­riodic-tasks.html
  128. celery.schedules
    http://docs.celeryproject­.org/en/latest/reference/ce­lery.schedules.html#celery­.schedules.crontab
  129. Pros and cons to use Celery vs. RQ
    https://stackoverflow.com/qu­estions/13440875/pros-and-cons-to-use-celery-vs-rq
  130. Priority queue
    https://en.wikipedia.org/wi­ki/Priority_queue
  131. Jupyter
    https://jupyter.org/
  132. How IPython and Jupyter Notebook work
    https://jupyter.readthedoc­s.io/en/latest/architectu­re/how_jupyter_ipython_wor­k.html
Našli jste v článku chybu?