Obsah
1. Použití databáze Redis v aplikacích naprogramovaných v Go
2. Instalace Redisu i balíčku pro jazyk Go
3. Inicializace klienta zajišťujícího komunikaci s Redisem
4. Ověření připojení protokolem PING-PONG
5. Alternativní způsob specifikace připojení k Redisu
8. Rozlišení chyby, ke které při čtení došlo
9. Nastavení doby expirace záznamu
10. Zvýšení či snížení celočíselné hodnoty uložené v Redisu
11. Zvýšení či snížení numerické hodnoty s plovoucí řádovou čárkou
13. Základní operace se seznamy – lpush, lpop a llen
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
19. Repositář s demonstračními příklady
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:
- Databáze Redis (nejenom) pro vývojáře používající Python
https://www.root.cz/clanky/databaze-redis-nejenom-pro-vyvojare-pouzivajici-python/ - Databáze Redis (nejenom) pro vývojáře používající Python (dokončení)
https://www.root.cz/clanky/databaze-redis-nejenom-pro-vyvojare-pouzivajici-python-dokonceni/ - 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/ - 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/ - 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
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) }
Tento demonstrační příklad naleznete na adrese https://github.com/tisnik/go-root/blob/master/article65/01_new_client/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_ping_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_string, 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:
- K chybě nedošlo a máme tedy hodnotu přečtenou z Redisu
- Hodnota neexistuje, takže se vrátila hodnota nil
- 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) } }
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_expiration/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) }
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) }
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) }
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 |
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_list/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:
// 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:
20. Odkazy na Internetu
- Repositář projektu s Redis klientem pro jazyk Go
https://github.com/go-redis/redis - Type-safe Redis client for Go
https://redis.uptrace.dev/ - Dokumentace k balíčku redis
https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc - godis – redis client implement by golang, inspired by jedis.
https://github.com/piaohao/godis - How to Use Redis Go Client go-redis/redis with GoLang
https://kb.objectrocket.com/redis/how-to-use-redis-go-client-go-redis-redis-with-golang-592 - Adventures in message queues
http://antirez.com/news/88 - redeo
https://github.com/bsm/redeo - First-in, first-out queues
https://redislabs.com/ebook/part-2-core-concepts/chapter-6-application-components-in-redis/6–4-task-queues/6–4–1-first-in-first-out-queues/ - Stránky projektu Redis
https://redis.io/ - Introduction to Redis
https://redis.io/topics/introduction - Try Redis
http://try.redis.io/ - Redis tutorial, April 2010 (starší, ale pěkně udělaný)
https://static.simonwillison.net/static/2010/redis-tutorial/ - Redis: key-value databáze v paměti i na disku
https://www.zdrojak.cz/clanky/redis-key-value-databaze-v-pameti-i-na-disku/ - Praktický úvod do Redis (1): vaše distribuovaná NoSQL cache
http://www.cloudsvet.cz/?p=253 - Praktický úvod do Redis (2): transakce
http://www.cloudsvet.cz/?p=256 - Praktický úvod do Redis (3): cluster
http://www.cloudsvet.cz/?p=258 - Go Data Structures: Binary Search Tree
https://flaviocopes.com/golang-data-structure-binary-search-tree/ - Gobs of data
https://blog.golang.org/gobs-of-data - Formát BSON
http://bsonspec.org/ - Golang Guide: A List of Top Golang Frameworks, IDEs & Tools
https://blog.intelligentbee.com/2017/08/14/golang-guide-list-top-golang-frameworks-ides-tools/ - Tvorba univerzálních projevů
http://www.kyblsoft.cz/projevy - Repositář projektu Gift
https://github.com/disintegration/gift - Dokumentace k projektu Gift
https://godoc.org/github.com/disintegration/gift - Online x86 / x64 Assembler and Disassembler
https://defuse.ca/online-x86-assembler.htm#disassembly2 - The Design of the Go Assembler
https://talks.golang.org/2016/asm.slide#1 - A Quick Guide to Go's Assembler
https://golang.org/doc/asm - AssemblyPolicy
https://github.com/golang/go/wiki/AssemblyPolicy - Geohash in Golang Assembly
https://mmcloughlin.com/posts/geohash-assembly - Command objdump
https://golang.org/cmd/objdump/ - Assembly
https://goroutines.com/asm - Go & Assembly
http://www.doxsey.net/blog/go-and-assembly - A Foray Into Go Assembly Programming
https://blog.sgmansfield.com/2017/04/a-foray-into-go-assembly-programming/ - Golang Capturing log.Println And fmt.Println Output
https://medium.com/@hau12a1/golang-capturing-log-println-and-fmt-println-output-770209c791b4 - Stránka projektu plotly
https://plot.ly/ - Plotly JavaScript Open Source Graphing Library
https://plot.ly/javascript/ - Domain coloring
https://en.wikipedia.org/wiki/Domain_coloring - Michael Fogleman's projects
https://www.michaelfogleman.com/projects/tagged/graphics/ - Color Graphs of Complex Functions
https://web.archive.org/web/20120511021419/http://w.american.edu/cas/mathstat/lcrone/ComplexPlot.html - A Gallery of Complex Functions
http://wismuth.com/complex/gallery.html - package glot
https://godoc.org/github.com/Arafatk/glot - Gnuplotting: Output terminals
http://www.gnuplotting.org/output-terminals/ - Introducing Glot the plotting library for Golang
https://medium.com/@Arafat./introducing-glot-the-plotting-library-for-golang-3133399948a1 - Introducing Glot the plotting library for Golang
https://blog.gopheracademy.com/advent-2018/introducing-glot/ - Glot is a plotting library for Golang built on top of gnuplot
https://github.com/Arafatk/glot - Example plots (gonum/plot)
https://github.com/gonum/plot/wiki/Example-plots - A repository for plotting and visualizing data (gonum/plot)
https://github.com/gonum/plot - golang library to make https://chartjs.org/ plots
https://github.com/brentp/go-chartjs - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - The Gonum Numerical Computing Package
https://www.gonum.org/post/introtogonum/ - Gomacro na GitHubu
https://github.com/cosmos72/gomacro - gophernotes – Use Go in Jupyter notebooks and nteract
https://github.com/gopherdata/gophernotes - gonum
https://github.com/gonum - go-gota/gota – DataFrames and data wrangling in Go (Golang)
https://porter.io/github.com/go-gota/gota - A repository for plotting and visualizing data
https://github.com/gonum/plot - Gonum Numerical Packages
https://www.gonum.org/ - Stránky projektu MinIO
https://min.io/ - MinIO Quickstart Guide
https://docs.min.io/docs/minio-quickstart-guide.html - MinIO Go Client API Reference
https://docs.min.io/docs/golang-client-api-reference - MinIO Python Client API Reference
https://docs.min.io/docs/python-client-api-reference.html - Performance at Scale: MinIO Pushes Past 1.4 terabits per second with 256 NVMe Drives
https://blog.min.io/performance-at-scale-minio-pushes-past-1–3-terabits-per-second-with-256-nvme-drives/ - Benchmarking MinIO vs. AWS S3 for Apache Spark
https://blog.min.io/benchmarking-apache-spark-vs-aws-s3/ - MinIO Client Quickstart Guide
https://docs.min.io/docs/minio-client-quickstart-guide.html - Analýza kvality zdrojových kódů Minia
https://goreportcard.com/report/github.com/minio/minio - This is MinIO
https://www.youtube.com/watch?v=vF0lQh0XOCs - Running MinIO Standalone
https://www.youtube.com/watch?v=dIQsPCHvHoM - „Amazon S3 Compatible Storage in Kubernetes“ – Rob Girard, Principal Tech Marketing Engineer, Minio
https://www.youtube.com/watch?v=wlpn8K0jJ4U - Ginkgo
http://onsi.github.io/ginkgo/ - Gomega
https://onsi.github.io/gomega/ - Ginkgo's Preferred Matcher Library na GitHubu
https://github.com/onsi/gomega/ - Provided Matchers
http://onsi.github.io/gomega/#provided-matchers - Dokumentace k balíčku goexpect
https://godoc.org/github.com/google/goexpect - Balíček goexpect
https://github.com/google/goexpect - Balíček go-expect
https://github.com/Netflix/go-expect - Balíček gexpect
https://github.com/ThomasRooney/gexpect - Expect (originál naprogramovaný v TCL)
https://core.tcl-lang.org/expect/index - Expect (Wikipedia)
https://en.wikipedia.org/wiki/Expect - Pexpect
https://pexpect.readthedocs.io/en/stable/ - Golang SSH Client: Multiple Commands, Crypto & Goexpect Examples
http://networkbit.ch/golang-ssh-client/ - goblin na GitHubu
https://github.com/franela/goblin - Mocha framework
https://mochajs.org/ - frisby na GitHubu
https://github.com/verdverm/frisby - package frisby
https://godoc.org/github.com/verdverm/frisby - Frisby alternatives and similar packages (generováno)
https://go.libhunt.com/frisby-alternatives - Cucumber for golang
https://github.com/DATA-DOG/godog - How to Use Godog for Behavior-driven Development in Go
https://semaphoreci.com/community/tutorials/how-to-use-godog-for-behavior-driven-development-in-go - Comparative Analysis Of GoLang Testing Frameworks
https://www.slideshare.net/DushyantBhalgami/comparative-analysis-of-golang-testing-frameworks - A Quick Guide to Testing in Golang
https://caitiem.com/2016/08/18/a-quick-guide-to-testing-in-golang/ - Tom's Obvious, Minimal Language.
https://github.com/toml-lang/toml - xml.org
http://www.xml.org/ - Soubory .properties
https://en.wikipedia.org/wiki/.properties - Soubory INI
https://en.wikipedia.org/wiki/INI_file - JSON to YAML
https://www.json2yaml.com/ - Data Format Converter
https://toolkit.site/format.html - Viper na GitHubu
https://github.com/spf13/viper - GoDotEnv na GitHubu
https://github.com/joho/godotenv - The fantastic ORM library for Golang
http://gorm.io/ - Dokumentace k balíčku gorilla/mux
https://godoc.org/github.com/gorilla/mux - Gorilla web toolkitk
http://www.gorillatoolkit.org/ - Metric types
https://prometheus.io/docs/concepts/metric_types/ - Histograms with Prometheus: A Tale of Woe
http://linuxczar.net/blog/2017/06/15/prometheus-histogram-2/ - Why are Prometheus histograms cumulative?
https://www.robustperception.io/why-are-prometheus-histograms-cumulative - Histograms and summaries
https://prometheus.io/docs/practices/histograms/ - Instrumenting Golang server in 5 min
https://medium.com/@gsisimogang/instrumenting-golang-server-in-5-min-c1c32489add3 - Semantic Import Versioning in Go
https://www.aaronzhuo.com/semantic-import-versioning-in-go/ - Sémantické verzování
https://semver.org/ - Getting started with Go modules
https://medium.com/@fonseka.live/getting-started-with-go-modules-b3dac652066d - Create projects independent of $GOPATH using Go Modules
https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o - Anatomy of Modules in Go
https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16 - Modules
https://github.com/golang/go/wiki/Modules - Go Modules Tutorial
https://tutorialedge.net/golang/go-modules-tutorial/ - Module support
https://golang.org/cmd/go/#hdr-Module_support - Go Lang: Memory Management and Garbage Collection
https://vikash1976.wordpress.com/2017/03/26/go-lang-memory-management-and-garbage-collection/ - Golang Internals, Part 4: Object Files and Function Metadata
https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html - What is REPL?
https://pythonprogramminglanguage.com/repl/ - What is a REPL?
https://codewith.mu/en/tutorials/1.0/repl - Programming at the REPL: Introduction
https://clojure.org/guides/repl/introduction - What is REPL? (Quora)
https://www.quora.com/What-is-REPL - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - Read-eval-print loop (Wikipedia)
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - Vim as a Go (Golang) IDE using LSP and vim-go
https://octetz.com/posts/vim-as-go-ide - gopls
https://github.com/golang/go/wiki/gopls - IDE Integration Guide
https://github.com/stamblerre/gocode/blob/master/docs/IDE_integration.md - How to instrument Go code with custom expvar metrics
https://sysdig.com/blog/golang-expvar-custom-metrics/ - Golang expvar metricset (Metricbeat Reference)
https://www.elastic.co/guide/en/beats/metricbeat/7.x/metricbeat-metricset-golang-expvar.html - Package expvar
https://golang.org/pkg/expvar/#NewInt - Java Platform Debugger Architecture: Overview
https://docs.oracle.com/en/java/javase/11/docs/specs/jpda/jpda.html - The JVM Tool Interface (JVM TI): How VM Agents Work
https://www.oracle.com/technetwork/articles/javase/index-140680.html - JVM Tool Interface Version 11.0
https://docs.oracle.com/en/java/javase/11/docs/specs/jvmti.html - Creating a Debugging and Profiling Agent with JVMTI
http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html - JVM TI (Wikipedia)
http://en.wikipedia.org/wiki/JVM_TI - IBM JVMTI extensions
http://publib.boulder.ibm.com/infocenter/realtime/v2r0/index.jsp?topic=%2Fcom.ibm.softrt.doc%2Fdiag%2Ftools%2Fjvmti_extensions.html - Go & cgo: integrating existing C code with Go
http://akrennmair.github.io/golang-cgo-slides/#1 - Using cgo to call C code from within Go code
https://wenzr.wordpress.com/2018/06/07/using-cgo-to-call-c-code-from-within-go-code/ - Package trace
https://golang.org/pkg/runtime/trace/ - Introducing HTTP Tracing
https://blog.golang.org/http-tracing - Command trace
https://golang.org/cmd/trace/ - A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
https://github.com/wesovilabs/koazee - Funkce vyššího řádu v knihovně Underscore
https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/ - Delve: a debugger for the Go programming language.
https://github.com/go-delve/delve - Příkazy debuggeru Delve
https://github.com/go-delve/delve/tree/master/Documentation/cli - Debuggery a jejich nadstavby v Linuxu
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/ - Debuggery a jejich nadstavby v Linuxu (2. část)
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/ - Debuggery a jejich nadstavby v Linuxu (3): Nemiver
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/ - Debuggery a jejich nadstavby v Linuxu (4): KDbg
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/ - Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/ - Debugging Go Code with GDB
https://golang.org/doc/gdb - Debugging Go (golang) programs with gdb
https://thornydev.blogspot.com/2014/01/debugging-go-golang-programs-with-gdb.html - GDB – Dokumentace
http://sourceware.org/gdb/current/onlinedocs/gdb/ - GDB – Supported Languages
http://sourceware.org/gdb/current/onlinedocs/gdb/Supported-Languages.html#Supported-Languages - GNU Debugger (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Debugger - The LLDB Debugger
http://lldb.llvm.org/ - Debugger (Wikipedia)
https://en.wikipedia.org/wiki/Debugger - 13 Linux Debuggers for C++ Reviewed
http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817 - Go is on a Trajectory to Become the Next Enterprise Programming Language
https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e - Go Proverbs: Simple, Poetic, Pithy
https://go-proverbs.github.io/ - Handling Sparse Files on Linux
https://www.systutorials.com/136652/handling-sparse-files-on-linux/ - Gzip (Wikipedia)
https://en.wikipedia.org/wiki/Gzip - Deflate
https://en.wikipedia.org/wiki/DEFLATE - 10 tools written in Go that every developer needs to know
https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Hex dump
https://en.wikipedia.org/wiki/Hex_dump - Rozhraní io.ByteReader
https://golang.org/pkg/io/#ByteReader - Rozhraní io.RuneReader
https://golang.org/pkg/io/#RuneReader - Rozhraní io.ByteScanner
https://golang.org/pkg/io/#ByteScanner - Rozhraní io.RuneScanner
https://golang.org/pkg/io/#RuneScanner - Rozhraní io.Closer
https://golang.org/pkg/io/#Closer - Rozhraní io.Reader
https://golang.org/pkg/io/#Reader - Rozhraní io.Writer
https://golang.org/pkg/io/#Writer - Typ Strings.Reader
https://golang.org/pkg/strings/#Reader - VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Goroutine IDs
https://blog.sgmansfield.com/2015/12/goroutine-ids/ - Different ways to pass channels as arguments in function in go (golang)
https://stackoverflow.com/questions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang - justforfunc #22: using the Go execution tracer
https://www.youtube.com/watch?v=ySy3sR1LFCQ - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation - The zero value of a slice is not nil
https://stackoverflow.com/questions/30806931/the-zero-value-of-a-slice-is-not-nil - Go-tcha: When nil != nil
https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic - Nils in Go
https://www.doxsey.net/blog/nils-in-go