Obsah
1. Implementace front zpráv podle normy POSIX
2. Knihovna, s níž je nutné slinkovat demonstrační příklady
3. Vytvoření fronty, její otevření a poslání zprávy
4. Úplný zdrojový kód aplikace pro otevření fronty a poslání zprávy
5. Otevření existující fronty, získání podrobnějších informací o frontě a přečtení zprávy
6. Úplný zdrojový kód aplikace pro otevření fronty a přečtení zprávy
8. Nastavení parametrů vytvářené fronty
9. Kontinuální posílání a příjem zpráv (vytvoření pipeline)
10. Utilitka pro vytvoření nové prázdné fronty
11. Klient pro posílání zpráv s periodou jedné sekundy
12. Klient pro kontinuální čtení zpráv z fronty
13. Ukázka komunikace mezi několika klienty
14. Použití signálů a handlerů pro příjem zpráv
15. Vylepšení předchozího příkladu – korektní alokace paměti pro zprávu v příjemci
16. Sledování front přes virtuální souborový systém
17. Informace o systému front získané přes souborový systém procfs
18. Repositář s demonstračními příklady
1. Implementace front zpráv podle normy POSIX
V úterním článku jsme se seznámili s implementací front zpráv používanou pro meziprocesovou komunikaci. Jednalo se o technologii založenou na implementaci použité poprvé v Systemu V, což je mj. i jeden z přímých předchůdců Solarisu, ovšem vlastnosti Systemu V se postupně dostaly do dalších unixových systémů i do Linuxu. Tyto fronty zpráv lze v současnosti použít prakticky kdekoli a navíc se jedná o relativně jednoduše zvládnutelnou technologii, pro jejíž využití postačuje znát pouze tři příkazy (ipcs, ipcmk a ipcrm) a čtyři knihovní funkce (volatelné z céčka a tím pádem i z prakticky jakéhokoli programovacího jazyka s FFI či obdobnou technologií). Existuje ovšem i alternativní implementace front zpráv, která je definována v normě POSIX, kterou do větší či menší míry implementují všechny moderní unixy i systém Linux.
Tato implementace může vypadat z pohledu programátora nepatrně složitěji, protože je k dispozici větší množství funkcí. Namísto původních čtyř funkcí lze využít celkem devět funkcí, jejichž jména v mnoha případech přímo odpovídají příslušným syscallům (voláním služeb jádra operačního systému):
# | Funkce | Odpovídající syscall | Stručný popis funkce |
---|---|---|---|
1 | mq_open | mq_open | otevření, popř. i vytvoření fronty zpráv |
2 | mq_close | close | uzavření fronty zpráv |
3 | mq_unlink | mq_unlink | smazání fronty zpráv |
3 | mq_send | mq_timedsend | poslání zprávy do fronty |
4 | mq_timedsend | mq_timedsend | poslání zprávy do fronty se specifikací timeoutu |
5 | mq_receive | mq_timedreceive | přečtení zprávy z fronty |
6 | mq_timedreceive | mq_timedreceive | přečtení zprávy z fronty se specifikací timeoutu |
7 | mq_notify | mq_notify | registrace asynchronního upozornění na novou zprávu |
8 | mq_getattr | mq_getsetattr | získání parametrů vybrané fronty |
9 | mq_setattr | mq_getsetattr | změna parametrů vybrané fronty |
Ovšem mezi oběma implementacemi jsou i další, mnohdy velmi podstatné rozdíly. V POSIXových frontách je fronta jednoznačně identifikována svým jménem, takže není zapotřebí používat relativně křehký koncept generovaných klíčů. Dále se POSIXové fronty chovají jako běžné file deskriptory, což je ostatně vidět i na tom, že funkce mq_close používá syscall close. A nakonec POSIXové fronty umožňují použít (poslat, přečíst) zprávy s prioritou, což je pro mnohé implementace velmi důležité.
2. Knihovna, s níž je nutné slinkovat demonstrační příklady
Minule popsané funkce, které se používají pro ovládání front zpráv podle Systemu V, jsou vlastně pouhými rozhraními na příslušné syscally jádra systému, o čemž se ostatně můžeme přesvědčit pohledem na manuálovou stránku:
$ man syscalls | grep \ \ msg msgctl(2) 2.0 See notes on ipc(2) msgget(2) 2.0 See notes on ipc(2) msgrcv(2) 2.0 See notes on ipc(2) msgsnd(2) 2.0 See notes on ipc(2)
V případě dnes popisovaného systému front je situace nepatrně odlišná, protože je nutné slinkovat všechny demonstrační příklady s knihovnou nazvanou librt, která je v systému dostupná jak ve formě archivu objektových souborů (.a), tak i sdílené knihovny (.so). V příkladech popsaných v následujících kapitolách je tedy nutné použít přepínač -lrt předaný linkeru. Úplná varianta souboru Makefile bude upravena následujícím způsobem (viz zvýrazněné řádky):
CC=gcc LINKER=gcc CFLAGS=-O0 -Wall -pedantic LFLAGS=-lrt .PHONY: clean all: publisher subscriber %.o: %.c $(CC) -c -o $@ $(CFLAGS) $< publisher: publisher.o $(CC) -o $@ $< $(LFLAGS) subscriber: subscriber.o $(CC) -o $@ $< $(LFLAGS) clean: rm -f *.o \ rm -f publisher \ rm -f subscriber
3. Vytvoření fronty, její otevření a poslání zprávy
V této kapitole si ukážeme, jak lze vytvořit novou frontu, otevřít ji a poslat do ní zprávy. Fronty (podle POSIXu) jsou identifikovány svým jménem, které musí začínat lomítkem, za nímž může následovat maximálně 254 alfanumerických znaků (kromě lomítka). Jméno fronty tedy nahrazuje původní koncept založený na vygenerovaném klíči. V prvním příkladu použijeme toto jméno:
#define QUEUE_NAME "/queue1"
Vytvoření a současně i otevření fronty zajišťuje funkce mq_open, které se předávají čtyři parametry. V prvním parametru je předáno jméno fronty, v parametru druhém režim otevření/vytvoření fronty, v parametru třetím režim vytvoření nové fronty a konečně v parametru čtvrtém ukazatel na strukturu s popisem dalších parametrů fronty. V našem konkrétním případě je fronta vytvořena (O_CREAT) a otevřena v režimu čtení i zápisu (O_RDWR), ovšem s tím, že pokud již fronta existuje, dojde k chybě (O_EXCL). Režim vytvoření fronty je 0770 (S_IRXWU a S_IRWXG). Žádné další parametry fronty prozatím nejsou specifikovány, takže se v posledním parametru namísto ukazatele předává NULL. Samozřejmě nesmíme zapomenout zkontrolovat, zda se vrátil handle fronty, či zda došlo k nějaké chybě:
mqd_t message_queue_id; message_queue_id = mq_open(QUEUE_NAME, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG, NULL); if (message_queue_id == -1) { perror("Unable to create queue"); return 2; }
Poslání zprávy je ve skutečnosti velmi jednoduché, protože potřebujeme znát pouze handle fronty (viz předchozí odstavec), samotný text zprávy (libovolná sekvence bajtů, tedy včetně řetězců), délku zprávy a její prioritu. Pro tuto chvíli budeme u všech zpráv používat prioritu nastavenou na jedničku:
int status; status = mq_send(message_queue_id, message_text, strlen(message_text)+1, priority); if (status == -1) { perror("Unable to send message"); return 2; }
Samotné poslání zprávy do fronty může být blokující i neblokující operace, což je rozdíl, který poznáme ve chvíli, kdy je fronta zaplněna (ve výchozím nastavení po poslání deseti zpráv). Implicitně je zápis do fronty blokující operace, což znamená, že se ve funkci mq_send aktuální vlákno zastaví a čeká na dobu, kdy dojde k uvolnění fronty.
Na konci programu je slušné frontu zavřít, a to zavoláním funkce mq_close. I tato funkce může vracet chybový stav, s ním ovšem již žádným dalším způsobem nebudeme pracovat, pouze program ukončíme:
int status; status = mq_close(message_queue_id); if (status == -1) { perror("Unable to close message queue"); return 2; }
4. Úplný zdrojový kód aplikace pro otevření fronty a poslání zprávy
Úplný zdrojový kód aplikace, která po svém spuštění vytvoří a otevře novou frontu, do které pošle zprávu, bude vypadat následovně (celý zdrojový kód lze nalézt na adrese https://github.com/tisnik/message-queues-examples/blob/master/unix-messages/posix/example1/publisher.c):
#include <stdio.h> #include <string.h> #include <mqueue.h> #define QUEUE_NAME "/queue1" int main(void) { mqd_t message_queue_id; unsigned int priority = 0; char message_text[100]; int status; message_queue_id = mq_open(QUEUE_NAME, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG, NULL); if (message_queue_id == -1) { perror("Unable to create queue"); return 2; } strcpy(message_text, "Hello world!"); status = mq_send(message_queue_id, message_text, strlen(message_text)+1, priority); if (status == -1) { perror("Unable to send message"); return 2; } status = mq_close(message_queue_id); if (status == -1) { perror("Unable to close message queue"); return 2; } return 0; }
5. Otevření existující fronty a přečtení zprávy
Příjemce zprávy bude taktéž potřebovat přístup k frontě. Tato fronta již ale musí existovat, takže volání funkce mq_open bude v tomto případě nepatrně jednodušší. Zadáme totiž pouze jméno fronty a režim otevření fronty (O_RDRW); další dva nepovinné parametry funkce mq_open není zapotřebí specifikovat:
#define QUEUE_NAME "/queue1" mqd_t message_queue_id; message_queue_id = mq_open(QUEUE_NAME, O_RDWR); if (message_queue_id == -1) { perror("Unable to open queue"); return 2; }
Poněkud komplikovanější může být přečtení zprávy, protože nemáme k dispozici (resp. alespoň prozatím nevíme jak získat) délku zprávy. Zatím tedy vytvoříme dostatečně velký buffer, do kterého bude zpráva načítána a jak adresu, tak i délku tohoto bufferu předáme funkci mq_receive. Poslední parametr může být NULL, popř. může obsahovat ukazatel na proměnnou, do které se uloží priorita právě získané zprávy:
int status; status = mq_receive(message_queue_id, message_text, sizeof(message_text), &sender); if (status == -1) { perror("Unable to receive message"); return 2; }
I čtení z fronty je, podobně jako zápis do ní, buď operace blokující nebo neblokující. Ve výchozím nastavení se jedná o blokující operaci, což znamená, že pokud je fronta prázdná, bude funkce mq_receive čekat až do chvíle, kdy je do fronty nějaká zpráva zapsána.
Na konci pochopitelně nesmíme zapomenout frontu opět uzavřít s případnou kontrolou, zda se uzavření podařilo:
status = mq_close(message_queue_id); if (status == -1) { perror("Unable to close message queue"); return 2; }
6. Úplný zdrojový kód aplikace pro otevření fronty a přečtení zprávy
Opět se podívejme na úplný zdrojový kód aplikace, po jejímž spuštění se otevře fronta „/queue1“ a přečte se z ní zpráva. Úplný zdrojový kód tohoto příkladu naleznete na adrese https://github.com/tisnik/message-queues-examples/blob/master/unix-messages/posix/example1/subscriber.c:
#include <stdio.h> #include <string.h> #include <mqueue.h> #define QUEUE_NAME "/queue1" int main(void) { mqd_t message_queue_id; char message_text[10000]; unsigned int sender; int status; message_queue_id = mq_open(QUEUE_NAME, O_RDWR); if (message_queue_id == -1) { perror("Unable to open queue"); return 2; } status = mq_receive(message_queue_id, message_text, sizeof(message_text), &sender); if (status == -1) { perror("Unable to receive message"); return 2; } printf("Received message (%d bytes) from %d: %s\n", status, sender, message_text); status = mq_close(message_queue_id); if (status == -1) { perror("Unable to close message queue"); return 2; } return 0; }
7. Přečtení atributů fronty
V předchozím příkladu jsme řešili jeden problém – jak zjistit maximální možnou délku zprávy umístěné do fronty. Tato informace je ve skutečnosti součástí atributů fronty a tyto atributy je možné přečíst funkcí mq_getattr. Jedná se o funkci naplňující prvky struktury typu mq_attr:
struct mq_attr { __syscall_slong_t mq_flags; /* Message queue flags. */ __syscall_slong_t mq_maxmsg; /* Maximum number of messages. */ __syscall_slong_t mq_msgsize; /* Maximum message size. */ __syscall_slong_t mq_curmsgs; /* Number of messages currently queued. */ __syscall_slong_t __pad[4]; };
Samotné přečtení atributů lze realizovat například tímto kódem:
struct mq_attr msgq_attr; mq_getattr(message_queue_id, &msgq_attr); printf("Queue: %s\n", QUEUE_NAME); printf("Max. messages: %ld\n", msgq_attr.mq_maxmsg); printf("Current messages: %ld\n", msgq_attr.mq_curmsgs); printf("Max. message size: %ld\n", msgq_attr.mq_msgsize);
Získání těchto informací je možné prakticky kdykoli, jak je to ostatně ukázáno v dalším příkladu:
#include <stdio.h> #include <string.h> #include <mqueue.h> #define QUEUE_NAME "/queue2" int main(void) { mqd_t message_queue_id; char message_text[10000]; unsigned int sender; int status; message_queue_id = mq_open(QUEUE_NAME, O_RDWR); if (message_queue_id == -1) { perror("Unable to open queue"); return 2; } { struct mq_attr msgq_attr; mq_getattr(message_queue_id, &msgq_attr); printf("Queue: %s\n", QUEUE_NAME); printf("Max. messages: %ld\n", msgq_attr.mq_maxmsg); printf("Current messages: %ld\n", msgq_attr.mq_curmsgs); printf("Max. message size: %ld\n", msgq_attr.mq_msgsize); } status = mq_receive(message_queue_id, message_text, sizeof(message_text), &sender); if (status == -1) { perror("Unable to receive message"); return 2; } printf("Received message (%d bytes) from %d: %s\n", status, sender, message_text); status = mq_close(message_queue_id); if (status == -1) { perror("Unable to close message queue"); return 2; } return 0; }
8. Nastavení parametrů vytvářené fronty
Parametry fronty lze v případě potřeby i nastavit, ovšem v rámci mezí, o nichž se zmíníme v sedmnácté kapitole. Samotné nastavení se provádí přes datovou strukturu mq_attr:
struct mq_attr msgq_attr; msgq_attr.mq_flags = 0; msgq_attr.mq_maxmsg = 10; msgq_attr.mq_msgsize = 20; msgq_attr.mq_curmsgs = 1;
Ukazatel na tuto strukturu se předá do čtvrtého parametru funkce mq_open:
message_queue_id = mq_open(QUEUE_NAME, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG, &msgq_attr); if (message_queue_id == -1) { perror("Unable to create queue"); return 2; }
Úplný zdrojový kód upraveného producenta zpráv může vypadat následovně:
#include <stdio.h> #include <string.h> #include <mqueue.h> #define QUEUE_NAME "/queue3" int main(void) { mqd_t message_queue_id; unsigned int priority = 0; char message_text[100]; int status; struct mq_attr msgq_attr; msgq_attr.mq_flags = 0; msgq_attr.mq_maxmsg = 10; msgq_attr.mq_msgsize = 20; msgq_attr.mq_curmsgs = 1; mq_unlink(QUEUE_NAME); message_queue_id = mq_open(QUEUE_NAME, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG, &msgq_attr); if (message_queue_id == -1) { perror("Unable to create queue"); return 2; } strcpy(message_text, "Hello world!"); status = mq_send(message_queue_id, message_text, strlen(message_text)+1, priority); if (status == -1) { perror("Unable to send message"); return 2; } status = mq_close(message_queue_id); if (status == -1) { perror("Unable to close message queue"); return 2; } return 0; }
Konzument zpráv se prakticky nezmění, až na vylepšené vypsání informace o otevřené frontě do jediného řádku:
#include <stdio.h> #include <string.h> #include <mqueue.h> #define QUEUE_NAME "/queue3" int main(void) { mqd_t message_queue_id; char message_text[10000]; unsigned int sender; int status; message_queue_id = mq_open(QUEUE_NAME, O_RDWR); if (message_queue_id == -1) { perror("Unable to open queue"); return 2; } { struct mq_attr msgq_attr; mq_getattr(message_queue_id, &msgq_attr); printf("Queue \"%s\":\n\t- stores at most %ld messages\n\t- large at most %ld bytes each\n\t- currently holds %ld messages\n", QUEUE_NAME, msgq_attr.mq_maxmsg, msgq_attr.mq_msgsize, msgq_attr.mq_curmsgs); } status = mq_receive(message_queue_id, message_text, sizeof(message_text), &sender); if (status == -1) { perror("Unable to receive message"); return 2; } printf("Received message (%d bytes) from %d: %s\n", status, sender, message_text); status = mq_close(message_queue_id); if (status == -1) { perror("Unable to close message queue"); return 2; } return 0; }
9. Kontinuální posílání a příjem zpráv (vytvoření pipeline)
V navazujících kapitolách si ukážeme, jak lze realizovat kontinuální zápis a čtení zpráv s využitím jediné fronty a jak se systém bude chovat po připojení většího množství komunikujících uzlů k jedné frontě.
10. Utilitka pro vytvoření nové prázdné fronty
Pro vytvoření nové fronty, tedy pro obdobu příkazu ipcmk popsaného minule, můžeme použít následující jednoduchý program, který po svém spuštění smaže frontu /queue4 (pokud tedy taková fronta existuje) a vytvoří místo ní frontu novou s kapacitou deseti zpráv (což je většinou výchozí maximální povolená hodnota):
#include <stdio.h> #include <string.h> #include <unistd.h> #include <mqueue.h> #define QUEUE_NAME "/queue4" int main(void) { mqd_t message_queue_id; struct mq_attr msgq_attr; msgq_attr.mq_flags = 0; msgq_attr.mq_maxmsg = 10; msgq_attr.mq_msgsize = 20; msgq_attr.mq_curmsgs = 1; mq_unlink(QUEUE_NAME); message_queue_id = mq_open(QUEUE_NAME, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG, &msgq_attr); if (message_queue_id == -1) { perror("Unable to create queue"); return 2; } return 0; }
11. Klient pro posílání zpráv s periodou jedné sekundy
Klienta, který do fronty /queue4 posílá zprávy s frekvencí přibližně jedné sekundy, můžeme implementovat například následujícím způsobem:
#include <stdio.h> #include <string.h> #include <unistd.h> #include <mqueue.h> #define QUEUE_NAME "/queue4" int main(void) { mqd_t message_queue_id; unsigned int priority = 0; char message_text[100]; int status; int msg_number = 1; struct mq_attr msgq_attr; msgq_attr.mq_flags = 0; msgq_attr.mq_maxmsg = 10; msgq_attr.mq_msgsize = 20; msgq_attr.mq_curmsgs = 1; message_queue_id = mq_open(QUEUE_NAME, O_WRONLY, S_IRWXU | S_IRWXG, &msgq_attr); if (message_queue_id == -1) { perror("Unable to create queue"); return 2; } while (1) { struct mq_attr msgq_attr; sprintf(message_text, "Message #%d", msg_number); status = mq_send(message_queue_id, message_text, strlen(message_text)+1, priority); if (status == -1) { perror("Unable to send message"); return 2; } mq_getattr(message_queue_id, &msgq_attr); printf("%ld/%ld\n", msgq_attr.mq_curmsgs, msgq_attr.mq_maxmsg); msg_number++; sleep(1); } status = mq_close(message_queue_id); if (status == -1) { perror("Unable to close message queue"); return 2; } return 0; }
12. Klient pro kontinuální čtení zpráv z fronty
Úprava předchozích konzumentů zpráv takovým způsobem, aby se zprávy četly kontinuálně, je snadná, jak je to ostatně ukázáno v dalším demonstračním příkladu:
#include <stdio.h> #include <string.h> #include <mqueue.h> #define QUEUE_NAME "/queue4" int main(void) { mqd_t message_queue_id; char message_text[10000]; unsigned int sender; int status; message_queue_id = mq_open(QUEUE_NAME, O_RDONLY); if (message_queue_id == -1) { perror("Unable to open queue"); return 2; } { struct mq_attr msgq_attr; mq_getattr(message_queue_id, &msgq_attr); printf("Queue \"%s\":\n\t- stores at most %ld messages\n\t- large at most %ld bytes each\n\t- currently holds %ld messages\n", QUEUE_NAME, msgq_attr.mq_maxmsg, msgq_attr.mq_msgsize, msgq_attr.mq_curmsgs); } while (1) { status = mq_receive(message_queue_id, message_text, sizeof(message_text), &sender); if (status == -1) { perror("Unable to receive message"); return 2; } printf("Received message (%d bytes) from %d: %s\n", status, sender, message_text); } status = mq_close(message_queue_id); if (status == -1) { perror("Unable to close message queue"); return 2; } return 0; }
13. Ukázka komunikace mezi několika klienty
Jeden producent a jeden konzument:
$ ./publisher 0/10 0/10 0/10 0/10 0/10 0/10 0/10 0/10 0/10
$ ./subscriber Queue "/queue4": - stores at most 10 messages - large at most 20 bytes each - currently holds 0 messages Received message (11 bytes) from 0: Message #1 Received message (11 bytes) from 0: Message #2 Received message (11 bytes) from 0: Message #3 Received message (11 bytes) from 0: Message #4 Received message (11 bytes) from 0: Message #5 Received message (11 bytes) from 0: Message #6
Připojení jednoho producenta a dvou konzumentů:
$ ./publisher 0/10 0/10 0/10 0/10 0/10 0/10 0/10 0/10 0/10 0/10
$ ./subscriber Queue "/queue4": - stores at most 10 messages - large at most 20 bytes each - currently holds 0 messages Received message (11 bytes) from 0: Message #1 Received message (11 bytes) from 0: Message #3 Received message (11 bytes) from 0: Message #5 Received message (11 bytes) from 0: Message #7 Received message (11 bytes) from 0: Message #9
$ ./subscriber Queue "/queue4": - stores at most 10 messages - large at most 20 bytes each - currently holds 0 messages Received message (11 bytes) from 0: Message #2 Received message (11 bytes) from 0: Message #4 Received message (11 bytes) from 0: Message #6 Received message (11 bytes) from 0: Message #8 Received message (12 bytes) from 0: Message #10
Dva producenti a jeden konzument:
$ ./publisher 0/10 0/10 0/10 0/10 0/10 0/10
$ ./publisher 0/10 0/10 0/10 0/10 0/10 0/10
$ ./subscriber Queue "/queue4": - stores at most 10 messages - large at most 20 bytes each - currently holds 0 messages Received message (11 bytes) from 0: Message #1 Received message (11 bytes) from 0: Message #1 Received message (11 bytes) from 0: Message #2 Received message (11 bytes) from 0: Message #2 Received message (11 bytes) from 0: Message #3 Received message (11 bytes) from 0: Message #3 Received message (11 bytes) from 0: Message #4 Received message (11 bytes) from 0: Message #4 Received message (11 bytes) from 0: Message #5
14. Použití signálů a handlerů pro příjem zpráv
Zajímavý a poměrně užitečný koncept, s nímž se setkáme při práci s POSIXovými frontami zpráv, je možnost poslat signál (a následně ho zachytit v k tomu určeném handleru) ve chvíli, kdy je do fronty vložena nová zpráva. V následujícím demonstračním příkladu je tento koncept využit, protože samotná zpráva je zpracována v asynchronně spuštěném handleru. Nejdříve je nutné handler zaregistrovat, a to konkrétně s využitím funkce nazvané mq_notify. Registraci provedeme ve zvláštní uživatelské funkci, jejíž existence se nám bude později hodit:
void register_signal(mqd_t message_queue_id) { struct sigevent sev; mqd_t mqdes; sev.sigev_notify = SIGEV_THREAD; sev.sigev_notify_function = on_message; sev.sigev_notify_attributes = NULL; sev.sigev_value.sival_ptr = &mqdes; if (mq_notify(message_queue_id, &sev) == -1) { perror("Unable to register event"); } else { puts("Handler has been registered"); } }
/* Structure to transport application-defined values with signals. */ typedef struct sigevent { sigval_t sigev_value; int sigev_signo; int sigev_notify; union { int _pad[__SIGEV_PAD_SIZE]; /* When SIGEV_SIGNAL and SIGEV_THREAD_ID set, LWP ID of the thread to receive the signal. */ __pid_t _tid; struct { void (*_function) (sigval_t); /* Function to start. */ pthread_attr_t *_attribute; /* Thread attributes. */ } _sigev_thread; } _sigev_un; } sigevent_t; /* POSIX names to access some of the members. */ #define sigev_notify_function _sigev_un._sigev_thread._function #define sigev_notify_attributes _sigev_un._sigev_thread._attribute
Samotná implementace handleru, v němž se z fronty přečte zpráva, je prozatím velmi přímočará, protože v ní využijeme globální proměnnou message_queue_id, v níž je uloženo ID fronty, s níž pracujeme. Přečtení zprávy v handleru se tedy nijak neodlišuje od předchozích demonstračních příkladů:
static void on_message(union sigval sv) { unsigned int sender; char message_text[10000]; int status; puts("On message..."); status = mq_receive(message_queue_id, message_text, sizeof(message_text), &sender); if (status == -1) { perror("Unable to receive message"); exit(2); } printf("Received message (%d bytes) from %d: %s\n", status, sender, message_text); }
Na konci samotného handleru nesmíme zapomenout na to, že handler je zaregistrován pouze pro přijetí jediné zprávy. Pokud tedy budeme potřebovat podobným způsobem zpracovat větší množství zpráv, je nutné handler znovu zaregistrovat, což je nepatrná úprava, kterou provedeme v rámci dalšího demonstračního příkladu:
register_signal(message_queue_id);
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <unistd.h> #include <signal.h> #include <mqueue.h> #define QUEUE_NAME "/queue5" mqd_t message_queue_id; static void on_message(union sigval sv) { unsigned int sender; char message_text[10000]; int status; puts("On message..."); status = mq_receive(message_queue_id, message_text, sizeof(message_text), &sender); if (status == -1) { perror("Unable to receive message"); exit(2); } printf("Received message (%d bytes) from %d: %s\n", status, sender, message_text); } void register_signal(mqd_t message_queue_id) { struct sigevent sev; mqd_t mqdes; sev.sigev_notify = SIGEV_THREAD; sev.sigev_notify_function = on_message; sev.sigev_notify_attributes = NULL; sev.sigev_value.sival_ptr = &mqdes; if (mq_notify(message_queue_id, &sev) == -1) { perror("Unable to register event"); } else { puts("Handler has been registered"); } } int main(void) { int status; message_queue_id = mq_open(QUEUE_NAME, O_RDONLY); if (message_queue_id == -1) { perror("Unable to open queue"); return 2; } register_signal(message_queue_id); pause(); status = mq_close(message_queue_id); if (status == -1) { perror("Unable to close message queue"); return 2; } return 0; }
#include <stdio.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <mqueue.h> #define QUEUE_NAME "/queue5" int main(void) { mqd_t message_queue_id; unsigned int sender; char message_text[10000]; int status; message_queue_id = mq_open(QUEUE_NAME, O_RDONLY | O_NONBLOCK); if (message_queue_id == -1) { perror("Unable to get queue"); return 2; } while (1) { status = mq_receive(message_queue_id, message_text, sizeof(message_text), &sender); if (status == -1) { if (errno == EAGAIN) { puts("Message queue is empty..."); break; } perror("Unable to receive message"); return 2; } printf("Received message (%d bytes) from %d: %s\n", status, sender, message_text); } status = mq_close(message_queue_id); if (status == -1) { perror("Unable to close message queue"); return 2; } return 0; }
15. Vylepšení předchozího příkladu – korektní alokace paměti pro zprávu v příjemci
Poslední demonstrační příklad, který si dnes ukážeme, vychází z příkladu předchozího. Došlo v něm k vylepšení alokace paměti, do které se ukládají zprávy získané z fronty. Namísto mnohdy zbytečně velkého (ovšem potenciálně nedostatečného) bufferu můžeme přímo z fronty získat informaci o tom, jak velký blok s daty se má očekávat:
long message_size; if (mq_getattr(message_queue_id, &msgq_attr) == -1) { perror("Can not read message queue attributes"); } message_size = msgq_attr.mq_msgsize;
Tento blok je následně naalokován, použit pro příjem zprávy a následně explicitně uvolněn z paměti:
message_text = malloc(message_size * sizeof(char)); if (message_text == NULL) { perror("Allocation error"); } status = mq_receive(message_queue_id, message_text, message_size, &sender); if (status == -1) { perror("Unable to receive message"); exit(2); } printf("Received message (%d bytes) from %d: %s\n", status, sender, message_text); free(message_text);
Taktéž na konci samotného handleru provedeme jeho opětovnou registraci, takže bude připraven přečíst a zpracovat další zprávy, které do fronty budou postupně přicházet:
register_signal(message_queue_id);
Další změny ve zdrojovém kódu příkladu jsou jen nepatrné:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <unistd.h> #include <signal.h> #include <mqueue.h> #define QUEUE_NAME "/queue6" void register_signal(mqd_t message_queue_id); mqd_t message_queue_id; static void on_message(union sigval sv) { unsigned int sender; struct mq_attr msgq_attr; char *message_text; int status; long message_size; puts("On message..."); if (mq_getattr(message_queue_id, &msgq_attr) == -1) { perror("Can not read message queue attributes"); } message_size = msgq_attr.mq_msgsize; message_text = malloc(message_size * sizeof(char)); if (message_text == NULL) { perror("Allocation error"); } status = mq_receive(message_queue_id, message_text, message_size, &sender); if (status == -1) { perror("Unable to receive message"); exit(2); } printf("Received message (%d bytes) from %d: %s\n", status, sender, message_text); free(message_text); register_signal(message_queue_id); } void register_signal(mqd_t message_queue_id) { struct sigevent sev; mqd_t mqdes; sev.sigev_notify = SIGEV_THREAD; sev.sigev_notify_function = on_message; sev.sigev_notify_attributes = NULL; sev.sigev_value.sival_ptr = &mqdes; if (mq_notify(message_queue_id, &sev) == -1) { perror("Unable to register event"); } else { puts("Handler has been registered"); } } int main(void) { int status; message_queue_id = mq_open(QUEUE_NAME, O_RDONLY); if (message_queue_id == -1) { perror("Unable to open queue"); return 2; } register_signal(message_queue_id); pause(); status = mq_close(message_queue_id); if (status == -1) { perror("Unable to close message queue"); return 2; } return 0; }
16. Sledování front přes virtuální souborový systém
Mezi jednu z předností POSIXové implementace front zpráv patří fakt, že stav front je možné sledovat pouze s využitím základních nástrojů určených pro práci se soubory; není tedy nutné (a vlastně ani možné) používat minule popsané nástroje ipcs, ipcmk či ipcrm). Namísto toho lze příkazem mount připojit virtuální souborový systém, který bude informace o frontách obsahovat a poskytovat uživatelům. Tuto operaci je nutné provést s právy superuživatele (a to pouze za předpokladu, že již není virtuální souborový systém připraven):
# mkdir /dev/mqueue # mount -t mqueue none /dev/mqueue
Dále již můžeme pracovat pod běžným uživatelským účtem, přesněji řečeno pod účtem toho uživatele, jehož proces fronty vytvořil. Získání seznamu front je triviální, neboť ho lze přečíst jako obsah adresáře /dev/mqueue:
$ ls -l /dev/mqueue/ total 0 -rwxrwx---. 1 ptisnovs ptisnovs 80 Nov 20 18:08 queue3 -rwxrwx---. 1 ptisnovs ptisnovs 80 Nov 20 18:08 queue4 -rwxrwx---. 1 ptisnovs ptisnovs 80 Nov 20 18:16 queue6
#define QUEUE_NAME "/queue6" mq_unlink(QUEUE_NAME); message_queue_id = mq_open(QUEUE_NAME, O_RDWR | O_CREAT | O_EXCL, S_IWUSR | S_IRGRP, &msgq_attr); if (message_queue_id == -1) { perror("Unable to create queue"); return 2; }
Nový obsah virtuálního souborového systému se změní, a to konkrétně u fronty pojmenované /queue6:
$ ls -l /dev/mqueue/ total 0 -rwxrwx---. 1 ptisnovs ptisnovs 80 Nov 20 18:08 queue3 -rwxrwx---. 1 ptisnovs ptisnovs 80 Nov 20 18:08 queue4 --w-r-----. 1 ptisnovs ptisnovs 80 Nov 20 18:33 queue6
Samozřejmě si můžeme prohlédnout obsah jednotlivých souborů reprezentujících vytvořené fronty, například nástrojem cat, otevřením v textovém editoru atd. atd.:
$ cat /dev/mqueue/queue3 QSIZE:0 NOTIFY:0 SIGNO:0 NOTIFY_PID:0
$ cat /dev/mqueue/queue4 QSIZE:0 NOTIFY:0 SIGNO:0 NOTIFY_PID:0
$ cat /dev/mqueue/queue6 QSIZE:55 NOTIFY:0 SIGNO:0 NOTIFY_PID:0
V případě, že je spuštěn příjemce zpráv založený na použití signálů a handlerů, může situace v případě poslední fronty vypadat odlišně, protože se zobrazí informace o počtu notifikací i o ID procesu, který notifikace zpracovává:
$ cat /dev/mqueue/queue6 QSIZE:110 NOTIFY:2 SIGNO:0 NOTIFY_PID:619
17. Informace o systému front získané přes souborový systém procfs
Další informace o systému front zpráv jsou dostupné ve virtuálním souborovém systému procfs (viz například shrnující článek dostupný na adrese https://en.wikipedia.org/wiki/Procfs). Některé z těchto informací (či možná lépe řečeno atributů) je možné s příslušnými právy nastavit, další jsou určeny pouze pro čtení a jsou nastavovány při překladu jádra operačního systému. Konkrétně se jedná o následující soubory:
Soubor | Význam |
---|---|
/proc/sys/fs/mqueue/msg_default | kapacita zpráv pro nově vytvářené fronty |
/proc/sys/fs/mqueue/msg_max | maximální možná kapacita zpráv pro nově vytvářené fronty |
/proc/sys/fs/mqueue/msgsize_default | výchozí velikost zpráv pro nově vytvářené fronty |
/proc/sys/fs/mqueue/msgsize_max | maximální možná hodnota předchozí volby |
/proc/sys/fs/mqueue/queues_max | celkový počet front, které lze vytvořit |
Výchozí hodnoty jsou odlišné podle verze jádra, ale pochopitelně je můžeme velmi snadno přečíst a dále zpracovat:
$ cat /proc/sys/fs/mqueue/msg_default 10 $ cat /proc/sys/fs/mqueue/msg_max 10 $ cat /proc/sys/fs/mqueue/queues_max 256 $ cat /proc/sys/fs/mqueue/msgsize_default 8192 $ cat /proc/sys/fs/mqueue/msgsize_max 8192
Ke změně hodnot některých atributů (nikoli těch, které končí příponou _max) je nutné použít práva roota a projeví se při zakládání nových front.
18. Repositář s demonstračními příklady
Zdrojové kódy všech dnes popsaných demonstračních příkladů vyvinutých v programovacím jazyku Go 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á stále ještě doslova několik kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
19. Odkazy na Internetu
- procfs
https://en.wikipedia.org/wiki/Procfs - POSIX message queues in Linux
https://www.softprayog.in/programming/interprocess-communication-using-posix-message-queues-in-linux - How is a message queue implemented in the Linux kernel?
https://unix.stackexchange.com/questions/6930/how-is-a-message-queue-implemented-in-the-linux-kernel/6935 - ‘IPCS’ command in Linux with examples
https://www.geeksforgeeks.org/ipcs-command-linux-examples/ - System V IPC: Message Queues
https://nitish712.blogspot.com/2012/11/system-v-ipc-message-queues.html - How to create, check and delete IPC share memory, semaphare and message queue on linux
https://fibrevillage.com/sysadmin/225-how-to-create-check-and-delete-ipc-share-memory-semaphare-and-message-queue-on-linux - MQ_OVERVIEW(7): Linux Programmer's Manual
http://man7.org/linux/man-pages/man7/mq_overview.7.html - mq_overview (7) – Linux Man Pages
https://www.systutorials.com/docs/linux/man/7-mq_overview/ - POSIX.4 Message Queues (+ rozšíření QNX)
https://users.pja.edu.pl/~jms/qnx/help/watcom/clibref/mq_overview.html - System V message queues in Linux
https://www.softprayog.in/programming/interprocess-communication-using-system-v-message-queues-in-linux - Linux System V and POSIX IPC Examples
http://hildstrom.com/projects/ipc_sysv_posix/index.html - Programming Tutorial – Linux: Message Queues
https://ccppcoding.blogspot.com/2013/03/linux-message-queues.html - Go wrapper for POSIX Message Queues
https://github.com/syucream/posix_mq - Stránka projektu NSQ
https://nsq.io/ - Dokumentace k projektu NSQ
https://nsq.io/overview/design.html - Dokumentace ke klientovi pro Go
https://godoc.org/github.com/nsqio/go-nsq - Dokumentace ke klientovi pro Python
https://pynsq.readthedocs.io/en/latest/ - Binární tarbally s NSQ
https://nsq.io/deployment/installing.html - GitHub repositář projektu NSQ
https://github.com/nsqio/nsq - Klienti pro NSQ
https://nsq.io/clients/client_libraries.html - Klient pro Go
https://github.com/nsqio/go-nsq - Klient pro Python
https://github.com/nsqio/pynsq - An Example of Using NSQ From Go
http://tleyden.github.io/blog/2014/11/12/an-example-of-using-nsq-from-go/ - Go Go Gadget
https://word.bitly.com/post/29550171827/go-go-gadget - Simplehttp
https://github.com/bitly/simplehttp - Dramatiq: simple task processing
https://dramatiq.io/ - Cookbook (for Dramatiq)
https://dramatiq.io/cookbook.html - Balíček dramatiq na PyPi
https://pypi.org/project/dramatiq/ - Dramatiq dashboard
https://github.com/Bogdanp/dramatiq_dashboard - Dramatiq na Redditu
https://www.reddit.com/r/dramatiq/ - A Dramatiq broker that can be used with Amazon SQS
https://github.com/Bogdanp/dramatiq_sqs - nanomsg na GitHubu
https://github.com/nanomsg/nanomsg - Referenční příručka knihovny nanomsg
https://nanomsg.org/v1.1.5/nanomsg.html - nng (nanomsg-next-generation)
https://github.com/nanomsg/nng - Differences between nanomsg and ZeroMQ
https://nanomsg.org/documentation-zeromq.html - NATS
https://nats.io/about/ - NATS Streaming Concepts
https://nats.io/documentation/streaming/nats-streaming-intro/ - NATS Streaming Server
https://nats.io/download/nats-io/nats-streaming-server/ - NATS Introduction
https://nats.io/documentation/ - NATS Client Protocol
https://nats.io/documentation/internals/nats-protocol/ - NATS Messaging (Wikipedia)
https://en.wikipedia.org/wiki/NATS_Messaging - Stránka Apache Software Foundation
http://www.apache.org/ - Informace o portu 5672
http://www.tcp-udp-ports.com/port-5672.htm - Třída MessagingHandler knihovny Qpid Proton
https://qpid.apache.org/releases/qpid-proton-0.27.0/proton/python/api/proton._handlers.MessagingHandler-class.html - Třída Event knihovny Qpid Proton
https://qpid.apache.org/releases/qpid-proton-0.27.0/proton/python/api/proton._events.Event-class.html - package stomp (Go)
https://godoc.org/github.com/go-stomp/stomp - Go language library for STOMP protocol
https://github.com/go-stomp/stomp - python-qpid-proton 0.26.0 na PyPi
https://pypi.org/project/python-qpid-proton/ - Qpid Proton
http://qpid.apache.org/proton/ - Using the AMQ Python Client
https://access.redhat.com/documentation/en-us/red_hat_amq/7.1/html-single/using_the_amq_python_client/ - Apache ActiveMQ
http://activemq.apache.org/ - Apache ActiveMQ Artemis
https://activemq.apache.org/artemis/ - Apache ActiveMQ Artemis User Manual
https://activemq.apache.org/artemis/docs/latest/index.html - KahaDB
http://activemq.apache.org/kahadb.html - Understanding the KahaDB Message Store
https://access.redhat.com/documentation/en-US/Fuse_MQ_Enterprise/7.1/html/Configuring_Broker_Persistence/files/KahaDBOverview.html - Command Line Tools (Apache ActiveMQ)
https://activemq.apache.org/activemq-command-line-tools-reference.html - stomp.py 4.1.21 na PyPi
https://pypi.org/project/stomp.py/ - Stomp Tutorial
https://access.redhat.com/documentation/en-US/Fuse_Message_Broker/5.5/html/Connectivity_Guide/files/FMBConnectivityStompTelnet.html - Heartbeat (computing)
https://en.wikipedia.org/wiki/Heartbeat_(computing) - Apache Camel
https://camel.apache.org/ - Red Hat Fuse
https://developers.redhat.com/products/fuse/overview/ - Confusion between ActiveMQ and ActiveMQ-Artemis?
https://serverfault.com/questions/873533/confusion-between-activemq-and-activemq-artemis - Staré stránky projektu HornetQ
http://hornetq.jboss.org/ - Snapshot JeroMQ verze 0.4.4
https://oss.sonatype.org/content/repositories/snapshots/org/zeromq/jeromq/0.4.4-SNAPSHOT/ - Difference between ActiveMQ vs Apache ActiveMQ Artemis
http://activemq.2283324.n4.nabble.com/Difference-between-ActiveMQ-vs-Apache-ActiveMQ-Artemis-td4703828.html - Microservices communications. Why you should switch to message queues
https://dev.to/matteojoliveau/microservices-communications-why-you-should-switch-to-message-queues–48ia - Stomp.py 4.1.19 documentation
https://stomppy.readthedocs.io/en/stable/ - Repositář knihovny JeroMQ
https://github.com/zeromq/jeromq/ - ØMQ – Distributed Messaging
http://zeromq.org/ - ØMQ Community
http://zeromq.org/community - Get The Software
http://zeromq.org/intro:get-the-software - PyZMQ Documentation
https://pyzmq.readthedocs.io/en/latest/ - Module: zmq.decorators
https://pyzmq.readthedocs.io/en/latest/api/zmq.decorators.html - ZeroMQ is the answer, by Ian Barber
https://vimeo.com/20605470 - ZeroMQ RFC
https://rfc.zeromq.org/ - ZeroMQ and Clojure, a brief introduction
https://antoniogarrote.wordpress.com/2010/09/08/zeromq-and-clojure-a-brief-introduction/ - zeromq/czmq
https://github.com/zeromq/czmq - golang wrapper for CZMQ
https://github.com/zeromq/goczmq - ZeroMQ version reporting in Python
http://zguide.zeromq.org/py:version - A Go interface to ZeroMQ version 4
https://github.com/pebbe/zmq4 - Broker vs. Brokerless
http://zeromq.org/whitepapers:brokerless - Learning ØMQ with pyzmq
https://learning-0mq-with-pyzmq.readthedocs.io/en/latest/ - Céčková funkce zmq_ctx_new
http://api.zeromq.org/4–2:zmq-ctx-new - Céčková funkce zmq_ctx_destroy
http://api.zeromq.org/4–2:zmq-ctx-destroy - Céčková funkce zmq_bind
http://api.zeromq.org/4–2:zmq-bind - Céčková funkce zmq_unbind
http://api.zeromq.org/4–2:zmq-unbind - Céčková C funkce zmq_connect
http://api.zeromq.org/4–2:zmq-connect - Céčková C funkce zmq_disconnect
http://api.zeromq.org/4–2:zmq-disconnect - Céčková C funkce zmq_send
http://api.zeromq.org/4–2:zmq-send - Céčková C funkce zmq_recv
http://api.zeromq.org/4–2:zmq-recv - Třída Context (Python)
https://pyzmq.readthedocs.io/en/latest/api/zmq.html#context - Třída Socket (Python)
https://pyzmq.readthedocs.io/en/latest/api/zmq.html#socket - Python binding
http://zeromq.org/bindings:python - Why should I have written ZeroMQ in C, not C++ (part I)
http://250bpm.com/blog:4 - Why should I have written ZeroMQ in C, not C++ (part II)
http://250bpm.com/blog:8 - About Nanomsg
https://nanomsg.org/ - Advanced Message Queuing Protocol
https://www.amqp.org/ - Advanced Message Queuing Protocol na Wikipedii
https://en.wikipedia.org/wiki/Advanced_Message_Queuing_Protocol - Dokumentace k příkazu rabbitmqctl
https://www.rabbitmq.com/rabbitmqctl.8.html - 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/ - 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 - Queue – A thread-safe FIFO implementation
https://pymotw.com/2/Queue/ - 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 - Priority queue
https://en.wikipedia.org/wiki/Priority_queue - Jupyter
https://jupyter.org/ - How IPython and Jupyter Notebook work
https://jupyter.readthedocs.io/en/latest/architecture/how_jupyter_ipython_work.html - Context Managers
http://book.pythontips.com/en/latest/context_managers.html