Hlavní navigace

Použití databáze Redis v aplikacích naprogramovaných v Go (2)

 Autor: Redis
V dalším článku o využití databáze Redis v aplikacích v Go si ukážeme některé pokročilejší vlastnosti Redisu. Zejména bude vysvětlena implementace základních komunikačních strategií, použití kanálů, pipeline a skriptů.
Pavel Tišnovský 25. 6. 2020
Doba čtení: 47 minut

Sdílet

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

20. Odkazy na Internetu

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í.

Poznámka: stále častěji se ovšem setkáme s tím, že se namísto klasické komunikační strategie PUBLISH-SUBSCRIBE použije technologie streamingu událostí implementovaná v již zmíněném NATS Streaming Serveru nebo v Apache Kafka. V takovém případě se záznam o události neztratí (pokud ho komponenty nestihnou přijmout), ale je uložen do rostoucího logu s neměnitelnými záznamy předchozích událostí. Toto řešení je pro některé typy problémů takřka ideální, ovšem u některých aplikací můžeme vidět i opačný problém – nasazování streamingu i tam, kde by neměl být použit, resp. tam, kde jeho použití pouze přináší větší nároky na systémové zdroje.

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)
}
Poznámka: chování konzumenta ani producenta by se nemělo nijak změnit.

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 PUBLISHSUBSCRIBE, 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)
        }
}
Poznámka: povšimněte si, že se jedná o neblokující operaci, tudíž se u ní ani neuvádí timeout.

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)
        }
}
Poznámka: tento způsob má jednu zásadní nevýhodu – umožňuje příjem zpráv z jednoho kanálu. Ovšem v dalších kapitolách si ukážeme řešení použitelné pro příjem zpráv z více kanálů.

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)
}
Poznámka: celý zdrojový kód tohoto příkladu je také dostupný na adrese https://github.com/tisnik/go-root/blob/master/article66/02_pub_sub/02_pub_­sub.go.

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!")
}
Poznámka: v praxi to znamená, že se celé schéma poskládání mikroslužeb může stát komplikovanější, protože jedna mikroslužba může odebírat zprávy z většího množství kanálů, nebo naopak může zprávy produkovat do více kanálů.

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:

MIF obecny

n, err := script.Run(context, client, []string{counterKey}, 2).Result()
fmt.Println(n, err)
Poznámka: povšimněte si způsobu předávání parametrů skriptu (pole řetězců, což odpovídá zpracování parametru).

Ú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:

# Příklad Stručný popis Cesta
1 01_session/01_session.go session (sezení) – vylepšení message brokera https://github.com/tisnik/go-root/blob/master/article66/01_ses­sion/01_session.go
2 02_pub_sub/02_pub_sub.go příklad implementace strategie PUBLISH-SUBSCRIBE https://github.com/tisnik/go-root/blob/master/article66/02_pub_sub/02_pub_­sub.go
3 03_pub_more_sub/03_pub_more_sub.go strategie PUBLISH-SUBSCRIBE s více příjemci https://github.com/tisnik/go-root/blob/master/article66/03_pub_mo­re_sub/03_pub_more_sub.go
4 04_pub_sub_channel/04_pub_sub_chan­nel.go strategie PUBLISH-SUBSCRIBE s příjemci používajícími kanály https://github.com/tisnik/go-root/blob/master/article66/04_pub_sub_chan­nel/04_pub_sub_channel.go
5 05_pipeline/05_pipeline.go pipeline příkazů v Redisu https://github.com/tisnik/go-root/blob/master/article66/05_pi­peline/05_pipeline.go
6 06_script/06_script.go spuštění skriptu na straně serveru (Redisu) https://github.com/tisnik/go-root/blob/master/article66/06_scrip­t/06_script.go
Poznámka: jednotlivé zdrojové kódy jsou umístěny ve vlastních adresářích, protože musíme využít systém modulů programovacího jazyka Go.

20. Odkazy na Internetu

  1. Repositář projektu s Redis klientem pro jazyk Go
    https://github.com/go-redis/redis
  2. Type-safe Redis client for Go
    https://redis.uptrace.dev/
  3. Dokumentace k balíčku redis
    https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc
  4. godis – redis client implement by golang, inspired by jedis.
    https://github.com/piaohao/godis
  5. 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
  6. Adventures in message queues
    http://antirez.com/news/88
  7. redeo
    https://github.com/bsm/redeo
  8. 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/
  9. Stránky projektu Redis
    https://redis.io/
  10. Introduction to Redis
    https://redis.io/topics/introduction
  11. Try Redis
    http://try.redis.io/
  12. Redis tutorial, April 2010 (starší, ale pěkně udělaný)
    https://static.simonwilli­son.net/static/2010/redis-tutorial/
  13. 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/
  14. Praktický úvod do Redis (1): vaše distribuovaná NoSQL cache
    http://www.cloudsvet.cz/?p=253
  15. Praktický úvod do Redis (2): transakce
    http://www.cloudsvet.cz/?p=256
  16. Praktický úvod do Redis (3): cluster
    http://www.cloudsvet.cz/?p=258
  17. Go Data Structures: Binary Search Tree
    https://flaviocopes.com/golang-data-structure-binary-search-tree/
  18. Gobs of data
    https://blog.golang.org/gobs-of-data
  19. Formát BSON
    http://bsonspec.org/
  20. Golang Guide: A List of Top Golang Frameworks, IDEs & Tools
    https://blog.intelligentbe­e.com/2017/08/14/golang-guide-list-top-golang-frameworks-ides-tools/
  21. Tvorba univerzálních projevů
    http://www.kyblsoft.cz/projevy
  22. Repositář projektu Gift
    https://github.com/disinte­gration/gift
  23. Dokumentace k projektu Gift
    https://godoc.org/github.com/di­sintegration/gift
  24. Online x86 / x64 Assembler and Disassembler
    https://defuse.ca/online-x86-assembler.htm#disassembly2
  25. The Design of the Go Assembler
    https://talks.golang.org/2016/as­m.slide#1
  26. A Quick Guide to Go's Assembler
    https://golang.org/doc/asm
  27. AssemblyPolicy
    https://github.com/golang/go/wi­ki/AssemblyPolicy
  28. Geohash in Golang Assembly
    https://mmcloughlin.com/posts/geohash-assembly
  29. Command objdump
    https://golang.org/cmd/objdump/
  30. Assembly
    https://goroutines.com/asm
  31. Go & Assembly
    http://www.doxsey.net/blog/go-and-assembly
  32. A Foray Into Go Assembly Programming
    https://blog.sgmansfield.com/2017/04/a-foray-into-go-assembly-programming/
  33. Golang Capturing log.Println And fmt.Println Output
    https://medium.com/@hau12a1/golang-capturing-log-println-and-fmt-println-output-770209c791b4
  34. Stránka projektu plotly
    https://plot.ly/
  35. Plotly JavaScript Open Source Graphing Library
    https://plot.ly/javascript/
  36. Domain coloring
    https://en.wikipedia.org/wi­ki/Domain_coloring
  37. Michael Fogleman's projects
    https://www.michaelfogleman­.com/projects/tagged/grap­hics/
  38. Color Graphs of Complex Functions
    https://web.archive.org/web/20120511021419/htt­p://w.american.edu/cas/mat­hstat/lcrone/ComplexPlot.html
  39. A Gallery of Complex Functions
    http://wismuth.com/complex/ga­llery.html
  40. package glot
    https://godoc.org/github.com/A­rafatk/glot
  41. Gnuplotting: Output terminals
    http://www.gnuplotting.org/output-terminals/
  42. Introducing Glot the plotting library for Golang
    https://medium.com/@Arafat­./introducing-glot-the-plotting-library-for-golang-3133399948a1
  43. Introducing Glot the plotting library for Golang
    https://blog.gopheracademy.com/advent-2018/introducing-glot/
  44. Glot is a plotting library for Golang built on top of gnuplot
    https://github.com/Arafatk/glot
  45. Example plots (gonum/plot)
    https://github.com/gonum/plot/wi­ki/Example-plots
  46. A repository for plotting and visualizing data (gonum/plot)
    https://github.com/gonum/plot
  47. golang library to make https://chartjs.org/ plots
    https://github.com/brentp/go-chartjs
  48. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  49. The Gonum Numerical Computing Package
    https://www.gonum.org/pos­t/introtogonum/
  50. Gomacro na GitHubu
    https://github.com/cosmos72/gomacro
  51. gophernotes – Use Go in Jupyter notebooks and nteract
    https://github.com/gopher­data/gophernotes
  52. gonum
    https://github.com/gonum
  53. go-gota/gota – DataFrames and data wrangling in Go (Golang)
    https://porter.io/github.com/go-gota/gota
  54. A repository for plotting and visualizing data
    https://github.com/gonum/plot
  55. Gonum Numerical Packages
    https://www.gonum.org/
  56. Stránky projektu MinIO
    https://min.io/
  57. MinIO Quickstart Guide
    https://docs.min.io/docs/minio-quickstart-guide.html
  58. MinIO Go Client API Reference
    https://docs.min.io/docs/golang-client-api-reference
  59. MinIO Python Client API Reference
    https://docs.min.io/docs/python-client-api-reference.html
  60. 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/
  61. Benchmarking MinIO vs. AWS S3 for Apache Spark
    https://blog.min.io/benchmarking-apache-spark-vs-aws-s3/
  62. MinIO Client Quickstart Guide
    https://docs.min.io/docs/minio-client-quickstart-guide.html
  63. Analýza kvality zdrojových kódů Minia
    https://goreportcard.com/re­port/github.com/minio/minio
  64. This is MinIO
    https://www.youtube.com/wat­ch?v=vF0lQh0XOCs
  65. Running MinIO Standalone
    https://www.youtube.com/wat­ch?v=dIQsPCHvHoM
  66. „Amazon S3 Compatible Storage in Kubernetes“ – Rob Girard, Principal Tech Marketing Engineer, Minio
    https://www.youtube.com/wat­ch?v=wlpn8K0jJ4U
  67. Ginkgo
    http://onsi.github.io/ginkgo/
  68. Gomega
    https://onsi.github.io/gomega/
  69. Ginkgo's Preferred Matcher Library na GitHubu
    https://github.com/onsi/gomega/
  70. Provided Matchers
    http://onsi.github.io/gomega/#provided-matchers
  71. Dokumentace k balíčku goexpect
    https://godoc.org/github.com/go­ogle/goexpect
  72. Balíček goexpect
    https://github.com/google/goexpect
  73. Balíček go-expect
    https://github.com/Netflix/go-expect
  74. Balíček gexpect
    https://github.com/Thomas­Rooney/gexpect
  75. Expect (originál naprogramovaný v TCL)
    https://core.tcl-lang.org/expect/index
  76. Expect (Wikipedia)
    https://en.wikipedia.org/wiki/Expect
  77. Pexpect
    https://pexpect.readthedoc­s.io/en/stable/
  78. Golang SSH Client: Multiple Commands, Crypto & Goexpect Examples
    http://networkbit.ch/golang-ssh-client/
  79. goblin na GitHubu
    https://github.com/franela/goblin
  80. Mocha framework
    https://mochajs.org/
  81. frisby na GitHubu
    https://github.com/verdverm/frisby
  82. package frisby
    https://godoc.org/github.com/ver­dverm/frisby
  83. Frisby alternatives and similar packages (generováno)
    https://go.libhunt.com/frisby-alternatives
  84. Cucumber for golang
    https://github.com/DATA-DOG/godog
  85. How to Use Godog for Behavior-driven Development in Go
    https://semaphoreci.com/com­munity/tutorials/how-to-use-godog-for-behavior-driven-development-in-go
  86. Comparative Analysis Of GoLang Testing Frameworks
    https://www.slideshare.net/Dushy­antBhalgami/comparative-analysis-of-golang-testing-frameworks
  87. A Quick Guide to Testing in Golang
    https://caitiem.com/2016/08/18/a-quick-guide-to-testing-in-golang/
  88. Tom's Obvious, Minimal Language.
    https://github.com/toml-lang/toml
  89. xml.org
    http://www.xml.org/
  90. Soubory .properties
    https://en.wikipedia.org/wi­ki/.properties
  91. Soubory INI
    https://en.wikipedia.org/wi­ki/INI_file
  92. JSON to YAML
    https://www.json2yaml.com/
  93. Data Format Converter
    https://toolkit.site/format.html
  94. Viper na GitHubu
    https://github.com/spf13/viper
  95. GoDotEnv na GitHubu
    https://github.com/joho/godotenv
  96. The fantastic ORM library for Golang
    http://gorm.io/
  97. Dokumentace k balíčku gorilla/mux
    https://godoc.org/github.com/go­rilla/mux
  98. Gorilla web toolkitk
    http://www.gorillatoolkit.org/
  99. Metric types
    https://prometheus.io/doc­s/concepts/metric_types/
  100. Histograms with Prometheus: A Tale of Woe
    http://linuxczar.net/blog/2017/06/15/pro­metheus-histogram-2/
  101. Why are Prometheus histograms cumulative?
    https://www.robustperception.io/why-are-prometheus-histograms-cumulative
  102. Histograms and summaries
    https://prometheus.io/doc­s/practices/histograms/
  103. Instrumenting Golang server in 5 min
    https://medium.com/@gsisi­mogang/instrumenting-golang-server-in-5-min-c1c32489add3
  104. Semantic Import Versioning in Go
    https://www.aaronzhuo.com/semantic-import-versioning-in-go/
  105. Sémantické verzování
    https://semver.org/
  106. Getting started with Go modules
    https://medium.com/@fonse­ka.live/getting-started-with-go-modules-b3dac652066d
  107. Create projects independent of $GOPATH using Go Modules
    https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o
  108. Anatomy of Modules in Go
    https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16
  109. Modules
    https://github.com/golang/go/wi­ki/Modules
  110. Go Modules Tutorial
    https://tutorialedge.net/golang/go-modules-tutorial/
  111. Module support
    https://golang.org/cmd/go/#hdr-Module_support
  112. Go Lang: Memory Management and Garbage Collection
    https://vikash1976.wordpres­s.com/2017/03/26/go-lang-memory-management-and-garbage-collection/
  113. Golang Internals, Part 4: Object Files and Function Metadata
    https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html
  114. What is REPL?
    https://pythonprogramminglan­guage.com/repl/
  115. What is a REPL?
    https://codewith.mu/en/tu­torials/1.0/repl
  116. Programming at the REPL: Introduction
    https://clojure.org/guides/re­pl/introduction
  117. What is REPL? (Quora)
    https://www.quora.com/What-is-REPL
  118. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  119. Read-eval-print loop (Wikipedia)
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  120. Vim as a Go (Golang) IDE using LSP and vim-go
    https://octetz.com/posts/vim-as-go-ide
  121. gopls
    https://github.com/golang/go/wi­ki/gopls
  122. IDE Integration Guide
    https://github.com/stamble­rre/gocode/blob/master/doc­s/IDE_integration.md
  123. How to instrument Go code with custom expvar metrics
    https://sysdig.com/blog/golang-expvar-custom-metrics/
  124. Golang expvar metricset (Metricbeat Reference)
    https://www.elastic.co/gu­ide/en/beats/metricbeat/7­.x/metricbeat-metricset-golang-expvar.html
  125. Package expvar
    https://golang.org/pkg/expvar/#NewInt
  126. Java Platform Debugger Architecture: Overview
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jpda/jpda­.html
  127. The JVM Tool Interface (JVM TI): How VM Agents Work
    https://www.oracle.com/technet­work/articles/javase/index-140680.html
  128. JVM Tool Interface Version 11.0
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jvmti­.html
  129. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  130. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  131. IBM JVMTI extensions
    http://publib.boulder.ibm­.com/infocenter/realtime/v2r0/in­dex.jsp?topic=%2Fcom.ibm.sof­trt.doc%2Fdiag%2Ftools%2Fjvmti_ex­tensions.html
  132. Go & cgo: integrating existing C code with Go
    http://akrennmair.github.io/golang-cgo-slides/#1
  133. Using cgo to call C code from within Go code
    https://wenzr.wordpress.com/2018/06/07/u­sing-cgo-to-call-c-code-from-within-go-code/
  134. Package trace
    https://golang.org/pkg/runtime/trace/
  135. Introducing HTTP Tracing
    https://blog.golang.org/http-tracing
  136. Command trace
    https://golang.org/cmd/trace/
  137. A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
    https://github.com/wesovilabs/koazee
  138. Funkce vyššího řádu v knihovně Underscore
    https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/
  139. Delve: a debugger for the Go programming language.
    https://github.com/go-delve/delve
  140. Příkazy debuggeru Delve
    https://github.com/go-delve/delve/tree/master/Do­cumentation/cli
  141. Debuggery a jejich nadstavby v Linuxu
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  142. Debuggery a jejich nadstavby v Linuxu (2. část)
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  143. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  144. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  145. 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/
  146. Debugging Go Code with GDB
    https://golang.org/doc/gdb
  147. Debugging Go (golang) programs with gdb
    https://thornydev.blogspot­.com/2014/01/debugging-go-golang-programs-with-gdb.html
  148. GDB – Dokumentace
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/
  149. GDB – Supported Languages
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/Suppor­ted-Languages.html#Supported-Languages
  150. GNU Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Debugger
  151. The LLDB Debugger
    http://lldb.llvm.org/
  152. Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/Debugger
  153. 13 Linux Debuggers for C++ Reviewed
    http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817
  154. 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
  155. Go Proverbs: Simple, Poetic, Pithy
    https://go-proverbs.github.io/
  156. Handling Sparse Files on Linux
    https://www.systutorials.com/136652/han­dling-sparse-files-on-linux/
  157. Gzip (Wikipedia)
    https://en.wikipedia.org/wiki/Gzip
  158. Deflate
    https://en.wikipedia.org/wiki/DEFLATE
  159. 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/
  160. Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
    https://www.root.cz/clanky/he­xadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/
  161. Hex dump
    https://en.wikipedia.org/wi­ki/Hex_dump
  162. Rozhraní io.ByteReader
    https://golang.org/pkg/io/#ByteReader
  163. Rozhraní io.RuneReader
    https://golang.org/pkg/io/#RuneReader
  164. Rozhraní io.ByteScanner
    https://golang.org/pkg/io/#By­teScanner
  165. Rozhraní io.RuneScanner
    https://golang.org/pkg/io/#Ru­neScanner
  166. Rozhraní io.Closer
    https://golang.org/pkg/io/#Closer
  167. Rozhraní io.Reader
    https://golang.org/pkg/io/#Reader
  168. Rozhraní io.Writer
    https://golang.org/pkg/io/#Writer
  169. Typ Strings.Reader
    https://golang.org/pkg/strin­gs/#Reader
  170. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  171. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  172. go-cron
    https://github.com/rk/go-cron
  173. gocron
    https://github.com/jasonlvhit/gocron
  174. clockwork
    https://github.com/whiteShtef/cloc­kwork
  175. clockwerk
    https://github.com/onatm/clockwerk
  176. JobRunner
    https://github.com/bamzi/jobrunner
  177. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  178. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  179. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  180. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  181. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  182. go-prompt
    https://github.com/c-bata/go-prompt
  183. readline
    https://github.com/chzyer/readline
  184. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  185. go-readline
    https://github.com/fiorix/go-readline
  186. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  187. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  188. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  189. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  190. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  191. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  192. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  193. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  194. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  195. Editline Library (libedit)
    http://thrysoee.dk/editline/
  196. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  197. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  198. WinEditLine
    http://mingweditline.sourceforge.net/
  199. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  200. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  201. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  202. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  203. history(3) – Linux man page
    https://linux.die.net/man/3/history
  204. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  205. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  206. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  207. Balíček ogletest
    https://github.com/jacobsa/ogletest
  208. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  209. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  210. package testing
    https://golang.org/pkg/testing/
  211. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  212. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  213. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  214. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  215. GoConvey
    http://goconvey.co/
  216. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  217. 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
  218. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  219. package gg
    https://godoc.org/github.com/fo­gleman/gg
  220. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  221. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  222. The Go image package
    https://blog.golang.org/go-image-package
  223. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  224. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  225. YAML
    https://yaml.org/
  226. edn
    https://github.com/edn-format/edn
  227. Smile
    https://github.com/FasterXML/smile-format-specification
  228. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  229. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  230. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  231. Introducing JSON
    http://json.org/
  232. Package json
    https://golang.org/pkg/encoding/json/
  233. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  234. Go by Example: JSON
    https://gobyexample.com/json
  235. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  236. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  237. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  238. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  239. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  240. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  241. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  242. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  243. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  244. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  245. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  246. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  247. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  248. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  249. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  250. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  251. Algorithms to Go
    https://yourbasic.org/
  252. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  253. 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/
  254. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  255. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  256. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  257. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  258. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  259. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  260. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  261. The Go Programming Language (home page)
    https://golang.org/
  262. GoDoc
    https://godoc.org/
  263. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  264. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  265. The Go Programming Language Specification
    https://golang.org/ref/spec
  266. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  267. Package builtin
    https://golang.org/pkg/builtin/
  268. Package fmt
    https://golang.org/pkg/fmt/
  269. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  270. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  271. Learning Go
    https://www.miek.nl/go/
  272. Go Bootcamp
    http://www.golangbootcamp.com/
  273. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  274. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  275. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  276. The Go Blog
    https://blog.golang.org/
  277. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  278. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  279. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  280. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  281. 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
  282. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  283. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  284. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  285. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  286. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  287. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  288. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  289. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  290. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  291. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  292. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  293. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  294. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  295. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  296. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  297. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  298. 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/
  299. 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
  300. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  301. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  302. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  303. Go vs. Python
    https://www.peterbe.com/plog/govspy
  304. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  305. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  306. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  307. Go by Example: Slices
    https://gobyexample.com/slices
  308. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  309. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  310. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  311. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  312. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  313. nils In Go
    https://go101.org/article/nil.html
  314. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  315. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  316. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  317. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  318. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  319. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  320. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  321. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  322. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  323. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  324. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  325. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  326. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  327. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  328. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  329. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  330. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  331. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  332. 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
  333. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  334. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  335. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  336. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  337. Selectors
    https://golang.org/ref/spec#Selectors
  338. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  339. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  340. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  341. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  342. Part 21: Goroutines
    https://golangbot.com/goroutines/
  343. Part 22: Channels
    https://golangbot.com/channels/
  344. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  345. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  346. 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/
  347. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  348. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  349. Control Structures
    https://www.golang-book.com/books/intro/5
  350. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  351. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  352. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  353. Goroutine IDs
    https://blog.sgmansfield.com/2015/12/go­routine-ids/
  354. Different ways to pass channels as arguments in function in go (golang)
    https://stackoverflow.com/qu­estions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang
  355. justforfunc #22: using the Go execution tracer
    https://www.youtube.com/wat­ch?v=ySy3sR1LFCQ
  356. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  357. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  358. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  359. Effective Go
    https://golang.org/doc/ef­fective_go.html
  360. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  361. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation
  362. The zero value of a slice is not nil
    https://stackoverflow.com/qu­estions/30806931/the-zero-value-of-a-slice-is-not-nil
  363. Go-tcha: When nil != nil
    https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic
  364. Nils in Go
    https://www.doxsey.net/blog/nils-in-go