Obsah
1. Posílání zpráv v aplikacích založených na mikroslužbách
2. Zpráva požadující provedení úlohy: COMMAND
3. Zpráva poslaná ve chvíli, kdy došlo k nějaké události – EVENT
4. Implementace komunikačních strategií PUSH-PULL a PUBLISH-SUBSCRIBE
5. Jednoduchý message broker implementující strategii PUSH-PULL
6. Otestování činnosti message brokera
7. Chování brokera při pozdějším spuštění konzumenta zpráv
8. Vylepšení příkladu: použití session se všemi informacemi o připojení k Redisu
9. Komunikační strategie PUBLISH-SUBSCRIBE
11. Implementace příjemce zpráv
12. Úplný zdrojový kód příkladu se zdrojem i příjemcem zpráv
13. Chování systému ve chvíli, kdy je připojeno větší množství příjemců
14. Příjemce používající kanál jazyka Go
15. Využití pipeline nabízené Redisem
16. Zdrojový kód příkladu používajícího pipeline
17. Skripty naprogramované v jazyku Lua a spouštěné na serveru
18. Příklad jednoduchého skriptu spouštěného na straně serveru
19. Repositář s demonstračními příklady
1. Posílání zpráv v aplikacích založených na mikroslužbách
„It's hard to get messaging right“
Programovací jazyk Go se používá v několika oblastech a jednou z těchto oblastí jsou i mikroslužby. Ty spolu musí vhodným způsobem komunikovat – a to s využitím nějakých zpráv, které typicky nebývají posílané přímo mezi jednotlivými mikroslužbami, ale využívá se nějaký centralizovaný či decentralizovaný message broker. Namísto specializovaného message brokeru si však mnohdy vystačíme i s jednodušším a přitom dobře škálovatelným řešením založeným na Redisu. A právě z tohoto důvodu je kombinace Go+Redis velmi často využívaná v praxi. V úvodních kapitolách si připomeneme, jaké dva typy komunikace se používá, ovšem s tím, že třetí typ (QUERY) není zmíněn, protože pro něj nepotřebujeme použít Redis. Ovšem pro komunikace typu PUSH-PULL a PUBLISH-SUBSCRIBE je Redis v mnoha případech poměrně dobrým řešením.
Obrázek 1: Příklad aplikace používající architekturu kappa. Teoreticky je možné i zde použít Redis, ovšem častěji se setkáme se specializovaným řešením založeným na projektu Apache Kafka (méně často s NATS).
V souvislosti se zprávami i s celým návrhem architektury aplikace založené na mikroslužbách se často setkáme s termíny CQS a CQRS. Termín CQS znamená Command–query separation a CQRS je zkratkou vzniklou z Command-query responsibility segregation (nebo separation). CQR se používá při vývoji a znamená takový návrh aplikace (typicky založené na OOP, ale není to nutné), kdy každá funkce či metoda provádí buď nějaký příkaz (command) nebo slouží k získání dat (query); žádná z metod by neměla provádět obě akce. Zatímco se metodika CQR typicky aplikuje na jednotlivé třídy, tedy na relativně malou a izolovanou část aplikace, je druhá z metodik CQRS aplikována na celou architekturu služeb a mikroslužeb, přičemž command je ta část modelu aplikace, která slouží ke změně stavu a query druhá část modelu používaná pro agregaci dat.
2. Zpráva požadující provedení úlohy: COMMAND
„Microservice – “small autonomous services modelled around business domain that work together““
Sam Newman, jeden z původních autorů myšlenky mikroslužeb
Zprávy typu COMMAND obecně slouží ke změně stavu aplikace a obecně tedy provádí nějaký side-effect, typicky modifikaci dat (kromě odpovědi se změní i nějaká další část aplikace, například se zapíše záznam do databáze atd.). Můžeme se ale setkat i s takovými zprávami typu COMMAND, které stav aplikace nezmění. Poměrně dobrým příkladem může být žádost o poslání e-mailu uživateli – zde se tedy mění spíše stav okolního systému (mailboxu příjemce).
Zprávy typu COMMAND jsou typické tím, že většinou existuje pouze jediná komponenta, která může daný příkaz provést. To, o kterou komponentu ve funkci příjemce zprávy se konkrétně jedná, však nemusí zdrojová komponenta (tj. komponenta, která příkaz posílá) řešit, resp. přesněji řečeno by to ani ve správně navržené aplikaci neměla řešit, protože by se jednalo o zbytečně těsné svázání obou komponent (mikroslužeb).
Většinou taktéž požadujeme, aby přijímající komponenta poslala odpověď na zprávu typu COMMAND. V naprosté většině případů se jedná o jednoduchou stavovou informaci typu OK/Not OK, popř. ACK/NACK, jen výjimečně s dalšími daty (například s ID vytvořeného požadavku); v případě, že by v odpovědi byla další data, jednalo by se pravděpodobně o porušení CQRS. Tento typ odpovědi může být poslán synchronně či asynchronně.
Existuje několik způsobů směrování (routing) používaných pro zprávy typu COMMAND. Typicky je možné rozhodnout přímo na základě příslušného příkazu, která komponenta má příkaz zpracovat (příklady příkazů: create_new_user, send_notification_email atd.). V tom nejjednodušším případě se příkaz ihned přepošle cílové komponentě, ovšem většinou se setkáme s využitím front zpráv (message queue), které slouží jak pro zajištění persistence zpráv v případě, že přijímající komponenta není spuštěna či pokud je přetížena, tak i případně pro load balancin. Použitou komunikační strategií je tedy strategie PUSH-PULL.
Obrázek 2: Komunikační strategie typu PUSH-PULL bez použití fronty.
3. Zpráva poslaná ve chvíli, kdy došlo k nějaké události – EVENT
Druhý typ zpráv nazvaný EVENT je prakticky libovolnými komponentami posílán ve chvíli, kdy dojde k určité události, o nichž chce komponenta informovat okolní systém. Může se jednat o prakticky libovolnou událost (tedy nikoli pouze o událost na GUI). Příkladem může být detekce změny některých dat, zjištění, že došlo k překročení nějakého časového limitu, informace o přetížení určitého uzlu v clusteru, informace o překročení nastaveného limitu databáze, opakované pokusy o přihlášení atd. Můžeme sem řadit i komponenty/mikroslužby typu cron, které slouží právě k posílání informací o naplánovaných událostech, ať již periodických (zaslání e-mailu se žádostí o změnu hesla, pravidelná kontrola jiné služby přes její API), tak i neperiodických (ad-hoc události). Typicky komponenta pouze oznámí, že došlo k nějaké události a neočekává žádné odpovědi. Z implementačního hlediska je posílání a zpracování těchto zpráv nejjednodušší.
Zprávy typu EVENT se většinou směrují odlišným způsobem, než zprávy typu COMMAND. Je tomu tak z toho důvodu, že na události může reagovat obecně větší množství komponent, nikoli jediný typ komponenty. Z tohoto důvodu se používá komunikační strategie PUBLISH-SUBSCRIBE neboli PUB-SUB, která je podporována většinou message brokerů. Ovšem můžeme se setkat i s dalšími konfiguracemi, například s takzvanými soupeřícími konzumenty (competing consumers) nebo s balanced consumers.
Obrázek 3: Komunikační strategie typu PUBLISH-SUBSCRIBE používaná při vzniku událostí.
4. Implementace komunikačních strategií PUSH-PULL a PUBLISH-SUBSCRIBE
Obě výše zmíněné komunikační strategie lze v Go (ale samozřejmě i v dalších jazycích) relativně snadno implementovat s využitím Redisu, přičemž se využijí jak jeho základní podporované datové typy (zejména seznamy), tak i možnosti poskytované knihovnou pro jazyk Go, která nabízí realizaci komunikační strategie PubSub (přesněji PUBLISH-SUBSCRIBE) takovým způsobem, že lze snadno využít kanály. Dále uvedené demonstrační příklady jsou vždy realizovány jediným zdrojovým kódem, v němž je naprogramován jak producent (producer) či zdroj (publisher) zpráv, tak i jejich konzument (consumer) či příjemce (subscriber). V praxi jsou však tyto role většinou odděleny, což lze v případě dále popsaných demonstračních příkladů realizovat poměrně snadným způsobem.
5. Jednoduchý message broker implementující strategii PUSH-PULL
Již v závěru předchozího článku jsme si ukázali funkce naprogramované v jazyku Go, které je možné využít při implementaci jednoduchého message brokera s frontou, který implementuje komunikační strategii PUSH-PULL: producenti zpráv používají operaci typu PUSH, konzumenti pak typu PULL. Jak producenta tak i konzumenta zpráv lze realizovat jediným programem, ovšem (pochopitelně) s tím, že producent poběží v jiné gorutině než konzument:
// spustíme producenta zpráv go producer(client, context, queueName, 0, 10) timeout, err := time.ParseDuration("10s") if err != nil { panic(err) } // nyní již můžeme spustit konzumenta zpráv consumer(client, context, queueName, timeout)
Takový systém bude funkční a jeho úplný programový kód je zobrazen pod tímto odstavcem:
package main import ( "context" "fmt" "time" "github.com/go-redis/redis/v8" ) // adresa určující službu Redisu, která se má použít const redisAddress = "localhost:6379" // jméno hodnoty použité pro implementaci jednoduché fronty const queueName = "fronta" // printQueueLength vypíše aktuální délku fronty, samotná délka je přitom // získána jiným způsobem (vložením prvku, použitím LLen atd.) func printQueueLength(length int64) { fmt.Printf("Queue length after enqueuing is %d\n", length) } // mustEnqueue zajistí vložení prvku do fronty, popř. pád aplikace v případě, // kdy vložení není možné provést (Redis je odpojen atd.) func mustEnqueueInteger(client *redis.Client, context context.Context, key string, value int) { fmt.Printf("Enqueuing %d into queue named '%s'\n", value, key) // přidání prvku do seznamu length, err := client.LPush(context, key, value).Result() if err != nil { panic(err) } printQueueLength(length) } func producer(client *redis.Client, context context.Context, key string, from int, to int) { // postupné vložení prvků do fronty for i := from; i < to; i++ { mustEnqueueInteger(client, context, queueName, i) time.Sleep(1 * time.Second) } } func consumer(client *redis.Client, context context.Context, key string, timeout time.Duration) { // přečtení všech hodnot z fronty for { // pokus o přečtení hodnoty z fronty keyValue, err := client.BRPop(context, timeout, queueName).Result() // vyhodnocení předchozí operace switch { case err == redis.Nil: fmt.Println("no value found") return case err != nil: panic(err) default: key := keyValue[0] value := keyValue[1] fmt.Printf( "Value dequed from queue named '%s': '%s'\n", key, value) } length := client.LLen(context, queueName).Val() printQueueLength(length) fmt.Println() } } // vstupní bod do demonstračního příkladu func main() { // vytvoření nového klienta s předáním konfiguračních parametrů client := redis.NewClient(&redis.Options{ Addr: redisAddress, Password: "", // no password set DB: 0, // use default DB }) // neměli bychom zapomenout na ukončení práce s klientem defer func() { err := client.Close() if err != nil { panic(err) } }() // získáme kontext context := client.Context() // pokus o klasický handshake typu PING-PONG _, err := client.Ping(context).Result() if err != nil { panic(err) } // smazání seznamu, pokud existoval client.Del(context, queueName) // spustíme producenta zpráv go producer(client, context, queueName, 0, 10) timeout, err := time.ParseDuration("10s") if err != nil { panic(err) } // nyní již můžeme spustit konzumenta zpráv consumer(client, context, queueName, timeout) }
6. Otestování činnosti message brokera
Po překladu zdrojového kódu a spuštění procesu by se měly objevit zprávy, které ukazují, že konzument začíná zprávy číst ihned poté, co jsou připraveny producentem:
Enqueuing 0 into queue named 'fronta' Value dequed from queue named 'fronta': '0' Queue length after enqueuing is 1 Queue length after enqueuing is 0 Enqueuing 1 into queue named 'fronta' Queue length after enqueuing is 1 Value dequed from queue named 'fronta': '1' Queue length after enqueuing is 0 Enqueuing 2 into queue named 'fronta' Value dequed from queue named 'fronta': '2' Queue length after enqueuing is 1 Queue length after enqueuing is 0 Enqueuing 3 into queue named 'fronta' Queue length after enqueuing is 1 Value dequed from queue named 'fronta': '3' Queue length after enqueuing is 0 Enqueuing 4 into queue named 'fronta' Queue length after enqueuing is 1 Value dequed from queue named 'fronta': '4' Queue length after enqueuing is 0 Enqueuing 5 into queue named 'fronta' Queue length after enqueuing is 1 Value dequed from queue named 'fronta': '5' Queue length after enqueuing is 0 Enqueuing 6 into queue named 'fronta' Queue length after enqueuing is 1 Value dequed from queue named 'fronta': '6' Queue length after enqueuing is 0 Enqueuing 7 into queue named 'fronta' Queue length after enqueuing is 1 Value dequed from queue named 'fronta': '7' Queue length after enqueuing is 0 Enqueuing 8 into queue named 'fronta' Queue length after enqueuing is 1 Value dequed from queue named 'fronta': '8' Queue length after enqueuing is 0 Enqueuing 9 into queue named 'fronta' Queue length after enqueuing is 1 Value dequed from queue named 'fronta': '9' Queue length after enqueuing is 0 no value found
7. Chování brokera při pozdějším spuštění konzumenta zpráv
Taktéž si můžeme otestovat chování brokera ve chvíli, kdy je konzument zpráv spuštěn později (což je ostatně v praxi velmi častý příklad). Upravíme tedy konec předchozího demonstračního příkladu vložením time.Sleep, aby konzument začal zpracovávat zprávy přibližně až po sedmi sekundách a nikoli okamžitě:
// spustíme producenta zpráv go producer(client, context, queueName, 0, 10) timeout, err := time.ParseDuration("10s") if err != nil { panic(err) } // konzument (jeho start) bude zpomalen time.Sleep(7 * time.Second) // nyní již můžeme spustit konzumenta zpráv consumer(client, context, queueName, timeout)
Výsledek nyní bude podle očekávání vypadat odlišně, protože se nejdříve začnou zprávy hromadit ve frontě, poté je konzument rychle zpracuje a následně bude čekat na zbývající tři zprávy:
Enqueuing 0 into queue named 'fronta' Queue length after enqueuing is 1 Enqueuing 1 into queue named 'fronta' Queue length after enqueuing is 2 Enqueuing 2 into queue named 'fronta' Queue length after enqueuing is 3 Enqueuing 3 into queue named 'fronta' Queue length after enqueuing is 4 Enqueuing 4 into queue named 'fronta' Queue length after enqueuing is 5 Enqueuing 5 into queue named 'fronta' Queue length after enqueuing is 6 Enqueuing 6 into queue named 'fronta' Queue length after enqueuing is 7 Value dequed from queue named 'fronta': '0' Queue length after enqueuing is 6 Value dequed from queue named 'fronta': '1' Queue length after enqueuing is 5 Value dequed from queue named 'fronta': '2' Queue length after enqueuing is 4 Value dequed from queue named 'fronta': '3' Queue length after enqueuing is 3 Value dequed from queue named 'fronta': '4' Queue length after enqueuing is 2 Value dequed from queue named 'fronta': '5' Queue length after enqueuing is 1 Value dequed from queue named 'fronta': '6' Queue length after enqueuing is 0 Enqueuing 7 into queue named 'fronta' Value dequed from queue named 'fronta': '7' Queue length after enqueuing is 1 Queue length after enqueuing is 0 Enqueuing 8 into queue named 'fronta' Queue length after enqueuing is 1 Value dequed from queue named 'fronta': '8' Queue length after enqueuing is 0 Enqueuing 9 into queue named 'fronta' Value dequed from queue named 'fronta': '9' Queue length after enqueuing is 1 Queue length after enqueuing is 0 no value found
8. Vylepšení příkladu: použití session se všemi informacemi o připojení k Redisu
Při vývoji aplikací v Go nebývá zvykem předávat do funkcí či metod příliš velké množství parametrů (ostatně pravidla formátování jsou v tomto ohledu poněkud nepříjemná, takže zápis parametrů pod sebe je nepřehledný), takže se namísto toho příbuzné parametry předávají v jediné datové struktuře, popř. v odkazu na strukturu. Toto řešení můžeme použít i při úpravě předchozího příkladu vytvořením struktury nesoucí informace o „sezení“ Redisu, tedy s odkazem na klienta a současně i na kontext:
type redisSession struct { client *redis.Client context context.Context }
Zdrojový kód demonstračního příkladu po úpravě bude vypadat následovně:
package main import ( "context" "fmt" "time" "github.com/go-redis/redis/v8" ) // adresa určující službu Redisu, která se má použít const redisAddress = "localhost:6379" // jméno hodnoty použité pro implementaci jednoduché fronty const queueName = "fronta" type redisSession struct { client *redis.Client context context.Context } // printQueueLength vypíše aktuální délku fronty, samotná délka je přitom // získána jiným způsobem (vložením prvku, použitím LLen atd.) func printQueueLength(length int64) { fmt.Printf("Queue length after enqueuing is %d\n", length) } // mustEnqueue zajistí vložení prvku do fronty, popř. pád aplikace v případě, // kdy vložení není možné provést (Redis je odpojen atd.) func mustEnqueueInteger(session redisSession, key string, value int) { fmt.Printf("Enqueuing %d into queue named '%s'\n", value, key) // přidání prvku do seznamu length, err := session.client.LPush( session.context, key, value).Result() if err != nil { panic(err) } printQueueLength(length) } func producer(session redisSession, key string, from int, to int) { // postupné vložení prvků do fronty for i := from; i < to; i++ { mustEnqueueInteger(session, queueName, i) time.Sleep(1 * time.Second) } } func consumer(session redisSession, key string, timeout time.Duration) { // přečtení všech hodnot z fronty for { // pokus o přečtení hodnoty z fronty keyValue, err := session.client.BRPop( session.context, timeout, queueName).Result() // vyhodnocení předchozí operace switch { case err == redis.Nil: fmt.Println("no value found") return case err != nil: panic(err) default: key := keyValue[0] value := keyValue[1] fmt.Printf( "Value dequed from queue named '%s': '%s'\n", key, value) } length := session.client.LLen(session.context, queueName).Val() printQueueLength(length) fmt.Println() } } // vstupní bod do demonstračního příkladu func main() { // vytvoření nového klienta s předáním konfiguračních parametrů client := redis.NewClient(&redis.Options{ Addr: redisAddress, Password: "", // no password set DB: 0, // use default DB }) // neměli bychom zapomenout na ukončení práce s klientem defer func() { err := client.Close() if err != nil { panic(err) } }() // získáme kontext context := client.Context() session := redisSession{ client: client, context: context, } // pokus o klasický handshake typu PING-PONG _, err := client.Ping(context).Result() if err != nil { panic(err) } // smazání seznamu, pokud existoval client.Del(context, queueName) // spustíme producenta zpráv go producer(session, queueName, 0, 10) // je možné pustit více producentů // go producer(session, queueName, 11, 20) timeout, err := time.ParseDuration("10s") if err != nil { panic(err) } // nyní již můžeme spustit konzumenta zpráv consumer(session, queueName, timeout) }
9. Komunikační strategie PUBLISH-SUBSCRIBE
Komunikační strategie PUBLISH-SUBSCRIBE se v několika ohledech odlišuje od strategie PUSH-PULL. Ve strategii PUSH-PULL jednu zprávu přijme a zpracuje pouze jeden příjemce, zatímco v PUBLISH-SUBSCRIBE to může být libovolné množství příjemců. Navíc se v PUSH-PULL používá fronta, takže pokud není žádný příjemce připojen, je zpráva (mezi)uložena do fronty. V PUBLISH-SUBSCRIBE je tomu jinak – zpráva je dodána aktuálně připojeným příjemcům a pokud takoví neexistují, není vůbec předána. Zprávy jsou navíc posílány do takzvaného kanálu, který je obdobou fronty ve strategii PUSH-PULL. Redis a knihovna s rozhraním pro Go navíc umožňují, aby příjemce byl připojen k více kanálům, resp. podle správné terminologie aby byl odběratelem zpráv z většího množství kanálů. Tato komunikační strategie je tedy určena pro oddělení zdroje zpráv od příjemce, což je v mnoha případech velmi dobré řešení.
10. Implementace zdroje zpráv
Vzhledem k tomu, že samotný Redis podporuje operace PUBLISH i SUBSCRIBE, je implementace zdroje zpráv, tedy procesu nebo gorutiny, která zprávy posílá, jednoduchá – postačuje použít metodu Publish, které se kromě kontextu musí předat i jméno kanálu (řetězec) a vlastní zpráva (hodnota prakticky jakéhokoli datového typu). Pro otestování, zda se publikování zprávy podařilo, postačuje zavolat metodu Err objektu/struktury IntCmd. Implementace je tedy jen několikařádková:
// zdroj zpráv func publisher(session redisSession, channel string, from int, to int) { for i := from; i < to; i++ { err := session.client.Publish(session.context, channel, i).Err() if err != nil { panic(err) } time.Sleep(1 * time.Second) } }
11. Implementace příjemce zpráv
Příjemce zpráv neboli subscriber je podobně jednoduchý, neboť zaregistrování a příjem zpráv je opět plně podporováno samotným Redisem. Nejdříve je nutné se zaregistrovat k příjmu zpráv z určitého kanálu (metoda Subscribe) a následně zprávy přijímat, například v nekonečné smyčce, metodou ReceiveMessage. Vrátí se dvě hodnoty – samotná zpráva (data) a informace o chybě:
// příjemce zpráv func subscriber(session redisSession, channel string) { pubsub := session.client.Subscribe(session.context, channel) for { message, err := pubsub.ReceiveMessage(session.context) if err != nil { panic(err) } fmt.Printf("Channel: %s Message: '%s'\n", message.Channel, message.Payload) } }
12. Úplný zdrojový kód příkladu se zdrojem i příjemcem zpráv
Pro úplnost si ukažme úplný zdrojový kód příkladu, který používá zdroje i příjemce zpráv popsané v kapitole 10 a taktéž v kapitole 11. Příjemce pochopitelně musí běžet v jiném vláknu, než zdroj zpráv:
package main import ( "context" "fmt" "time" "github.com/go-redis/redis/v8" ) // adresa určující službu Redisu, která se má použít const redisAddress = "localhost:6379" // jméno kanálu const channelName = "c1" // struktura obsahující "session" type redisSession struct { client *redis.Client context context.Context } // zdroj zpráv func publisher(session redisSession, channel string, from int, to int) { for i := from; i < to; i++ { err := session.client.Publish(session.context, channel, i).Err() if err != nil { panic(err) } time.Sleep(1 * time.Second) } } // příjemce zpráv func subscriber(session redisSession, channel string) { pubsub := session.client.Subscribe(session.context, channel) for { message, err := pubsub.ReceiveMessage(session.context) if err != nil { panic(err) } fmt.Printf("Channel: %s Message: '%s'\n", message.Channel, message.Payload) } } // vstupní bod do demonstračního příkladu func main() { // vytvoření nového klienta s předáním konfiguračních parametrů client := redis.NewClient(&redis.Options{ Addr: redisAddress, Password: "", // no password set DB: 0, // use default DB }) // neměli bychom zapomenout na ukončení práce s klientem defer func() { err := client.Close() if err != nil { panic(err) } }() // získáme kontext context := client.Context() // vytvoříme session session := redisSession{ client: client, context: context, } // pokus o klasický handshake typu PING-PONG _, err := client.Ping(context).Result() if err != nil { panic(err) } // smazání kanálu, pokud existoval client.Del(context, channelName) // spustíme zdroj zpráv go publisher(session, channelName, 0, 10) // nyní můžeme spustit příjemce zpráv subscriber(session, channelName) }
13. Chování systému ve chvíli, kdy je připojeno větší množství příjemců
Příklad si můžeme nepatrně upravit takovým způsobem, aby se spustilo větší množství příjemců zpráv, pochopitelně každý ve své vlastní gorutině. Upravit ve skutečnosti postačuje jen posledních pět řádků funkce main, a to takto:
// nyní můžeme spustit několik příjemců zpráv go subscriber(session, channelName) go subscriber(session, channelName) go subscriber(session, channelName) // spustíme zdroj zpráv publisher(session, channelName, 0, 10)
V praxi to znamená, že na stejném kanálu bude naslouchat několik příjemců a každý z těchto příjemců získá stejné zprávy – ostatně právě proto se jedná o komunikační strategii typu PUBLISH-SUBSCRIBE. Zdroj zpráv (či více zdrojů) přitom nemá žádnou přímou vazbu na příjemce, takže je zajištěno oddělení (decoupling) těchto modulů. Po spuštění příkladu by se stejná zpráva (představovaná pro jednoduchost celým číslem) měla přeposlat všem třem příjemcům:
Channel: c1 Message: '1' Channel: c1 Message: '1' Channel: c1 Message: '1' Channel: c1 Message: '2' Channel: c1 Message: '2' Channel: c1 Message: '2' Channel: c1 Message: '3' Channel: c1 Message: '3' Channel: c1 Message: '3' Channel: c1 Message: '4' Channel: c1 Message: '4' Channel: c1 Message: '4' Channel: c1 Message: '5' Channel: c1 Message: '5' Channel: c1 Message: '5' Channel: c1 Message: '6' Channel: c1 Message: '6' Channel: c1 Message: '6' Channel: c1 Message: '7' Channel: c1 Message: '7' Channel: c1 Message: '7' Channel: c1 Message: '8' Channel: c1 Message: '8' Channel: c1 Message: '8' Channel: c1 Message: '9' Channel: c1 Message: '9' Channel: c1 Message: '9'
Upravený demonstrační příklad vypadá takto:
package main import ( "context" "fmt" "time" "github.com/go-redis/redis/v8" ) // adresa určující službu Redisu, která se má použít const redisAddress = "localhost:6379" // jméno kanálu const channelName = "c1" // struktura obsahující "session" type redisSession struct { client *redis.Client context context.Context } // zdroj zpráv func publisher(session redisSession, channel string, from int, to int) { for i := from; i < to; i++ { err := session.client.Publish(session.context, channel, i).Err() if err != nil { panic(err) } time.Sleep(1 * time.Second) } } // příjemce zpráv func subscriber(session redisSession, channel string) { pubsub := session.client.Subscribe(session.context, channel) for { message, err := pubsub.ReceiveMessage(session.context) if err != nil { panic(err) } fmt.Printf("Channel: %s Message: '%s'\n", message.Channel, message.Payload) } } // vstupní bod do demonstračního příkladu func main() { // vytvoření nového klienta s předáním konfiguračních parametrů client := redis.NewClient(&redis.Options{ Addr: redisAddress, Password: "", // no password set DB: 0, // use default DB }) // neměli bychom zapomenout na ukončení práce s klientem defer func() { err := client.Close() if err != nil { panic(err) } }() // získáme kontext context := client.Context() // vytvoříme session session := redisSession{ client: client, context: context, } // pokus o klasický handshake typu PING-PONG _, err := client.Ping(context).Result() if err != nil { panic(err) } // smazání kanálu, pokud existoval client.Del(context, channelName) // nyní můžeme spustit několik příjemců zpráv go subscriber(session, channelName) go subscriber(session, channelName) go subscriber(session, channelName) // spustíme zdroj zpráv publisher(session, channelName, 0, 10) }
14. Příjemce používající kanál jazyka Go
Předchozí implementace příjemce s využitím nekonečné smyčky, v níž se vždy čeká na další zprávu, má jeden nepříjemný důsledek – bylo by poměrně složité zařídit, aby se zprávy četly z většího množství kanálů Redisu, což je ovšem na druhou stranu poměrně běžný požadavek. Řešení tohoto problému existuje a spočívá ve využití kanálů programovacího jazyka Go (což je něco zcela jiného než pojmenovaný kanál Redisu). Příjemce lze upravit takto:
// příjemce zpráv func subscriber(session redisSession, channel string) { pubsub := session.client.Subscribe(session.context, channel) ch := pubsub.Channel() for message := range ch { fmt.Printf("Channel: %s Message: '%s'\n", message.Channel, message.Payload) } }
Koncepce kanálů v jazyku Go je velmi mocná, protože nám umožňuje například čekat na zprávu, která může dojít do libovolného kanálu:
select { case <-ch1: fmt.Println("Data z kanálu 1") case <-ch2: fmt.Println("Data z kanálu 2") }
Tuto programovou konstrukci můžeme rozšířit o větev vyvolanou ve chvíli, kdy žádné zprávy nejsou v určitém časovém úseku přijaty:
select { case <-ch1: fmt.Println("Data z kanálu 1") case <-ch2: fmt.Println("Data z kanálu 2") case <-time.After(10 * time.Second): fmt.Println("Timeout!") }
Příklad s upravenou funkcí příjemce může vypadat následovně (ostatní kód se nijak nezměnil):
package main import ( "context" "fmt" "time" "github.com/go-redis/redis/v8" ) // adresa určující službu Redisu, která se má použít const redisAddress = "localhost:6379" // jméno kanálu const channelName = "c1" // struktura obsahující "session" type redisSession struct { client *redis.Client context context.Context } // zdroj zpráv func publisher(session redisSession, channel string, from int, to int) { for i := from; i < to; i++ { err := session.client.Publish(session.context, channel, i).Err() if err != nil { panic(err) } time.Sleep(1 * time.Second) } } // příjemce zpráv func subscriber(session redisSession, channel string) { pubsub := session.client.Subscribe(session.context, channel) ch := pubsub.Channel() for message := range ch { fmt.Printf("Channel: %s Message: '%s'\n", message.Channel, message.Payload) } } // vstupní bod do demonstračního příkladu func main() { // vytvoření nového klienta s předáním konfiguračních parametrů client := redis.NewClient(&redis.Options{ Addr: redisAddress, Password: "", // no password set DB: 0, // use default DB }) // neměli bychom zapomenout na ukončení práce s klientem defer func() { err := client.Close() if err != nil { panic(err) } }() // získáme kontext context := client.Context() // vytvoříme session session := redisSession{ client: client, context: context, } // pokus o klasický handshake typu PING-PONG _, err := client.Ping(context).Result() if err != nil { panic(err) } // smazání kanálu, pokud existoval client.Del(context, channelName) // nyní můžeme spustit několik příjemců zpráv go subscriber(session, channelName) go subscriber(session, channelName) go subscriber(session, channelName) // spustíme zdroj zpráv publisher(session, channelName, 0, 10) }
15. Využití pipeline nabízené Redisem
V Redisu je možné využít i takzvané pipeline, což jsou vlastně sekvence příkazů zpracované sekvenčně v takovém pořadí, v jakém jsou specifikovány. Nejedná se však o transakce a příkazy provedené v rámci pipeline ani nesplňují kritéria ACID. Samotné příkazy vkládané do pipeline se vytváří pomocí rozhraní Pipeliner, jehož předpis vypadá následovně:
type Pipeliner interface { StatefulCmdable Do(ctx context.Context, args ...interface{}) *Cmd Process(ctx context.Context, cmd Cmder) error Close() error Discard() error Exec(ctx context.Context) ([]Cmder, error) }
Příklad deklarace nové pipeline:
_, err = client.Pipelined(context, func(pipe redis.Pipeliner) error { ... ... ... return nil }) if err != nil { panic(err) }
V pipeline, která je reprezentována anonymní funkcí, lze pochopitelně používat i proměnné deklarované vně funkce a viditelné uvnitř jejího těla (Go podporuje uzávěry). Proto můžeme vytvořit pipeline se třemi příkazy, které ovlivní obsah vázaných proměnných counter1, counter2 a accumulator:
var counter1 *redis.IntCmd var counter2 *redis.IntCmd var accumulator *redis.FloatCmd _, err = client.Pipelined(context, func(pipe redis.Pipeliner) error { counter1 = pipe.Incr(context, "counter1") counter2 = pipe.Decr(context, "counter2") accumulator = pipe.IncrByFloat(context, "accumulator", 3.14) return nil }) if err != nil { panic(err) }
16. Zdrojový kód příkladu používajícího pipeline
V následujícím (dnes již předposledním) demonstračním příkladu je celá pipeline zavolána pětkrát za sebou, pokaždé s následným výpisem obsahu obou čítačů i akumulátoru:
package main import ( "fmt" "github.com/go-redis/redis/v8" ) // adresa určující službu Redisu, která se má použít const redisAddress = "localhost:6379" func main() { // vytvoření nového klienta s předáním konfiguračních parametrů client := redis.NewClient(&redis.Options{ Addr: redisAddress, Password: "", // no password set DB: 0, // use default DB }) // neměli bychom zapomenout na ukončení práce s klientem defer func() { err := client.Close() if err != nil { panic(err) } }() // získáme kontext context := client.Context() // pokus o klasický handshake typu PING-PONG _, err := client.Ping(context).Result() if err != nil { panic(err) } // smazání hodnoty, pokud existovala client.Del(context, "counter1") client.Del(context, "counter2") client.Del(context, "accumulator") var counter1 *redis.IntCmd var counter2 *redis.IntCmd var accumulator *redis.FloatCmd for i := 0; i < 5; i++ { _, err = client.Pipelined(context, func(pipe redis.Pipeliner) error { counter1 = pipe.Incr(context, "counter1") counter2 = pipe.Decr(context, "counter2") accumulator = pipe.IncrByFloat(context, "accumulator", 3.14) return nil }) if err != nil { panic(err) } fmt.Printf("1st counter: %d\n", counter1.Val()) fmt.Printf("2nd counter: %d\n", counter2.Val()) fmt.Printf("accumulator: %f\n\n", accumulator.Val()) } }
17. Skripty naprogramované v jazyku Lua a spouštěné na serveru
Jednou z poměrně velkých předností Redisu je fakt, že od verze 2.6 je možné dokonce využít i podporu pro přímé spouštění příkazů Redisu ze skriptů napsaných v programovacím jazyce Lua, který se skutečně velmi dobře hodí pro skriptování aplikací. Důležité přitom je, že skripty lze nahrát přímo do serveru Redisu a spustit je až ve chvíli, kdy jsou skutečně zapotřebí. Pro často prováděné operace tak může dojít k poměrně významnému ušetření přenosové kapacity, nehledě na to, že skripty psané v jazyku Lua mohou pro aplikace představovat rozhraní, které aplikace odstíní od nízkoúrovňového přístupu Redisu. Tímto způsobem by například bylo možné realizovat operace s frontou, prioritní frontou či specializovanější operace prováděné v transakci (lze zařídit i ACID).
18. Příklad jednoduchého skriptu spouštěného na straně serveru
Podívejme se nyní na jednoduchý skript naprogramovaný v jazyku Lua. Tento skript byl získán přímo z dokumentace k rozhraní k Redisu a vrací výsledek zvýšení nějakého čítače (hodnoty uložené pod klíčem) o zadaný offset:
if redis.call("GET", KEYS[1]) ~= false then return redis.call("INCRBY", KEYS[1], ARGV[1]) end return false
Skript načteme běžnými funkcemi ze standardní knihovny jazyka Go:
func mustLoadScriptSource(filename string) string { bytes, err := ioutil.ReadFile(filename) if err != nil { panic(err) } return string(bytes) } scriptSource := mustLoadScriptSource("script.lua")
Převedeme zdrojový kód na objekt reprezentující spustitelný skript:
script := redis.NewScript(scriptSource)
A použijeme ho:
n, err := script.Run(context, client, []string{counterKey}, 2).Result() fmt.Println(n, err)
Úplný zdrojový kód takto vytvořeného demonstračního příkladu vypadá následovně:
package main import ( "fmt" "io/ioutil" "github.com/go-redis/redis/v8" ) // adresa určující službu Redisu, která se má použít const redisAddress = "localhost:6379" const counterKey = "counter2" func mustLoadScriptSource(filename string) string { bytes, err := ioutil.ReadFile(filename) if err != nil { panic(err) } return string(bytes) } func main() { // vytvoření nového klienta s předáním konfiguračních parametrů client := redis.NewClient(&redis.Options{ Addr: redisAddress, Password: "", // no password set DB: 0, // use default DB }) // neměli bychom zapomenout na ukončení práce s klientem defer func() { err := client.Close() if err != nil { panic(err) } }() // získáme kontext context := client.Context() // pokus o klasický handshake typu PING-PONG _, err := client.Ping(context).Result() if err != nil { panic(err) } scriptSource := mustLoadScriptSource("script.lua") fmt.Printf("Loaded script:\n%s\n\n", scriptSource) script := redis.NewScript(scriptSource) n, err := script.Run(context, client, []string{counterKey}, 2).Result() fmt.Println(n, err) err = client.Set(context, counterKey, "40", 0).Err() if err != nil { panic(err) } n, err = script.Run(context, client, []string{counterKey}, 2).Result() fmt.Println(n, err) }
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do nového Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (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á přibližně stovku kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Repositář projektu s Redis klientem pro jazyk Go
https://github.com/go-redis/redis - Type-safe Redis client for Go
https://redis.uptrace.dev/ - Dokumentace k balíčku redis
https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc - godis – redis client implement by golang, inspired by jedis.
https://github.com/piaohao/godis - How to Use Redis Go Client go-redis/redis with GoLang
https://kb.objectrocket.com/redis/how-to-use-redis-go-client-go-redis-redis-with-golang-592 - Adventures in message queues
http://antirez.com/news/88 - redeo
https://github.com/bsm/redeo - First-in, first-out queues
https://redislabs.com/ebook/part-2-core-concepts/chapter-6-application-components-in-redis/6–4-task-queues/6–4–1-first-in-first-out-queues/ - 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/ - 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 - Go Data Structures: Binary Search Tree
https://flaviocopes.com/golang-data-structure-binary-search-tree/ - Gobs of data
https://blog.golang.org/gobs-of-data - Formát BSON
http://bsonspec.org/ - Golang Guide: A List of Top Golang Frameworks, IDEs & Tools
https://blog.intelligentbee.com/2017/08/14/golang-guide-list-top-golang-frameworks-ides-tools/ - Tvorba univerzálních projevů
http://www.kyblsoft.cz/projevy - Repositář projektu Gift
https://github.com/disintegration/gift - Dokumentace k projektu Gift
https://godoc.org/github.com/disintegration/gift - Online x86 / x64 Assembler and Disassembler
https://defuse.ca/online-x86-assembler.htm#disassembly2 - The Design of the Go Assembler
https://talks.golang.org/2016/asm.slide#1 - A Quick Guide to Go's Assembler
https://golang.org/doc/asm - AssemblyPolicy
https://github.com/golang/go/wiki/AssemblyPolicy - Geohash in Golang Assembly
https://mmcloughlin.com/posts/geohash-assembly - Command objdump
https://golang.org/cmd/objdump/ - Assembly
https://goroutines.com/asm - Go & Assembly
http://www.doxsey.net/blog/go-and-assembly - A Foray Into Go Assembly Programming
https://blog.sgmansfield.com/2017/04/a-foray-into-go-assembly-programming/ - Golang Capturing log.Println And fmt.Println Output
https://medium.com/@hau12a1/golang-capturing-log-println-and-fmt-println-output-770209c791b4 - Stránka projektu plotly
https://plot.ly/ - Plotly JavaScript Open Source Graphing Library
https://plot.ly/javascript/ - Domain coloring
https://en.wikipedia.org/wiki/Domain_coloring - Michael Fogleman's projects
https://www.michaelfogleman.com/projects/tagged/graphics/ - Color Graphs of Complex Functions
https://web.archive.org/web/20120511021419/http://w.american.edu/cas/mathstat/lcrone/ComplexPlot.html - A Gallery of Complex Functions
http://wismuth.com/complex/gallery.html - package glot
https://godoc.org/github.com/Arafatk/glot - Gnuplotting: Output terminals
http://www.gnuplotting.org/output-terminals/ - Introducing Glot the plotting library for Golang
https://medium.com/@Arafat./introducing-glot-the-plotting-library-for-golang-3133399948a1 - Introducing Glot the plotting library for Golang
https://blog.gopheracademy.com/advent-2018/introducing-glot/ - Glot is a plotting library for Golang built on top of gnuplot
https://github.com/Arafatk/glot - Example plots (gonum/plot)
https://github.com/gonum/plot/wiki/Example-plots - A repository for plotting and visualizing data (gonum/plot)
https://github.com/gonum/plot - golang library to make https://chartjs.org/ plots
https://github.com/brentp/go-chartjs - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - The Gonum Numerical Computing Package
https://www.gonum.org/post/introtogonum/ - Gomacro na GitHubu
https://github.com/cosmos72/gomacro - gophernotes – Use Go in Jupyter notebooks and nteract
https://github.com/gopherdata/gophernotes - gonum
https://github.com/gonum - go-gota/gota – DataFrames and data wrangling in Go (Golang)
https://porter.io/github.com/go-gota/gota - A repository for plotting and visualizing data
https://github.com/gonum/plot - Gonum Numerical Packages
https://www.gonum.org/ - Stránky projektu MinIO
https://min.io/ - MinIO Quickstart Guide
https://docs.min.io/docs/minio-quickstart-guide.html - MinIO Go Client API Reference
https://docs.min.io/docs/golang-client-api-reference - MinIO Python Client API Reference
https://docs.min.io/docs/python-client-api-reference.html - Performance at Scale: MinIO Pushes Past 1.4 terabits per second with 256 NVMe Drives
https://blog.min.io/performance-at-scale-minio-pushes-past-1–3-terabits-per-second-with-256-nvme-drives/ - Benchmarking MinIO vs. AWS S3 for Apache Spark
https://blog.min.io/benchmarking-apache-spark-vs-aws-s3/ - MinIO Client Quickstart Guide
https://docs.min.io/docs/minio-client-quickstart-guide.html - Analýza kvality zdrojových kódů Minia
https://goreportcard.com/report/github.com/minio/minio - This is MinIO
https://www.youtube.com/watch?v=vF0lQh0XOCs - Running MinIO Standalone
https://www.youtube.com/watch?v=dIQsPCHvHoM - „Amazon S3 Compatible Storage in Kubernetes“ – Rob Girard, Principal Tech Marketing Engineer, Minio
https://www.youtube.com/watch?v=wlpn8K0jJ4U - Ginkgo
http://onsi.github.io/ginkgo/ - Gomega
https://onsi.github.io/gomega/ - Ginkgo's Preferred Matcher Library na GitHubu
https://github.com/onsi/gomega/ - Provided Matchers
http://onsi.github.io/gomega/#provided-matchers - Dokumentace k balíčku goexpect
https://godoc.org/github.com/google/goexpect - Balíček goexpect
https://github.com/google/goexpect - Balíček go-expect
https://github.com/Netflix/go-expect - Balíček gexpect
https://github.com/ThomasRooney/gexpect - Expect (originál naprogramovaný v TCL)
https://core.tcl-lang.org/expect/index - Expect (Wikipedia)
https://en.wikipedia.org/wiki/Expect - Pexpect
https://pexpect.readthedocs.io/en/stable/ - Golang SSH Client: Multiple Commands, Crypto & Goexpect Examples
http://networkbit.ch/golang-ssh-client/ - goblin na GitHubu
https://github.com/franela/goblin - Mocha framework
https://mochajs.org/ - frisby na GitHubu
https://github.com/verdverm/frisby - package frisby
https://godoc.org/github.com/verdverm/frisby - Frisby alternatives and similar packages (generováno)
https://go.libhunt.com/frisby-alternatives - Cucumber for golang
https://github.com/DATA-DOG/godog - How to Use Godog for Behavior-driven Development in Go
https://semaphoreci.com/community/tutorials/how-to-use-godog-for-behavior-driven-development-in-go - Comparative Analysis Of GoLang Testing Frameworks
https://www.slideshare.net/DushyantBhalgami/comparative-analysis-of-golang-testing-frameworks - A Quick Guide to Testing in Golang
https://caitiem.com/2016/08/18/a-quick-guide-to-testing-in-golang/ - Tom's Obvious, Minimal Language.
https://github.com/toml-lang/toml - xml.org
http://www.xml.org/ - Soubory .properties
https://en.wikipedia.org/wiki/.properties - Soubory INI
https://en.wikipedia.org/wiki/INI_file - JSON to YAML
https://www.json2yaml.com/ - Data Format Converter
https://toolkit.site/format.html - Viper na GitHubu
https://github.com/spf13/viper - GoDotEnv na GitHubu
https://github.com/joho/godotenv - The fantastic ORM library for Golang
http://gorm.io/ - Dokumentace k balíčku gorilla/mux
https://godoc.org/github.com/gorilla/mux - Gorilla web toolkitk
http://www.gorillatoolkit.org/ - Metric types
https://prometheus.io/docs/concepts/metric_types/ - Histograms with Prometheus: A Tale of Woe
http://linuxczar.net/blog/2017/06/15/prometheus-histogram-2/ - Why are Prometheus histograms cumulative?
https://www.robustperception.io/why-are-prometheus-histograms-cumulative - Histograms and summaries
https://prometheus.io/docs/practices/histograms/ - Instrumenting Golang server in 5 min
https://medium.com/@gsisimogang/instrumenting-golang-server-in-5-min-c1c32489add3 - Semantic Import Versioning in Go
https://www.aaronzhuo.com/semantic-import-versioning-in-go/ - Sémantické verzování
https://semver.org/ - Getting started with Go modules
https://medium.com/@fonseka.live/getting-started-with-go-modules-b3dac652066d - Create projects independent of $GOPATH using Go Modules
https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o - Anatomy of Modules in Go
https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16 - Modules
https://github.com/golang/go/wiki/Modules - Go Modules Tutorial
https://tutorialedge.net/golang/go-modules-tutorial/ - Module support
https://golang.org/cmd/go/#hdr-Module_support - Go Lang: Memory Management and Garbage Collection
https://vikash1976.wordpress.com/2017/03/26/go-lang-memory-management-and-garbage-collection/ - Golang Internals, Part 4: Object Files and Function Metadata
https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html - What is REPL?
https://pythonprogramminglanguage.com/repl/ - What is a REPL?
https://codewith.mu/en/tutorials/1.0/repl - Programming at the REPL: Introduction
https://clojure.org/guides/repl/introduction - What is REPL? (Quora)
https://www.quora.com/What-is-REPL - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - Read-eval-print loop (Wikipedia)
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - Vim as a Go (Golang) IDE using LSP and vim-go
https://octetz.com/posts/vim-as-go-ide - gopls
https://github.com/golang/go/wiki/gopls - IDE Integration Guide
https://github.com/stamblerre/gocode/blob/master/docs/IDE_integration.md - How to instrument Go code with custom expvar metrics
https://sysdig.com/blog/golang-expvar-custom-metrics/ - Golang expvar metricset (Metricbeat Reference)
https://www.elastic.co/guide/en/beats/metricbeat/7.x/metricbeat-metricset-golang-expvar.html - Package expvar
https://golang.org/pkg/expvar/#NewInt - Java Platform Debugger Architecture: Overview
https://docs.oracle.com/en/java/javase/11/docs/specs/jpda/jpda.html - The JVM Tool Interface (JVM TI): How VM Agents Work
https://www.oracle.com/technetwork/articles/javase/index-140680.html - JVM Tool Interface Version 11.0
https://docs.oracle.com/en/java/javase/11/docs/specs/jvmti.html - Creating a Debugging and Profiling Agent with JVMTI
http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html - JVM TI (Wikipedia)
http://en.wikipedia.org/wiki/JVM_TI - IBM JVMTI extensions
http://publib.boulder.ibm.com/infocenter/realtime/v2r0/index.jsp?topic=%2Fcom.ibm.softrt.doc%2Fdiag%2Ftools%2Fjvmti_extensions.html - Go & cgo: integrating existing C code with Go
http://akrennmair.github.io/golang-cgo-slides/#1 - Using cgo to call C code from within Go code
https://wenzr.wordpress.com/2018/06/07/using-cgo-to-call-c-code-from-within-go-code/ - Package trace
https://golang.org/pkg/runtime/trace/ - Introducing HTTP Tracing
https://blog.golang.org/http-tracing - Command trace
https://golang.org/cmd/trace/ - A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
https://github.com/wesovilabs/koazee - Funkce vyššího řádu v knihovně Underscore
https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/ - Delve: a debugger for the Go programming language.
https://github.com/go-delve/delve - Příkazy debuggeru Delve
https://github.com/go-delve/delve/tree/master/Documentation/cli - Debuggery a jejich nadstavby v Linuxu
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/ - Debuggery a jejich nadstavby v Linuxu (2. část)
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/ - Debuggery a jejich nadstavby v Linuxu (3): Nemiver
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/ - Debuggery a jejich nadstavby v Linuxu (4): KDbg
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/ - Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/ - Debugging Go Code with GDB
https://golang.org/doc/gdb - Debugging Go (golang) programs with gdb
https://thornydev.blogspot.com/2014/01/debugging-go-golang-programs-with-gdb.html - GDB – Dokumentace
http://sourceware.org/gdb/current/onlinedocs/gdb/ - GDB – Supported Languages
http://sourceware.org/gdb/current/onlinedocs/gdb/Supported-Languages.html#Supported-Languages - GNU Debugger (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Debugger - The LLDB Debugger
http://lldb.llvm.org/ - Debugger (Wikipedia)
https://en.wikipedia.org/wiki/Debugger - 13 Linux Debuggers for C++ Reviewed
http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817 - Go is on a Trajectory to Become the Next Enterprise Programming Language
https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e - Go Proverbs: Simple, Poetic, Pithy
https://go-proverbs.github.io/ - Handling Sparse Files on Linux
https://www.systutorials.com/136652/handling-sparse-files-on-linux/ - Gzip (Wikipedia)
https://en.wikipedia.org/wiki/Gzip - Deflate
https://en.wikipedia.org/wiki/DEFLATE - 10 tools written in Go that every developer needs to know
https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Hex dump
https://en.wikipedia.org/wiki/Hex_dump - Rozhraní io.ByteReader
https://golang.org/pkg/io/#ByteReader - Rozhraní io.RuneReader
https://golang.org/pkg/io/#RuneReader - Rozhraní io.ByteScanner
https://golang.org/pkg/io/#ByteScanner - Rozhraní io.RuneScanner
https://golang.org/pkg/io/#RuneScanner - Rozhraní io.Closer
https://golang.org/pkg/io/#Closer - Rozhraní io.Reader
https://golang.org/pkg/io/#Reader - Rozhraní io.Writer
https://golang.org/pkg/io/#Writer - Typ Strings.Reader
https://golang.org/pkg/strings/#Reader - VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Goroutine IDs
https://blog.sgmansfield.com/2015/12/goroutine-ids/ - Different ways to pass channels as arguments in function in go (golang)
https://stackoverflow.com/questions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang - justforfunc #22: using the Go execution tracer
https://www.youtube.com/watch?v=ySy3sR1LFCQ - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation - The zero value of a slice is not nil
https://stackoverflow.com/questions/30806931/the-zero-value-of-a-slice-is-not-nil - Go-tcha: When nil != nil
https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic - Nils in Go
https://www.doxsey.net/blog/nils-in-go