Hlavní navigace

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

 Autor: Redis
Dnes si ukážeme způsob přístupu k databázi Redis. Tu lze využít pro mnoho účelů – od centrálního uložení konfigurace přes dokumentovou databázi až pro implementaci vlastního message brokera.
Pavel Tišnovský 23. 6. 2020
Doba čtení: 52 minut

Sdílet

11. Zvýšení či snížení numerické hodnoty s plovoucí řádovou čárkou

12. Základy práce se seznamy

13. Základní operace se seznamy – lpush, lpopllen

14. Seznam použitý v roli fronty

15. Opačný přístup k prvkům seznamu použitého ve funkci fronty

16. Fronta jakožto základ pro message brokera

17. Producent zpráv

18. Konzument zpráv

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

20. Odkazy na Internetu

1. Použití databáze Redis v aplikacích naprogramovaných v Go

V dnešní části seriálu o programovacím jazyku Go si ukážeme základní způsoby použití databáze Redis, což je v Go poměrně velmi často využívaná technologie. Programovací jazyk Go se totiž, jak již ostatně víme, velmi často používá mj. i pro implementaci různých služeb a mikroslužeb. V těchto případech se stav služby (pochopitelně pokud se nejedná o bezstavovou službu) musí ukládat do nějakého datového úložiště. Může se – podle povahy dat a popř. při požadavcích na ACID, HA atd. – jednat o relační databáze, dokumentové databáze, objektové databáze atd. A velmi často se setkáme právě s použitím systému Redis. Jedná se o databázi typu key-value, což znamená, že hodnoty ukládané do databáze je možné jednoznačně identifikovat (najít, smazat atd.) na základě klíče, který je reprezentován řetězcem. Podobných databází samozřejmě existuje celá řada; za zmínku stojí především Berkeley DB, dále pak MemcacheDB, Dynamo či InfinityDB. Databáze Redis může být pro vývojáře zajímavá a užitečná zejména z toho důvodu, že se jedná o velmi flexibilní systém, který lze škálovat nejenom „nahoru“ (distribuované systémy se shardingem a/nebo replikací), ale i „dolů“ (jednoduše nastavitelné datové odkladiště pro jednouživatelskou aplikaci).

Samotný systém Redis jsme si již na stránkách Roota popsali ve dvojici článků zmíněných pod tímto odstavcem. Tyto články, přesněji řečeno použité demonstrační příklady, byly orientovány především na vývojáře používající programovací jazyk Python. Taktéž jsme si v jiných článcích ukázali některé možnosti využití Redisu v message brokerech, protože v této oblasti je Redis velmi populární. Jedná se zejména o projekt RQ neboli Redis Queue, ovšem například i o minulý týden popsaný nástroj Huey (ovšem Redis se někdy používá i v souvislosti s Kafkou pro uložení offsetů či dalších informací se stavem konzumentů). Bližší informace lze najít v těchto článcích:

  1. Databáze Redis (nejenom) pro vývojáře používající Python
    https://www.root.cz/clanky/databaze-redis-nejenom-pro-vyvojare-pouzivajici-python/
  2. Databáze Redis (nejenom) pro vývojáře používající Python (dokončení)
    https://www.root.cz/clanky/databaze-redis-nejenom-pro-vyvojare-pouzivajici-python-dokonceni/
  3. Použití nástroje RQ (Redis Queue) pro správu úloh zpracovávaných na pozadí
    https://www.root.cz/clanky/pouziti-nastroje-rq-redis-queue-pro-spravu-uloh-zpracovavanych-na-pozadi/
  4. Nástroj huey: užitečná knihovna pro práci s frontami úloh v Pythonu
    https://www.root.cz/clanky/nastroj-huey-uzitecna-knihovna-pro-praci-s-frontami-uloh-v-pythonu/
  5. Apache Kafka: distribuovaná streamovací platforma
    https://www.root.cz/clanky/apache-kafka-distribuovana-streamovaci-platforma/

2. Instalace Redisu i balíčku pro jazyk Go

Instalace Redisu do Linuxu je snadná, protože ve většině distribucí existuje příslušný balíček s touto službou. Tímto tématem jsme se ostatně již zabývali v páté kapitole článku Nástroj huey: užitečná knihovna pro práci s frontami úloh v Pythonu.

Po instalaci a úpravě konfiguračního souboru redis.conf celou službu Redis spustíme:

15018:C 19 Jun 20:28:15.250 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
15018:C 19 Jun 20:28:15.250 # Redis version=4.0.10, bits=64, commit=00000000, modified=0, pid=15018, just started
15018:C 19 Jun 20:28:15.250 # Configuration loaded
15018:C 19 Jun 20:28:15.250 * supervised by systemd, will signal readiness
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 4.0.10 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 15018
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'
 
15018:M 19 Jun 20:28:15.253 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
15018:M 19 Jun 20:28:15.253 # Server initialized
15018:M 19 Jun 20:28:15.254 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_
15018:M 19 Jun 20:28:15.254 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues wi
15018:M 19 Jun 20:28:15.254 * DB loaded from disk: 0.000 seconds
15018:M 19 Jun 20:28:15.254 * Ready to accept connections
Poznámka: důležité je, aby se v logu skutečně objevil poslední řádek „Ready to accept connections“

Instalace balíčku, který zprostředkuje rozhraní mezi Redisem a aplikací naprogramovanou v jazyku Go, vyžaduje použití modulů, protože se používá verzování. Pro tento účel si můžeme vytvořit modul použitý pouze pro instalaci – po ní ho můžeme zahodit:

$ go mod init x
 
go: creating new go.mod: module x

V adresáři, kde vznikl soubor go.mod spustíme příkaz zajišťující instalaci balíčku:

$ go get github.com/go-redis/redis/v8
 
go: finding github.com/go-redis/redis/v8 v8.0.0-beta.5
go: finding github.com/go-redis/redis v6.15.8+incompatible
go: downloading github.com/go-redis/redis/v8 v8.0.0-beta.5
go: downloading github.com/go-redis/redis v6.15.8+incompatible
go: extracting github.com/go-redis/redis v6.15.8+incompatible
go: extracting github.com/go-redis/redis/v8 v8.0.0-beta.5
go: downloading github.com/dgryski/go-rendezvous v0.0.0-20200609043717-5ab96a526299
go: downloading go.opentelemetry.io/otel v0.6.0
go: extracting github.com/dgryski/go-rendezvous v0.0.0-20200609043717-5ab96a526299
go: extracting go.opentelemetry.io/otel v0.6.0
go: downloading google.golang.org/grpc v1.29.1
go: extracting google.golang.org/grpc v1.29.1
go: finding github.com/dgryski/go-rendezvous v0.0.0-20200609043717-5ab96a526299
go: finding go.opentelemetry.io/otel v0.6.0
go: finding google.golang.org/grpc v1.29.1

Od této chvíle by měl být balíček dostupný pro všechny aplikace vyvíjené v programovacím jazyku Go, ovšem za předpokladu, že tyto aplikace taktéž využívají systém modulů (což je dnes již samozřejmostí).

3. Inicializace klienta zajišťujícího komunikaci s Redisem

Nyní si již můžeme ukázat první demonstrační příklad, který pouze provede inicializaci klienta, jenž zajišťuje komunikaci s Redisem. Příklad je založen na využití systému modulů a bude tedy umístěn ve vlastním adresáři se souborem go.mod, jenž může vypadat následovně:

module redis1
 
go 1.13
 
require github.com/go-redis/redis/v8 v8.0.0-beta.5

Klient, resp. přesněji řečeno struktura představující klienta, se vytvoří konstruktorem redis.NewClient, jemuž se předává další datová struktura s podrobnějšími konfiguračními informacemi. Nutné je zadat adresu běžícího serveru (i s portem), případné heslo pro přihlášení (v našem případě je prázdné) a identifikaci použité databáze (0 = výchozí databáze). Konstruktor nekontroluje připojení a i z tohoto důvodu nevrací ve druhé návratové hodnotě strukturu s chybou:

// 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
})

Při ukončování aplikace by se měl klient uzavřít, což zajistí zavolání metody Close, kterou můžeme provést v bloku defer:

defer func() {
        err := client.Close()
        if err != nil {
                panic(err)
        }
}()

Úplný zdrojový kód dnešního prvního demonstračního příkladu vypadá následovně:

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)
                }
        }()
 
        // pouze zobrazíme textovou podobu struktury Client
        fmt.Println("Redis client:", client)
}
Poznámka: vzhledem k tomu, že se nekontroluje připojení, bude příklad spustitelný i ve chvíli, kdy připojení k Redisu nebylo navázáno.

Tento demonstrační příklad naleznete na adrese https://github.com/tisnik/go-root/blob/master/article65/01_new_cli­ent/01_new_client.go.

4. Ověření připojení protokolem PING-PONG

Příkazem „ping“ můžeme snadno otestovat, jestli se klient připojí k serveru a zda od něj dokáže získávat odpovědi. Chování si můžeme ověřit z CLI klienta:

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> ping test
"test"

Prakticky stejným způsobem lze postupovat v programovacím jazyku Go, protože rozhraní, které je splněno strukturou klient, má předepsánu i metodu Ping. Tato metoda – podobně jako všechny metody reprezentující příkazy Redisu – vrací hodnotu typu „příkaz“, jehož výsledek (a případnou chybu) vrací metoda Result:

// pokus o klasický handshake typu PING-PONG
pong, err := client.Ping(context).Result()

Ukažme si nyní úplný zdrojový kód upraveného demonstračního příkladu lze získat na adrese https://github.com/tisnik/go-root/blob/master/article65/02_pin­g_pong/02_ping_pong.go:

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)
                }
        }()
 
        // pouze zobrazíme textovou podobu struktury Client
        fmt.Println("Redis client:", client)
 
        // získáme kontext a zobrazíme informace o něm
        context := client.Context()
        fmt.Println("Context:", context)
 
        // pokus o klasický handshake typu PING-PONG
        pong, err := client.Ping(context).Result()
        fmt.Println("Ping-pong result:", pong, err)
}

Chování při úspěšném připojení k Redisu:

Redis client: Redis<localhost:6379 db:0>
Context: context.Background
Ping-pong result: PONG <nil>

Chování ve chvíli, kdy se připojení nezdařilo:

Redis client: Redis<localhost:6379 db:0>
Context: context.Background
Ping-pong result:  dial tcp [::1]:6379: connect: connection refused

5. Alternativní způsob specifikace připojení k Redisu

Parametry připojení, které jsme používali v předchozích dvou demonstračních příkladech, je možné zadat i jediným řetězcem, což je ostatně i velmi často používaný způsob, protože takový „connection string“ může být snadno uložen v proměnných prostředí či v konfiguračním souboru. Pro zpracování takového řetězce a vytvoření struktury reprezentující parametry připojení se používá funkce nazvaná ParseURL:

// konfigurační parametry nutné pro připojení k Redisu
options, err := redis.ParseURL("redis://" + redisAddress)
if err != nil {
        panic(err)
}

Tuto funkci lze velmi snadno zakomponovat do příkladu, a to následujícím způsobem:

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() {
        // konfigurační parametry nutné pro připojení k Redisu
        options, err := redis.ParseURL("redis://" + redisAddress)
        if err != nil {
                panic(err)
        }
 
        // vytvoření nového klienta s předáním konfiguračních parametrů
        client := redis.NewClient(options)
 
        // neměli bychom zapomenout na ukončení práce s klientem
        defer func() {
                err := client.Close()
                if err != nil {
                        panic(err)
                }
        }()
 
        // pouze zobrazíme textovou podobu struktury Client
        fmt.Println("Redis client:", client)
 
        // získáme kontext a zobrazíme informace o něm
        context := client.Context()
        fmt.Println("Context:", context)
 
        // pokus o klasický handshake typu PING-PONG
        pong, err := client.Ping(context).Result()
        fmt.Println("Ping-pong result:", pong, err)
}

Chování příkladu při úspěšném připojení k Redisu:

Redis client: Redis<localhost:6379 db:0>
Context: context.Background
Ping-pong result: PONG <nil>

Chování příkladu ve chvíli, kdy se připojení nezdařilo:

Redis client: Redis<localhost:6379 db:0>
Context: context.Background
Ping-pong result:  dial tcp [::1]:6379: connect: connection refused

6. Uložení řetězců do Redisu

Základním datovým typem, který se v Redisu používá, jsou řetězce. Ve skutečnosti se jedná o sekvenci bajtů známé délky, které nejsou žádným způsobem interpretovány. Díky tomu, že je délka řetězce uložena ve zvláštním atributu, nemusí Redis používat například znak s kódem 0 pro ukončení řetězce a tudíž se i tento znak může bez problému v řetězci vyskytovat (na rozdíl od klasických céčkovských řetězců). Maximální délka řetězce je v současné verzi Redisu 512 MB, což v praxi znamená, že se řetězce mohou použít například pro uložení dokumentů, strukturovaných dat reprezentovaných ve formátech JSON, XML, YAML atd. atd.

V dalším demonstračním příkladu je ukázáno, jakým způsobem je možné do Redisu uložit řetězec z programu napsaného v Go. Použijeme zde metodu Set, které je nutné předat kontext, klíč (což je také řetězec), vlastní hodnotu a dále dobu platnosti hodnoty (tedy relativní čas expirace). Pokud použijeme nulu, bude hodnota uložena trvale. Tato metoda vrací případnou chybu:

err := client.Set(context,
        "Seriál o jazyku Go",
        "https://www.root.cz/serialy/programovaci-jazyk-go/",
        0).Err()
if err != nil {
        panic(err)
}

Úplný zdrojový kód tohoto demonstračního příkladu, který je dostupný na adrese https://github.com/tisnik/go-root/tree/master/article65/04_set_strin­g, vypadá následovně:

package main
 
import (
        "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()
 
        // zápis hodnoty do databáze Redisu
        err := client.Set(context,
                "Seriál o jazyku Go",
                "https://www.root.cz/serialy/programovaci-jazyk-go/",
                0).Err()
        if err != nil {
                panic(err)
        }
}

Po spuštění tohoto demonstračního příkladu se připojíme k Redisu a pokusíme se přečíst uloženou hodnotu přímo z CLI klienta:

$ redis-cli 
 
127.0.0.1:6379> ping
PONG

Vypíšeme všechny dostupné klíče:

127.0.0.1:6379> keys *
1) "Seri\xc3\xa1l o jazyku Go"

A přečteme hodnotu:

127.0.0.1:6379> get "Seri\xc3\xa1l o jazyku Go"
"https://www.root.cz/serialy/programovaci-jazyk-go/"
 
127.0.0.1:6379> quit

Pokud vám vadí, že se některé Unicode znaky nevypíšou čitelným způsobem, lze klienta spustit s přepínačem –raw a ponechat transformaci znaků na emulátoru terminálu:

$ redis-cli --raw
 
127.0.0.1:6379> ping
PONG
 
127.0.0.1:6379> keys *
Seriál o jazyku Go
 
127.0.0.1:6379> get "Seriál o jazyku Go"
https://www.root.cz/serialy/programovaci-jazyk-go/
 
127.0.0.1:6379> quit

7. Přečtení řetězce z Redisu

Opakem metody Set, kterou jsme si popsali v předchozí kapitole, je pochopitelně metoda nazvaná Get, která slouží k přečtení (řetězcové) hodnoty z Redisu. Základní způsob použití je založen na stejném principu, jaký jsme již mohli vidět při popisu metody Ping – návratová hodnota je typu „příkaz Redisu“, takže je nutné pro získání skutečné hodnoty (a vlastně i pro provedení operace – včetně komunikace s Redisem) použít metodu Result vracející jak výsledek, tak i informaci o případné chybě, která nastala:

// přečtení hodnoty z databáze Redisu
address, err := client.Get(context, "Seriál o jazyku Go").Result()
if err != nil {
        panic(err)
}
fmt.Println("Adresa:", address)

Podívejme se na úplný zdrojový kód demonstračního příkladu, který tuto operaci provede:

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()
 
        // přečtení hodnoty z databáze Redisu
        address, err := client.Get(context, "Seriál o jazyku Go").Result()
        if err != nil {
                panic(err)
        }
        fmt.Println("Adresa:", address)
}

Předchozí příklad má však jednu malou vadu – nedokáže rozlišit mezi chybou způsobenou například tím, že Redis není dostupný, a chybou při přístupu k neexistující hodnotě (tj. ke klíči, ke kterému žádná hodnota není přiřazena). Rozlišení mezi těmito dvěma stavy, z nichž každý je sémanticky značně odlišný, si ukážeme v navazující kapitole.

8. Rozlišení chyby, ke které při čtení došlo

Nedostatek zmíněný na konci předchozí kapitoly, tj. rozlišení mezi chybou při komunikaci, popř. chybou způsobenou neexistující hodnotou, lze relativně snadno napravit. Můžeme totiž zjistit, jakého typu je vlastní návratová hodnota reprezentující chybu. Nejprve se pokusíme o přečtení hodnoty (v našem případě adresy – URL) z Redisu:

// přečtení hodnoty z databáze Redisu
address, err := client.Get(context, "Seriál o jazyku Go").Result()

A následně se pokusíme chybu analyzovat. Mohou přitom nastat tři základní situace:

  1. K chybě nedošlo a máme tedy hodnotu přečtenou z Redisu
  2. Hodnota neexistuje, takže se vrátila hodnota nil
  3. K chybě došlo při komunikaci s Redisem (odmítnutí připojení atd.)

Mezi těmito třemi stavy můžeme snadno rozlišit – důležitá je hned první podmínka:

// vyhodnocení předchozí operace
switch {
case err == redis.Nil:
        fmt.Println("no value found")
case err != nil:
        panic(err)
default:
        fmt.Println("Adresa:", address)
}

Opět si samozřejmě ukážeme úplný zdrojový kód demonstračního příkladu, který rozlišení chyby provádí:

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()
 
        // přečtení hodnoty z databáze Redisu
        address, err := client.Get(context, "Seriál o jazyku Go").Result()
 
        // vyhodnocení předchozí operace
        switch {
        case err == redis.Nil:
                fmt.Println("no value found")
        case err != nil:
                panic(err)
        default:
                fmt.Println("Adresa:", address)
        }
}
Poznámka: sami si vyzkoušejte, jak se bude systém chovat ve chvíli, kdy Redis neběží, popř. při čtení neexistující hodnoty.

9. Nastavení doby expirace záznamu

V případě, že se systém Redis používá pro implementaci vyrovnávací paměti, je možné využít jeho další užitečnou funkci – u všech záznamů je totiž možné specifikovat dobu jejich životnosti (TTL – Time To Live). K tomuto účelu se používá několik příkazů, zejména pak:

Příkaz Význam
setex vytvoření záznamu + nastavení jeho životnosti v sekundách
psetex vytvoření záznamu + nastavení jeho životnosti v milisekundách
expire nastavení životnosti existujícího záznamu v sekundách
pexpire nastavení životnosti existujícího záznamu v milisekundách

I tyto příkazy lze zavolat přímo z jazyka Go, a to díky existenci jejich ekvivalentu v rozhraní klienta. Nejjednodušší je situace při použití metody Set, u níž lze třetím parametrem nastavit dobu expirace. Prozatím jsme namísto této hodnoty dosazovali nulu, ovšem akceptována je jakákoli hodnota typu time.Duration. Příklad vytvoření struktury reprezentující „dobu trvanlivosti“ pět minut:

// specifikace doby platnosti hodnoty uložené do Redisu
expiration, err := time.ParseDuration("5m")
if err != nil {
        panic(err)
}

V příkladu se nejdříve záznam s dobou expirace vytvoří:

// zápis hodnoty do databáze Redisu
err = client.Set(context,
        "Seriál o jazyku Go",
        "https://www.root.cz/serialy/programovaci-jazyk-go/",
        expiration).Err()
if err != nil {
        panic(err)
}

A následně se pokusíme o jeho postupné čtení v programové smyčce:

for {
        // přečtení hodnoty z databáze Redisu
        address, err := client.Get(context, "Seriál o jazyku Go").Result()
 
        // vyhodnocení předchozí operace
        switch {
                case err == redis.Nil:
                        // záznam již neexistuje - ukončení smyčky
                        fmt.Println("no value found")
                        return
                ...
                ...
                ...
        time.Sleep(1 * time.Second)
}

Úplný demonstrační příklad naleznete na adrese https://github.com/tisnik/go-root/blob/master/article65/07_set_ex­piration/07_set_expiration­.go:

package main
 
import (
        "fmt"
        "time"

        "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()
 
        // specifikace doby platnosti hodnoty uložené do Redisu
        expiration, err := time.ParseDuration("10s")
        if err != nil {
                panic(err)
        }
 
        // zápis hodnoty do databáze Redisu
        err = client.Set(context,
                "Seriál o jazyku Go",
                "https://www.root.cz/serialy/programovaci-jazyk-go/",
                expiration).Err()
        if err != nil {
                panic(err)
        }
 
        for {
                // přečtení hodnoty z databáze Redisu
                address, err := client.Get(context, "Seriál o jazyku Go").Result()
 
                // vyhodnocení předchozí operace
                switch {
                case err == redis.Nil:
                        // záznam již neexistuje - ukončení smyčky
                        fmt.Println("no value found")
                        return
                case err != nil:
                        panic(err)
                default:
                        fmt.Println("Adresa:", address)
                }
 
                time.Sleep(1 * time.Second)
        }
}

Příklad běhu příkladu – po cca deseti sekundách již záznam přestane existovat a program se ukončí:

Adresa: https://www.root.cz/serialy/programovaci-jazyk-go/
Adresa: https://www.root.cz/serialy/programovaci-jazyk-go/
Adresa: https://www.root.cz/serialy/programovaci-jazyk-go/
Adresa: https://www.root.cz/serialy/programovaci-jazyk-go/
Adresa: https://www.root.cz/serialy/programovaci-jazyk-go/
Adresa: https://www.root.cz/serialy/programovaci-jazyk-go/
Adresa: https://www.root.cz/serialy/programovaci-jazyk-go/
Adresa: https://www.root.cz/serialy/programovaci-jazyk-go/
Adresa: https://www.root.cz/serialy/programovaci-jazyk-go/
Adresa: https://www.root.cz/serialy/programovaci-jazyk-go/
no value found

10. Zvýšení či snížení celočíselné hodnoty uložené v Redisu

Zajímavé je, že i když Redis neobsahuje přímou podporu pro datový typ „celé číslo“, nabízí svým uživatelům několik operací určených pro atomickou změnu numerických hodnot reprezentovaných řetězcem v běžném dekadickém formátu. Pro zvýšení hodnoty o jedničku se používá operace INCR, opakem je pochopitelně funkce DECR. V případě, že budeme potřebovat zvýšit nebo snížit uloženou hodnotu o krok odlišný od jedničky, je možné pro tento účel použít operaci pojmenovanou příhodně INCRBY.

Tyto operace jsou dostupné i v programovacím jazyku Go; konkrétně se jedná o metody pojmenované Incr a IncrBy. Tyto metody nejenže zvýší/sníží celočíselnou hodnotu o zadaný ofset, ale vrátí novou hodnotu (dostupnou přes Val), což se v některých případech může velmi dobře hodit (operace je z pohledu dalších programů atomická). Podívejme se nyní na příklad, který tyto dvě metody používá:

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, "counter")
 
        // inkrementace (neexistující) hodnoty
        newValue := client.Incr(context, "counter").Val()
        fmt.Println("Counter value:", newValue)
 
        // přečtení hodnoty z databáze Redisu
        newValue = client.IncrBy(context, "counter", 0).Val()
        fmt.Println("Counter value:", newValue)
 
        // inkrementace (nyní již existující) hodnoty
        newValue = client.Incr(context, "counter").Val()
        fmt.Println("Counter value:", newValue)
 
        // dekrementace (nyní již existující) hodnoty
        newValue = client.IncrBy(context, "counter", -1).Val()
        fmt.Println("Counter value:", newValue)
}
Poznámka: zdrojový kód příkladu naleznete na adrese https://github.com/tisnik/go-root/blob/master/article65/08_in­cr/08_incr.go.

Naprosto stejným způsobem lze použít metody Decr a DecrBy, takže si již bez dalšího popisu ukažme použití těchto metod:

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, "counter")
 
        // dekrementace (neexistující) hodnoty
        newValue := client.Decr(context, "counter").Val()
        fmt.Println("Counter value:", newValue)
 
        // přečtení hodnoty z databáze Redisu
        newValue = client.DecrBy(context, "counter", 0).Val()
        fmt.Println("Counter value:", newValue)
 
        // dekrementace (nyní již existující) hodnoty
        newValue = client.Decr(context, "counter").Val()
        fmt.Println("Counter value:", newValue)
 
        // inkrementace (nyní již existující) hodnoty
        newValue = client.DecrBy(context, "counter", -1).Val()
        fmt.Println("Counter value:", newValue)
}
Poznámka: zdrojový kód příkladu naleznete na adrese https://github.com/tisnik/go-root/blob/master/article65/09_de­cr/09_decr.go.

11. Zvýšení či snížení numerické hodnoty s plovoucí řádovou čárkou

Podobná operace nazvaná INCRBYFLOAT slouží pro změnu hodnoty čísla s desetinnou tečkou (opět ovšem uloženého formou běžného řetězce). Tuto operaci je možné použít například pro implementaci akumulátoru. V případě, že hodnota vůbec neexistuje, je vytvořena a je jí přiřazena nulová hodnota (konkrétně 0,0). Následuje příklad na interpretaci řetězce jako čísla s plovoucí řádovou čárkou:

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, "accumulator")
 
        // inkrementace (neexistující) hodnoty
        newValue := client.IncrByFloat(context, "accumulator", 0.0).Val()
        fmt.Println("Accumulator value:", newValue)
 
        // přečtení hodnoty z databáze Redisu
        newValue = client.IncrByFloat(context, "accumulator", 0.0).Val()
        fmt.Println("Accumulator value:", newValue)
 
        // inkrementace (nyní již existující) hodnoty
        newValue = client.IncrByFloat(context, "accumulator", 3.14).Val()
        fmt.Println("Accumulator value:", newValue)
 
        // dekrementace (nyní již existující) hodnoty
        newValue = client.IncrByFloat(context, "accumulator", -10e12).Val()
        fmt.Println("Accumulator value:", newValue)
}
Poznámka: interaktivní klient Redisu skutečně s hodnotami pracuje jako s řetězci (resp. se snaží obsah řetězců interpretovat jako čísla), ovšem jazyk Go je typově (více) bezpečný, takže metoda IncrByFloat vyžaduje hodnotu typu float64.

12. Základy práce se seznamy

Dalším datovým typem, který se v Redisu velmi často používá, jsou seznamy (list). Tento název je ovšem poněkud nepřesný, protože seznamy je možné využít například i pro implementaci fronty (queue), zásobníku (stack), běžného pole (array) nebo dokonce obousměrné fronty (deque). Počet prvků zapisovaných do seznamu může dosahovat prakticky neomezené hodnoty, konkrétně lze do jediného seznamu uložit 232-1 prvků. Mezi základní operace pro práci se seznamy patří:

Příkaz Význam
lpush přidání prvku na začátek seznamu
rpush přidání prvku na konec seznamu
lpop přečtení prvního prvku ze seznamu s jeho odstraněním
rpop přečtení posledního prvku ze seznamu s jeho odstraněním
   
lset změna hodnoty prvku na určeném indexu v seznamu
lindex přečtení prvku se zadaným indexem
   
linsert přidání prvku na určený index seznamu (s posunem dalších prvků)
llen přečtení délky seznamu
Poznámka: jak uvidíme v dalším textu, lze seznamy použít i pro implementaci fronty, zásobníku, kruhové fronty atd.

13. Základní operace se seznamy – lpush, lpop a llen

V dalším demonstračním příkladu, jehož úplný zdrojový kód naleznete na adrese https://github.com/tisnik/go-root/blob/master/article65/11_lis­t/11_list.go, je ukázáno použití operací lpush (přidání prvku na začátek seznamu), lpop (přečtení prvního prvku ze seznamu s jeho odstraněním) a llen (přečtení délky seznamu). Nejdříve je do seznamu vloženo několik hodnot funkcí mustPush, která navíc vždy vypíše délku seznamu po provedení příslušné operace:

func mustPush(client *redis.Client, context context.Context, key string, value string) {
        fmt.Println("Pushing", value, "into", key)
        // přidání prvku do seznamu
        length, err := client.LPush(context, key, value).Result()
        if err != nil {
                panic(err)
        }
        fmt.Println("List length", length)
}

Dále ze seznamu postupně jednotlivé prvky vyčteme – ze seznamem se tedy pracuje jako se zásobníkem, tedy s datovou strukturou typu LIFO (Last In-First Out). Jakmile další prvek není nalezen, je programová smyčka ukončena:

// přečtení všech hodnot ze seznamu
for {
        // pokus o přečtení hodnoty ze seznamu
        value, err := client.LPop(context, "seznam").Result()
 
        // vyhodnocení předchozí operace
        switch {
        case err == redis.Nil:
                fmt.Println("no value found")
                return
        case err != nil:
                panic(err)
        default:
                fmt.Println("Value from list", value)
        }
 
        length := client.LLen(context, "seznam").Val()
        fmt.Println("List length", length)
}

Příklad výsledků:

Pushing foo into seznam
List length 1
Pushing bar into seznam
List length 2
Pushing baz into seznam
List length 3
 
Value from list baz
List length 2
Value from list bar
List length 1
Value from list foo
List length 0
no value found

Celý zdrojový kód tohoto demonstračního příkladu vypadá následovně:

package main
 
import (
        "context"
        "fmt"

        "github.com/go-redis/redis/v8"
)
 
// adresa určující službu Redisu, která se má použít
const redisAddress = "localhost:6379"
 
func mustPush(client *redis.Client, context context.Context, key string, value string) {
        fmt.Println("Pushing", value, "into", key)
        // přidání prvku do seznamu
        length, err := client.LPush(context, key, value).Result()
        if err != nil {
                panic(err)
        }
        fmt.Println("List length", length)
}
 
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, "seznam")
 
        mustPush(client, context, "seznam", "foo")
        mustPush(client, context, "seznam", "bar")
        mustPush(client, context, "seznam", "baz")
 
        fmt.Println()
 
        // přečtení všech hodnot ze seznamu
        for {
                // pokus o přečtení hodnoty ze seznamu
                value, err := client.LPop(context, "seznam").Result()
 
                // vyhodnocení předchozí operace
                switch {
                case err == redis.Nil:
                        fmt.Println("no value found")
                        return
                case err != nil:
                        panic(err)
                default:
                        fmt.Println("Value from list", value)
                }
 
                length := client.LLen(context, "seznam").Val()
                fmt.Println("List length", length)
        }
}

14. Seznam použitý v roli fronty

V případě, že operace se seznamem omezíme pouze na lpush a rpop, stane se ze seznamu fronta, protože prvky jsou vkládány na opačný konec, než z něhož jsou opět čteny. Čtení a zápis je v základním režimu neblokující, ovšem lze použít i blokující operace a přiblížit se tak skutečným frontám (například s omezenou kapacitou). Následující zdrojový kód se vlastně příliš neliší od předchozího příkladu, což ovšem není překvapující, protože operace nad frontou a nad zásobníkem jsou z pohledu implementace (měnitelný seznam) podobné, i když sémantika a způsob praktického použití je zcela jiný:

package main
 
import (
        "context"
        "fmt"
 
        "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 mustEnqueue(client *redis.Client, context context.Context, key string, value string) {
        fmt.Printf("Enqueuing '%s' 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)
}
 
// 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)
 
        // vložení prvků do fronty
        mustEnqueue(client, context, queueName, "první")
        mustEnqueue(client, context, queueName, "druhý")
        mustEnqueue(client, context, queueName, "třetí")
        mustEnqueue(client, context, queueName, "čtvrtý")
 
        fmt.Println()
 
        // přečtení všech hodnot z fronty
        for {
                // pokus o přečtení hodnoty z fronty
                value, err := client.RPop(context, queueName).Result()
 
                // vyhodnocení předchozí operace
                switch {
                case err == redis.Nil:
                        fmt.Println("no value found")
                        return
                case err != nil:
                        panic(err)
                default:
                        fmt.Printf("Value dequed from queue: '%s'\n", value)
                }
 
                length := client.LLen(context, queueName).Val()
                printQueueLength(length)
        }
}

Samozřejmě si opět můžeme ukázat, jak fronta pracuje v praxi:

Enqueuing 'první' into queue named 'fronta'
Queue length after enqueuing is 1
Enqueuing 'druhý' into queue named 'fronta'
Queue length after enqueuing is 2
Enqueuing 'třetí' into queue named 'fronta'
Queue length after enqueuing is 3
Enqueuing 'čtvrtý' into queue named 'fronta'
Queue length after enqueuing is 4
 
Value dequed from queue: 'první'
Queue length after enqueuing is 3
Value dequed from queue: 'druhý'
Queue length after enqueuing is 2
Value dequed from queue: 'třetí'
Queue length after enqueuing is 1
Value dequed from queue: 'čtvrtý'
Queue length after enqueuing is 0
no value found

Z výpisu je patrné, že prvky jsou z fronty čteny v tom pořadí, v jakém do ní byly vloženy, protože fronta je typu FIFO (First-in first-out).

15. Opačný přístup k prvkům seznamu použitého ve funkci fronty

Jen pro úplnost si ukažme, že seznam lze použít jako frontu i „obráceně“, konkrétně tak, že se namísto operací lpush a rpop použijí operace rpush a lpop. Z hlediska výkonu by mělo být (pravděpodobně) jedno, která dvojice operací se použije. Následující demonstrační příklad je tedy až na několik detailů prakticky totožný s příkladem z předchozí kapitoly:

package main
 
import (
        "context"
        "fmt"
 
        "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 mustEnqueue(client *redis.Client, context context.Context, key string, value string) {
        fmt.Printf("Enqueuing '%s' into queue named '%s'\n", value, key)
        // přidání prvku do seznamu
        length, err := client.RPush(context, key, value).Result()
        if err != nil {
                panic(err)
        }
        printQueueLength(length)
}
 
// 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)
 
        // vložení prvků do fronty
        mustEnqueue(client, context, queueName, "první")
        mustEnqueue(client, context, queueName, "druhý")
        mustEnqueue(client, context, queueName, "třetí")
        mustEnqueue(client, context, queueName, "čtvrtý")
 
        fmt.Println()
 
        // přečtení všech hodnot z fronty
        for {
                // pokus o přečtení hodnoty z fronty
                value, err := client.LPop(context, queueName).Result()
 
                // vyhodnocení předchozí operace
                switch {
                case err == redis.Nil:
                        fmt.Println("no value found")
                        return
                case err != nil:
                        panic(err)
                default:
                        fmt.Printf("Value dequed from queue: '%s'\n", value)
                }
 
                length := client.LLen(context, queueName).Val()
                printQueueLength(length)
        }
}

16. Fronta jakožto základ pro message brokera

Redis je velmi často použit jako jedna komponenta message brokera. Ostatně se není čemu divit, protože již v předchozích dvou kapitolách jsme mohli vidět, jak snadno se dá sestavit jednoduchá fronta, která tvoří základ prakticky každého message brokera. Ukažme si však kostru skutečného message brokera, který vlastně nahradí koncept kanálů programovacího jazyka Go – namísto kanálu se použije fronta, jež „přežije“ restart aplikace.

17. Producent zpráv

Nejdříve vytvoříme producenta zpráv, který bude ukládat zprávy do fronty. Zprávou je zde myšleno celé číslo v daných mezích. Nejedná se o žádnou novinku, protože všechny operace známe z předchozích kapitol:

MIF obecny

// 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)
        }
}

18. Konzument zpráv

Konzument zpráv je – alespoň prozatím – implementován velmi jednoduše nekonečnou smyčkou, v níž se postupně čtou hodnoty z fronty. Ovšem je zde jedna změna oproti předchozím zdrojovým kódům – namísto operace rpop používáme operaci nazvanou brpop, což je „blokující“ obdoba operace pop. Konzument tedy bude v případě prázdné fronty čekat na příchod zprávy s nastavenou maximální dobou čekání (timeout):

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()
        }
}

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_new_client/01_new_client.go vytvoření instance Redis klienta https://github.com/tisnik/go-root/blob/master/article65/01_new_cli­ent/01_new_client.go
2 02_ping_pong/02_ping_pong.go otestování komunikace s využitím PING-PONG https://github.com/tisnik/go-root/blob/master/article65/02_pin­g_pong/02_ping_pong.go
3 03_connection_string/03_con­nection_string.go použití řetězce se specifikací adresy serveru https://github.com/tisnik/go-root/blob/master/article65/03_con­nection_string/03_connecti­on_string.go
4 04_set_string/04_set_string.go uložení řetězcové hodnoty https://github.com/tisnik/go-root/blob/master/article65/04_set_strin­g/04_set_string.go
5 05_get_string/05_get_string.go přečtení řetězcové hodnoty https://github.com/tisnik/go-root/blob/master/article65/05_get_strin­g/05_get_string.go
6 06_more_checks/06_more_checks.go otestování přečtené hodnoty https://github.com/tisnik/go-root/blob/master/article65/06_mo­re_checks/06_more_checks.go
7 07_set_expiration/07_set_expiration.go nastavení doby expirace záznamu https://github.com/tisnik/go-root/blob/master/article65/07_set_ex­piration/07_set_expiration­.go
8 08_incr/08_incr.go zvýšení celočíselné hodnoty o zadanou konstantu https://github.com/tisnik/go-root/blob/master/article65/08_in­cr/08_incr.go
9 09_decr/09_decr.go snížení celočíselné hodnoty o zadanou konstantu https://github.com/tisnik/go-root/blob/master/article65/09_de­cr/09_decr.go
10 10_float/10_float.go zvýšení FP hodnoty o zadanou konstantu https://github.com/tisnik/go-root/blob/master/article65/10_flo­at/10_float.go
11 11_list/11_list.go základní operace se seznamu https://github.com/tisnik/go-root/blob/master/article65/11_lis­t/11_list.go
12 12_queue/12_queue.go použití seznamů ve funkci fronty https://github.com/tisnik/go-root/blob/master/article65/12_qu­eue/12_queue.go
13 13_reverse_queue/13_reverse_queue.go použití seznamů ve funkci fronty, opačné operace https://github.com/tisnik/go-root/blob/master/article65/13_re­verse_queue/13_reverse_qu­eue.go
14 14_message_broker/14_message_broker.go velmi primitivní message broker založený na frontě https://github.com/tisnik/go-root/blob/master/article65/14_mes­sage_broker/14_message_bro­ker.go
15 15_session/15_session.go úprava předchozího příkladu pro lepší čitelnost https://github.com/tisnik/go-root/blob/master/article65/15_ses­sion/15_session.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