Obsah
1. Databáze Redis (nejenom) pro vývojáře používající Python
3. Konfigurace vhodná pro první seznámení s Redisem
4. Spuštění Redisu a první experimenty se systémem
5. Perzistentní uložení databáze
7. Nastavení doby životnosti dat
8. Podporované datové typy, s nimiž Redis pracuje
9. Práce s řetězci uloženými do databáze
14. Množiny s ohodnocenými prvky (uspořádané množiny)
15. Další operace s uspořádanými množinami
18. Vybrané operace poskytované knihovnou redis
19. Využití „pipeline“ pro sloučení většího množství příkazů do jediného požadavku
1. Databáze Redis (nejenom) pro vývojáře používající Python
Databáze Redis (REmote DIctionary Server) patří do rozsáhlé a nehomogenní skupiny nerelačních databází. Konkrétně se jedná 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. Dnes popisovaná 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).
Na jedné straně je možné Redis provozovat na jediném stroji s tím, že data budou uložena pouze v operační paměti, na straně druhé je však možné relativně snadno nakonfigurovat Redis takovým způsobem, že data budou rozložena mezi více strojů (sharding), popř. se použije architektura typu master-slave, kdy bude Redis data replikovat na pozadí mezi uzlem typu master a uzly typu slave (se všemi z toho vyplývajícími důsledky).
Díky této flexibilitě je možné systém Redis v praxi využít mnoha různými způsoby. Používá se například ve formě vyrovnávací paměti (cache), přičemž je dokonce možné specifikovat dobu života jednotlivých údajů, takže mazání starších položek nemusí být řešeno přímo v aplikaci (životnost údajů lze snadno obnovit, což se hodí například při ukládání informací o session/sezení u webových aplikací). Ovšem Redis můžeme použít i jako plnohodnotnou key-value databázi s tím, že data budou na pozadí ukládána na nevolatilní paměť (typicky na pevný disk či dnes spíše na SSD), což znamená, že údaje přežijí restart Redisu, pád počítače či systému atd. K dispozici mají vývojáři dvě základní strategie ukládání dat do nevolatilní paměti, které lze dokonce použít současně, o čemž se ve stručnosti zmíníme v páté a šesté kapitole. V případě potřeby je možné realizovat i systém s transakcemi, popř. využít některé atomické operace, které Redis podporuje už ve své základní sadě příkazů.
Z praktického hlediska je užitečné, že k Redisu, který je již při základní instalaci vybaven klientem ovládaným z příkazové řádky, je možné přistupovat z mnoha programovacích jazyků: na stránce https://redis.io/clients jsou vypsány informace o rozhraních k padesáti (!) jazykům, samozřejmě včetně populární pětice C, C++, Java, Python a Go. Navíc je možné od verze 2.6 dokonce využít i podporu pro přímé spouštění příkazů Redisu ze skriptů napsaných v programovacím jazyce Lua, který se skutečně velmi dobře hodí pro skriptování aplikací. Důležité přitom je, že skripty lze nahrát přímo do serveru Redisu a spustit je až ve chvíli, kdy jsou skutečně zapotřebí. V dnešním článku se však zaměříme především na použití Redisu přímo ze standardního klienta redis-cli a posléze z Pythonu.
2. Instalace Redisu
Před otestováním možností Redisu je samozřejmě nutné tento nástroj nainstalovat. Vzhledem k tomu, že se jedná o (na dnešní poměry) vlastně velmi malý projekt, je instalace prakticky okamžitá. Jen pro ukázku se podívejme na instalaci na Fedoře 27 s využitím nástroje dnf:
$ sudo dnf install redis Last metadata expiration check: 0:15:30 ago on Wed 24 Oct 2018, 22:50:11 CEST. Dependencies resolved. ================================================================================ Package Arch Version Repository Size ================================================================================ Installing: redis x86_64 4.0.9-1.fc27 updates 580 k Installing dependencies: jemalloc x86_64 4.5.0-5.fc27 updates 210 k Transaction Summary ================================================================================ Install 2 Packages Total download size: 790 k Installed size: 2.0 M Is this ok [y/N]:
Samotný průběh instalace:
Downloading Packages: (1/2): jemalloc-4.5.0-5.fc27.x86_64.rpm 1.0 MB/s | 210 kB 00:00 (2/2): redis-4.0.9-1.fc27.x86_64.rpm 2.3 MB/s | 580 kB 00:00 -------------------------------------------------------------------------------- Total 1.0 MB/s | 790 kB 00:00 Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Installing : jemalloc-4.5.0-5.fc27.x86_64 1/2 Running scriptlet: jemalloc-4.5.0-5.fc27.x86_64 1/2 Running scriptlet: redis-4.0.9-1.fc27.x86_64 2/2 Installing : redis-4.0.9-1.fc27.x86_64 2/2 Running scriptlet: redis-4.0.9-1.fc27.x86_64 2/2 Running as unit: run-r389424a3db894d8e83bddf2d54cd6d4e.service Verifying : redis-4.0.9-1.fc27.x86_64 1/2 Verifying : jemalloc-4.5.0-5.fc27.x86_64 2/2 Installed: redis.x86_64 4.0.9-1.fc27 jemalloc.x86_64 4.5.0-5.fc27 Complete!
Na systémech založených na Debianu (včetně Ubuntu) lze pro instalaci použít příkaz:
$ apt-get install redis-server
Pokud budete potřebovat použít nejnovější verzi Redisu, můžete si ho sami přeložit. Postup je jednoduchý (mj. i díky minimálním závislostem na dalších knihovnách) a je podrobně popsán na stránce https://redis.io/topics/quickstart.
Po instalaci se můžeme přesvědčit, že je skutečně k dispozici spustitelný soubor s implementací serveru i řádkového klienta:
$ whereis -b redis-cli redis-cli: /usr/bin/redis-cli $ whereis -b redis-server redis-server: /usr/bin/redis-server
3. Konfigurace vhodná pro první seznámení s Redisem
Databázový server Redis je sice možné spustit jako službu, ale pro otestování jeho základní funkcionality bude jednodušší a především bezpečnější spustit tuto aplikaci pouze lokálně – Redis sice bude stále pracovat ve funkci serveru, ale bude akceptovat pouze požadavky o připojení, které přichází ze stejného počítače a nikoli z okolních strojů. Nejprve si vytvoříme adresář, v němž bude uložena jak konfigurace, tak všechny vytvářené datové soubory. Tento adresář pojmenovaný jednoduše „redis“ vytvoříme přímo v domácím adresáři právě přihlášeného uživatele a následně se do něj přepneme:
$ mkdir redis $ cd redis
Následně přímo v tomto adresáři vytvoříme konfigurační soubor nazvaný redis.conf. Můžeme se přitom inspirovat souborem /etc/redis/redis.conf (Debian), popř. /etc/redis.conf (Fedora, RHEL, CentOS), který je však poměrně rozsáhlý, protože kromě vlastních konfiguračních voleb obsahuje i podrobné informace o významu jednotlivých konfiguračních voleb. Tento soubor je taktéž dostupný na internetu na adrese https://raw.githubusercontent.com/antirez/redis/4.0/redis.conf.
Následuje výpis obsahu konfiguračního souboru, který je připraven pro lokální spuštění Redisu, bez nebezpečí, že se k běžícímu serveru připojí případný útočník. Důležité volby jsou zvýrazněny a některé z nich budou popsány na konci kapitoly. Pokud se vám soubor nechce kopírovat, naleznete ho na adrese https://github.com/tisnik/presentations/blob/master/redis/redis.conf:
bind 127.0.0.1 protected-mode yes port 6379 tcp-backlog 511 timeout 0 tcp-keepalive 300 daemonize no supervised no pidfile /var/run/redis_6379.pid loglevel notice logfile redis.log databases 16 always-show-logo yes save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename dump.rdb dir . slave-serve-stale-data yes slave-read-only yes repl-diskless-sync no repl-diskless-sync-delay 5 repl-disable-tcp-nodelay no slave-priority 100 lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no slave-lazy-flush no appendonly yes appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-load-truncated yes aof-use-rdb-preamble no lua-time-limit 5000 slowlog-log-slower-than 10000 slowlog-max-len 128 latency-monitor-threshold 0 notify-keyspace-events "" hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-size -2 list-compress-depth 0 set-max-intset-entries 512 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 hll-sparse-max-bytes 3000 activerehashing yes client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 hz 10 aof-rewrite-incremental-fsync yes
Popišme si nyní ve stručnosti zvýrazněné konfigurační volby:
Volba | Význam |
---|---|
bind 127.0.0.1 | seznam síťových rozhraní, na kterých bude Redis přijímat požadavky (zde explicitně povolujeme jen loopback) |
protected-mode yes | pokud zapomenete na předchozí volbu a nezadáte heslo, zapne se režim přijímaní lokálních požadavků |
logfile redis.log | jméno logovacího souboru, zde uvedeno bez adresáře: bude lokální (kde spustíme server) |
dir . | adresář, do kterého se budou ukládat soubory obsahující databázi |
appendfilename „appendonly.aof“ | jméno a umístění takzvaného „append only file“ popsaného níže |
Takto nastavený Redis bude ukládat prakticky všechny své soubory do aktuálního adresáře. Jediným souborem vytvářeným mimo tento adresář bude soubor s PID běžícího Redisu, který nalezneme v /var/run/redis_6379.pid.
4. Spuštění Redisu a první experimenty se systémem
Nejprve spustíme serverovou část Redisu, a to přímo z adresáře ~/redis, protože právě zde máme uložen výše popsaný konfigurační soubor „redis.conf“
$ pwd /home/tester/redis $ redis-server redis.conf
Na druhém terminálu pak již můžeme spustit klienta Redisu, který uživatelům nabízí interaktivní příkazový řádek:
$ redis-cli 127.0.0.1:6379>
Příkazem „ping“ můžeme otestovat, jestli se klient připojí k serveru a zda od něj dokáže získávat odpovědi:
127.0.0.1:6379> ping PONG 127.0.0.1:6379> ping test "test"
127.0.0.1:6379> ping PONG 127.0.0.1:6379> PinG PONG 127.0.0.1:6379> PING PONG
Uložení hodnoty do databáze se provádí příkazem set, kterému se předá klíč (řetězec) a hodnota (taktéž řetězec):
127.0.0.1:6379> set answer 42 OK
Opakem operace set je přečtení hodnoty příkazem get. Povšimněte si, že se skutečně vrací řetězec a pokud hodnota neexistuje (resp. přesněji řečeno pokud neexistuje dvojice klíč-hodnota), vrátí se hodnota nil:
127.0.0.1:6379> get answer "42" 127.0.0.1:6379> get foobar (nil)
Vzhledem k tomu, že klíčem může být jakýkoli řetězec, můžeme se setkat s tím, že ve jménech klíčů je vyjádřena nějaká hierarchie dat. Používají se například tečky, dvojtečky nebo lomítka:
127.0.0.1:6379> set users:root "Administrator" OK 127.0.0.1:6379> set users:pavel "Tester" OK 127.0.0.1:6379> set users:petr "Developer" OK 127.0.0.1:6379> get users:petr "Developer" 127.0.0.1:6379> set users:petr "DevOps" OK 127.0.0.1:6379> get users:petr "DevOps"
Příkazem setnx taktéž do databáze ukládáme dvojici klíč-hodnota, ale pouze v případě, že daný klíč ještě neexistuje. Pokud klíč existuje, není hodnota přepsána:
127.0.0.1:6379> setnx users:petr "Developer again" (integer) 0 127.0.0.1:6379> get users:petr "DevOps" 127.0.0.1:6379> setnx users:lukas "New Developer" (integer) 1 127.0.0.1:6379> get users:lukas "New Developer"
Často se setkáme s tím, že se současně načítá větší množství dvojic klíč-hodnota příkazem mget. Eliminuje se tím počet zpráv předávaných serveru, což se pozitivně projeví na plně zatíženém systému:
127.0.0.1:6379> mget users:root users:pavel users:petr users:zdenek 1) "Administrator" 2) "Tester" 3) "DevOps" 4) (nil)
Řádkový klient obsahuje i vestavěnou nápovědu:
127.0.0.1:6379> help redis-cli 4.0.9 To get help about Redis commands type: "help @<group>" to get a list of commands in <group> "help <command>" for help on <command> "help <tab>" to get a list of possible help topics "quit" to exit To set redis-cli preferences: ":set hints" enable online hints ":set nohints" disable online hints Set your preferences in ~/.redisclirc
Po ukončení interaktivního shellu můžeme ukončit i běh samotného serveru Redisu, například stiskem Ctrl+C v terminálu, kde server běží, popř. příkazem shutdown zadaným z řádkového klienta:
127.0.0.1:6379> shutdown not connected>
Nyní by se v aktuálním adresáři ~/redis/ měla nacházet čtveřice souborů:
$ ls -1 appendonly.aof dump.rdb redis.conf redis.log
V dalších kapitolách si vysvětlíme především význam souboru dump.rdb a appendonly.aof.
5. Perzistentní uložení databáze
Databázi Redis je možné nakonfigurovat mnoha různými způsoby. Pro jednoduchost předpokládejme, že celá databáze bude provozována na jediném stroji, tj. nebudeme používat ani horizontální škálování ani replikace. Takto nakonfigurovaná databáze může být uložena buď pouze v operační paměti (typické použití – cache), nebo ji lze ukládat i do nevolatilní paměti (pevný disk, SSD, …). Existují čtyři prakticky používané kombinace:
- Databáze je uložena jen v RAM
- Použití kombinace RAM + soubory RDB
- Použití kombinace RAM + soubory AOF
- Použití kombinace RAM + soubory RDB i AOF
Na discích, resp. obecně na paměťových médiích, je databáze ukládána do souborů s koncovkou .rdb. Jedná se o binární soubory, které jsou navrženy takovým způsobem, aby práce s nimi byla velmi rychlá. V případě potřeby se používá komprimace LZW pro zmenšení velikosti těchto souborů. Interně je před každým objektem uloženým v tomto souboru specifikována i velikost objektu, což zjednodušuje načítání .rdb zpět do operační paměti. Na konci souboru je navíc (v závislosti na konfiguraci) uložen 64bitový kontrolní součet, který je možné použít pro jednoduchou kontrolu konzistence dat.
Mimochodem, konzistenci samotného souboru s obrazem databáze je možné zkontrolovat příkazem redis-check-rdb:
$ redis-check-rdb dump.rdb [offset 0] Checking RDB file dump.rdb [offset 26] AUX FIELD redis-ver = '4.0.9' [offset 40] AUX FIELD redis-bits = '64' [offset 52] AUX FIELD ctime = '1541800518' [offset 67] AUX FIELD used-mem = '513184' [offset 83] AUX FIELD aof-preamble = '0' [offset 85] Selecting DB ID 0 [offset 455] Checksum OK [offset 455] \o/ RDB looks OK! \o/ [info] 18 keys read [info] 0 expires [info] 0 already expired
Jak se vlastně soubory RDB vytváří? Frekvence tvorby těchto souborů je plně konfigurovatelná administrátorem, který například může specifikovat, že se tyto soubory budou obnovovat každých pět minut, po zápisu deseti prvků atd. Samotný zápis probíhá do nového souboru a teprve po provedení celého zápisu (a provedení flush) se provede přejmenování souboru, což je – minimálně v Linuxu – atomická operace. Vždy tedy budeme mít k dispozici buď dvojici starý_RDB + potenciálně_neúplný_nový_RDB nebo nový_RDB; nemělo by dojít k situaci, kdy je starý RDB vymazán a nový ještě není konzistentní.
6. AOF – Append Only File
Víme již, že kromě obrazu databáze ukládaného do souborů RDB, je možné použít takzvané Append Only File(s) neboli AOF. Jedná se o textové a tudíž i snadno čitelné textové soubory, do nichž se postupně ukládají všechny příkazy, které modifikují obsah databáze (v praxi to znamená, že zde například nenalezneme příkazy GET). Při opětovném startu Redisu je tedy možné, aby server tento soubor „přehrál“ (vykonal všechny v něm uložené příkazy) a databázi tak obnovil. Použití souborů AOF je možné zakázat či povolit a při jejich povolení je navíc možné specifikovat, jak často se budou provádět zápisy příkazů. Platí jednoduché a pochopitelné pravidlo – čím častěji budeme chtít provádět zápisy (skutečné zápisy následované příkazem flush), tím více klíčů se nám podaří obnovit, ovšem zápisy AOF na druhou stranu zpomalí všechny zápisy do databáze (čtení není nijak ovlivněno). Záleží tedy na administrátorech databáze a samozřejmě též na aplikaci, které databázi používá, kolik potenciálně ztracených dat je možné tolerovat (údaje z poslední minuty? poslední dva zápisy? atd.).
Pokud se podíváme do souboru nazvaného appendonly.aof (viz třetí kapitolu s konfigurací, kde jsme specifikovali umístění tohoto souboru do aktuálního adresáře), měli bychom vidět následující obsah, který odpovídá operacím, které jsme v databázi provedli. Samozřejmě zůstává zachováno pořadí těchto operací. Je tomu tak z toho důvodu, že „přehráním“ AOF souboru bychom měli dostat přesný obraz databáze:
*2 $6 SELECT $1 0 *3 $3 set $6 answer $2 42
7. Nastavení doby životnosti dat
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 |
Podívejme se nyní na způsob použití příkazu expire. Již v předchozích kapitolách byly do databáze uloženy informace o několika uživatelích, ale klidně můžeme tyto příkazy zopakovat:
127.0.0.1:6379> set users:root "Administrator" OK 127.0.0.1:6379> set users:pavel "Tester" OK 127.0.0.1:6379> set users:petr "Developer" OK
Dobu životnosti jednotlivých záznamů lze ovlivnit příkazem expire, přičemž TTL je v nejjednodušším případě nastaveno v sekundách:
127.0.0.1:6379> expire users:pavel 10 (integer) 1 127.0.0.1:6379> expire users:petr 5 (integer) 1
Pokus o načtení čtyř záznamů ihned po provedení předchozích příkazů:
127.0.0.1:6379> mget users:root users:pavel users:petr users:zdenek 1) "Administrator" 2) "Tester" 3) "DevOps" 4) (nil)
Po čtvrtminutě ovšem dostaneme rozdílné výsledky:
127.0.0.1:6379> mget users:root users:pavel users:petr users:zdenek 1) "Administrator" 2) (nil) 3) (nil) 4) (nil)
Aktuální dobu životnosti můžeme zjistit příkazem ttl:
127.0.0.1:6379> expire users:root 20 (integer) 1 127.0.0.1:6379> ttl users:root (integer) 16 127.0.0.1:6379> ttl users:root (integer) 10 127.0.0.1:6379> ttl users:root (integer) 0 127.0.0.1:6379> ttl users:root (integer) -2
Zajímavá je poslední hodnota. Příkaz ttl totiž vrací buď přirozené číslo vyjadřující zbývající životnost, nebo zápornou hodnotu s tímto významem:
- –1: klíč existuje, ale nemá přiřazenou životnost (je neomezená)
- –2: klíč už neexistuje, takže záznam již byl pravděpodobně odstraněn
8. Podporované datové typy, s nimiž Redis pracuje
Při ukládání a zpracování dat ukládaných do Redisu je možné data reprezentovat několika datovými typy. Jména jednotlivých typů jsou vypsána v tabulce pod tímto odstavcem a v navazujících kapitolách si řekneme o vybraných typech některé bližší informace (zaměřené spíše na jejich praktické použití):
Jméno | Stručná charakteristika |
---|---|
string | řetězce, které lze ovšem využít i pro práci s celými čísly i čísly s FP |
list | seznamy, ve skutečnosti se s nimi pracuje jako se zásobníkem a frontou |
set | množiny, je zaručena unikátnost prvků |
sorted set | množiny, v nichž jsou jednotlivé prvky ohodnoceny skórem |
hash | mapy (též asociativní pole) |
bitmap (bit array) | pole bitů, interně mapovány na řetězce |
HyperLogLogs (HLL) | datová struktura používaná pro zjištění počtu unikátních prvků (s určitou chybou, ovšem s malou spotřebou paměti) |
9. Práce s řetězci uloženými do databáze
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. 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.
Mezi základní příkazy pro práci s řetězci patří nám již známé příkazu set a get. Ovšem řetězce lze i modifikovat příkazem append a můžeme získat délku řetězce pomocí strlen:
127.0.0.1:6379> set z "" OK 127.0.0.1:6379> append z "Hello" (integer) 5 127.0.0.1:6379> append z " " (integer) 6 127.0.0.1:6379> append z "world!" (integer) 12 127.0.0.1:6379> get z "Hello world!" 127.0.0.1:6379> strlen z (integer) 12
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. Podívejme se na příklady (spouštěné přímo z redis-cli:
127.0.0.1:6379> set x 0 OK 127.0.0.1:6379> INCR x (integer) 1 127.0.0.1:6379> get x "1" 127.0.0.1:6379> INCRBY x 100 (integer) 101 127.0.0.1:6379> get x "101" 127.0.0.1:6379> DECR x (integer) 100 127.0.0.1:6379> get x "100"
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). Příklad na interpretaci řetězce jako čísla s plovoucí řádovou čárkou:
127.0.0.1:6379> set y 0.5 OK 127.0.0.1:6379> INCRBYFLOAT y 0.3 "0.8" 127.0.0.1:6379> get y "0.8"
Řetězec „0.8“ ovšem již není možné považovat za reprezentaci celého čísla:
127.0.0.1:6379> incr y (error) ERR value is not an integer or out of range
127.0.0.1:6379> type x string 127.0.0.1:6379> type y string 127.0.0.1:6379> type z string
10. 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 |
Podívejme se nyní na několik příkladů. Seznam uložený pod klíčem „l“ se automaticky vytvoří hned prvním příkazem:
127.0.0.1:6379> lpush l 3 (integer) 1 127.0.0.1:6379> lpush l 2 (integer) 2 127.0.0.1:6379> lpush l 1 (integer) 3 127.0.0.1:6379> llen l (integer) 3 127.0.0.1:6379> rpush l 1 (integer) 4 127.0.0.1:6379> rpush l 2 (integer) 5 127.0.0.1:6379> rpush l 3 (integer) 6 127.0.0.1:6379> llen l (integer) 6
Typ hodnoty uložené pod klíčem „l“:
127.0.0.1:6379> type l list
Seznam nelze přečíst operací get:
127.0.0.1:6379> get l (error) WRONGTYPE Operation against a key holding the wrong kind of value
Čtení prvků ze začátku i konce seznamu s jejich postupným odstraňováním:
127.0.0.1:6379> lpop l "1" 127.0.0.1:6379> lpop l "2" 127.0.0.1:6379> lpop l "3" 127.0.0.1:6379> lpop l "1" 127.0.0.1:6379> lpop l "2" 127.0.0.1:6379> lpop l "3"
Pokus o přečtení hodnoty z prázdného seznamu:
127.0.0.1:6379> lpop l (nil)
11. Množiny
Dalším datovým typem, s nímž je možné v Redisu pracovat, jsou množiny. Každá množina může obsahovat až 232-1 prvků, což je stejná kapacita, jako u seznamů. Prvky se do množiny přidávají příkazem sadd. Pokud množina neexistuje, je prvním příkazem sadd vytvořena:
127.0.0.1:6379> sadd s "foo" (integer) 1 127.0.0.1:6379> sadd s "bar" (integer) 1 127.0.0.1:6379> sadd s "baz" (integer) 1
Počet prvků v množině získáme příkazem scard, seznam všech prvků pak příkazem smembers:
127.0.0.1:6379> scard s (integer) 3 127.0.0.1:6379> smembers s 1) "bar" 2) "baz" 3) "foo"
Test, jestli množina obsahuje nějaký prvek, zajistí příkaz sismember, který vrací numerickou hodnotu 0 nebo 1:
127.0.0.1:6379> sismember s "foo" (integer) 1 127.0.0.1:6379> sismember s "xyzzy" (integer) 0
Odstranění prvku z množiny zajistí příkaz srem, který navíc vrátí příznak, zda byl prvek odstraněn (tj. zda vůbec v množině figuroval):
127.0.0.1:6379> srem s "foo" (integer) 1 127.0.0.1:6379> sismember s "foo" (integer) 0 127.0.0.1:6379> smembers s 1) "bar" 2) "baz"
12. Množinové operace
Systém Redis podporuje provádění základních množinových operací – sjednocení, průniku a rozdílu. Tyto operace jsou vyvolány příkazy sunion, sunionstore, sinter, sinterstore, sdiff a sdiffstore.
Před ukázkou těchto operací si vytvoříme dvě množiny s1 a s2.
Naplnění množiny s1:
127.0.0.1:6379> sadd s1 1 (integer) 1 127.0.0.1:6379> sadd s1 2 (integer) 1 127.0.0.1:6379> sadd s1 3 (integer) 1 127.0.0.1:6379> sadd s1 4 (integer) 1
Naplnění množiny s2:
127.0.0.1:6379> sadd s2 3 (integer) 1 127.0.0.1:6379> sadd s2 4 (integer) 1 127.0.0.1:6379> sadd s2 5 (integer) 1 127.0.0.1:6379> sadd s2 6 (integer) 1
Obsah obou množin:
127.0.0.1:6379> smembers s1 1) "1" 2) "2" 3) "3" 4) "4" 127.0.0.1:6379> smembers s2 1) "3" 2) "4" 3) "5" 4) "6"
Příkazem sunionstore vytvoříme novou množinu, která bude sjednocením obou množin zdrojových:
127.0.0.1:6379> sunionstore s3 s1 s2 (integer) 6 127.0.0.1:6379> smembers s3 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" 6) "6"
Podobně je možné příkazem sinterstore vytvořit novou množinu pomocí operace průniku:
127.0.0.1:6379> sinterstore s4 s1 s2 (integer) 2 127.0.0.1:6379> smembers s4 1) "3" 2) "4"
Poslední podporovanou operací je rozdíl množin. Tato operace není komutativní, takže je pochopitelně rozdíl mezi rozdílem s1\s2 a s2\s1:
127.0.0.1:6379> sdiffstore s5 s1 s2 (integer) 2 127.0.0.1:6379> smembers s5 1) "1" 2) "2" 127.0.0.1:6379> sdiffstore s6 s2 s1 (integer) 2 127.0.0.1:6379> smembers s6 1) "5" 2) "6"
13. Mapy (asociativní pole)
Jedním z nejpoužívanějších datových typů v Redisu jsou mapy neboli asociativní pole. Každá mapa může obsahovat 232-1 dvojic klíč-hodnota, přičemž klíčem jsou řetězce. Příkazy pro práci s asociativními poli začínají prefixem „H“. Základním příkazem je hset pro uložení dvojice klíč-hodnota do množiny, ovšem častěji se setkáme s příkazem hmset, který umožňuje uložit větší množství dvojic jedinou operací. Opakem jsou operace hget pro přečtení jednoho prvku nebo hgetall pro přečtení všech dvojic (formou tabulky, tj. tabulka má dvakrát více prvků, než asociativní pole):
127.0.0.1:6379> hset apole x 1 (integer) 1 127.0.0.1:6379> hset apole y 2 (integer) 1 127.0.0.1:6379> hget apole x "1" 127.0.0.1:6379> hget apole z (nil)
Přepis existující hodnoty:
127.0.0.1:6379> hset apole x "nova hodnota" (integer) 0 127.0.0.1:6379> hget apole x "nova hodnota"
Použití příkazů hmset a hgetall:
127.0.0.1:6379> hmset user:1000 username antirez password P1pp0 age 34 OK 127.0.0.1:6379> hgetall user:1000 1) "username" 2) "antirez" 3) "password" 4) "P1pp0" 5) "age" 6) "34" 127.0.0.1:6379> hset user:1000 password 12345 (integer) 0 127.0.0.1:6379> hgetall user:1000 1) "username" 2) "antirez" 3) "password" 4) "12345" 5) "age" 6) "34"
14. Množiny s ohodnocenými prvky (uspořádané množiny)
Posledním datovým typem Redisu, o kterém se dnes zmíníme, jsou množiny s ohodnocenými prvky. Každému prvku je přiřazeno číslo, které je následně použito při porovnávání jednotlivých prvků (množina je tedy částečně uspořádaná), při zpětném čtení prvků apod. Příkazy, které s tímto datovým typem pracují, začínají prefixem „z“. Nejprve vytvoříme novou množinu a přidáme do ní několik prvků. Zadané číslo odpovídá ohodnocení prvků:
127.0.0.1:6379> zadd set 100 x (integer) 1 127.0.0.1:6379> zadd set 150 y (integer) 1 127.0.0.1:6379> zadd set 50 z (integer) 1 127.0.0.1:6379> zadd set -5 w (integer) 1
Hodnota ve skutečnosti může být reálné číslo, nikoli pouze číslo celé:
127.0.0.1:6379> zadd set 0.5 a (integer) 1
Aktuální pořadí prvku (na základě jeho ohodnocení) přečteme příkazem zrank:
127.0.0.1:6379> zrank set x (integer) 3 127.0.0.1:6379> zrank set w (integer) 0 127.0.0.1:6379> zrank set foo (nil)
Dále můžeme získat počet prvků množiny příkazem zcard, popř. příkazem zcount zjistit počet takových prvků, jejichž skóre leží v nějakém zadaném intervalu:
127.0.0.1:6379> zcard set (integer) 4 127.0.0.1:6379> zcount set -1000 1000 (integer) 4 127.0.0.1:6379> zcount set 0 1000 (integer) 3
Ohodnocení lze změnit příkazem zincrby, kterému se zadá relativní přírůstek (samozřejmě může být i záporný):
127.0.0.1:6379> zrank set x (integer) 3 127.0.0.1:6379> zincrby set 1000 x "1100" 127.0.0.1:6379> zrank set x (integer) 4 127.0.0.1:6379> zincrby set -2000 x "-900" 127.0.0.1:6379> zrank set x (integer) 0
15. Další operace s uspořádanými množinami
Nad uspořádanými množinami je možné provádět i další operace, například získat ty prvky, jejichž ohodnocení se nachází mezi specifikovanými mezními hodnotami:
127.0.0.1:6379> zrangebyscore set 1 200 1) "z" 2) "y" 127.0.0.1:6379> zrangebyscore set 1 300 1) "z" 2) "y" 3) "x"
Příkazem zremrangebyscore lze odstranit prvky, jejichž ohodnocení (skóre) se nachází mezi mezními hodnotami:
127.0.0.1:6379> zremrangebyscore set 1 200 (integer) 2 127.0.0.1:6379> zrangebyscore set -1000 1000 1) "w" 2) "x"
Pokus o vymazání prvků se skóre, které v množině neleží:
127.0.0.1:6379> zrangebyscore set 1 200 (empty list or set)
16. Transakce
Redis taktéž podporuje transakce. U příkazů, které jsou součástí transakce, jsou dodrženy následující podmínky:
- Všechny příkazy v transakci jsou provedeny v takovém pořadí, v jakém jsou zapsány uživatelem.
- Mezi tyto příkazy se nikdy nevmísí příkazy vyžadované jiným klientem.
- Transakce je atomická: buď se provede celá (všechny příkazy), nebo se neprovede vůbec.
Pro práci s transakcemi jsou určeny příkazy multi, exec a discard. Podívejme se nyní na velmi jednoduchý „školní“ příklad, v němž se převádí nějaká částka mezi dvěma účty „ucet1“ a „ucet2“:
127.0.0.1:6379> set ucet1 1000.0 OK 127.0.0.1:6379> set ucet2 0.0 OK
Tuto peněžní transakci musíme provést v databázové transakci, tj. zadáme ji mezi příkazy multi a exec:
127.0.0.1:6379> multi OK 127.0.0.1:6379> incrbyfloat ucet1 -150.50 QUEUED 127.0.0.1:6379> incrbyfloat ucet2 +150.50 QUEUED 127.0.0.1:6379> exec 1) "849.5" 2) "150.5"
Výsledné hodnoty na účtu po provedení transakce:
127.0.0.1:6379> get ucet1 "849.5" 127.0.0.1:6379> get ucet2 "150.5"
127.0.0.1:6379> multi OK 127.0.0.1:6379> multi (error) ERR MULTI calls can not be nested
Redis navíc neumožňuje ani rollback v případě, že při zpracování transakce dojde k chybě nějakého příkazu.
17. Použití Redisu z Pythonu
Ve druhé části dnešního článku si ukážeme, jakým způsobem lze používat databázi Redis z programovacího jazyka Python. Pro Redis samozřejmě existuje knihovna (přesněji řečeno dokonce více knihoven) s rozhraním k Redisu, které je – alespoň při použití běžných datových typů – velmi jednoduše použitelné. Nejprve samozřejmě musíme příslušnou knihovnu nainstalovat, což je otázka několika sekund. Postačuje použít příkaz pip popř. pip3 pro instalaci knihovny redis. Pro jistotu instalaci provedeme pouze pro aktivního uživatele, takže se knihovna i její metadata uloží do adresáře ~/.local/lib/python{VERZE}/site-packages/:
$ pip3 install --user redis Collecting redis Downloading https://files.pythonhosted.org/packages/3b/f6/7a76333cf0b9251ecf49efff635015171843d9b977e4ffcf59f9c4428052/redis-2.10.6-py2.py3-n 100% |████████████████████████████████| 71kB 909kB/s Installing collected packages: redis Successfully installed redis-2.10.6
Pokud instalace knihovny redis proběhla v pořádku, můžeme si ji vyzkoušet. Nejprve spustíme interaktivní smyčku Pythonu:
$ python3 Python 3.6.3 (default, Oct 9 2017, 12:11:29) [GCC 7.2.1 20170915 (Red Hat 7.2.1-2)] on linux Type "help", "copyright", "credits" or "license" for more information.
V interpretru Pythonu nyní nejprve naimportujeme modul redis.
>>> import redis
Můžeme si vyzkoušet zobrazit si nápovědu:
>>> help("redis")
S výsledkem:
Help on package redis: NAME redis PACKAGE CONTENTS _compat client connection exceptions lock sentinel utils CLASSES builtins.Exception(builtins.BaseException) redis.exceptions.RedisError redis.exceptions.AuthenticationError redis.exceptions.ConnectionError redis.exceptions.BusyLoadingError
Ovšem důležitější je samozřejmě vlastní rozhraní k databázi Redisu. Nejprve vytvoříme objekt typu Redis zavoláním stejnojmenné funkce, které se předá adresa a port Redis serveru, na který se klient pokusí připojit. Náš server naslouchá na loopbacku, konkrétně na portu 6379:
>>> r = redis.Redis(host='127.0.0.1', port=6379) >>> r Redis<ConnectionPool<Connection<host=127.0.0.1,port=6379,db=0>>>
V případě, že se připojení podařilo, můžeme se pokusit přečíst z databáze hodnotu uloženou pod klíčem „answer“. To je jednoduché, protože postačuje použít metodu get():
>>> r.get("answer") b'42' >>> print(r.get("answer")) b'42'
V případě, že se pokusíme přečíst hodnotu pod neexistujícím klíčem, vrátí se None:
>>> r.get("foo") >>> print(r.get("foobarbaz")) None
Samozřejmě je možné do databáze i zapisovat, a to v tom nejjednodušším případě metodou set:
>>> r.set("foo", -1) True >>> r.get("foo") b'-1'
Zjištění délky seznamu:
>>> r.llen("l") 1
Další příkazy si popíšeme v navazujícím textu.
18. Vybrané operace poskytované knihovnou redis
Vyzkoušejme si nyní některé další operace, které jsou poskytovány knihovnou redis a které jsou tedy přístupné všem uživatelům Pythonu.
Nejprve si ukážeme nastavení řetězcové hodnoty a její modifikaci připojením dalšího řetězce:
>>> r.set("greeting", "") True >>> r.append("greeting", "Hello") 5 >>> r.append("greeting", " ") 6 >>> r.append("greeting", "world!") 12 >>> r.get("greeting") b'Hello world!'
Nastavení řetězce a použití příkazů pro zvýšení a snížení numerické hodnoty reprezentované řetězcem:
>>> r.set("x", 10) True >>> r.incr("x", 20) 30 >>> r.get("x") b'30' >>> r.decr("x", 1000) -970 >>> r.get("x") b'-970'
Použití prvku nazvaného „zasobnik“ ve funkci skutečného zásobníku s operacemi PUSH a POP:
>>> r.lpush("zasobnik", 1) 1 >>> r.lpush("zasobnik", 10) 2 >>> r.lpop("zasobnik") b'10' >>> r.lpop("zasobnik") b'1' >>> r.lpop("zasobnik")
Použití prvku nazvaného „fronta“ ve funkci jednosměrné fronty s operacemi ENQUEUE a DEQUEUE (realizovanými jinak pojmenovanými operacemi Redisu):
>>> r.lpush("fronta", 1) 1 >>> r.lpush("fronta", 10) 2 >>> r.rpop("fronta") b'1' >>> r.rpop("fronta") b'10' >>> r.rpop("fronta")
19. Využití „pipeline“ pro sloučení většího množství příkazů do jediného požadavku
Velmi užitečný je v praxi objekt typu „pipeline“. Ten umožňuje specifikovat větší množství příkazů, které se následně přenesou do Redisu (na server) v jediném balíku. V praxi se totiž může ukázat, že přenos jednotlivých příkazů je úzkým hrdlem celého systému. Nejprve si ukažme, jakým způsobem se „pipeline“ použije pouze pro seskupení příkazů, nikoli pro provedení příkazů v transakci:
>>> pipe = r.pipeline(transaction=False) >>> for i in range(1, 11): ... pipe.lpush("list1", i) ... pipe.rpush("list2", i) ... Pipeline<ConnectionPool<Connection<host=127.0.0.1,port=6379,db=0>>> ... ... ... >>> pipe.execute() [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10]
Povšimněte si, že objekt typu pipeline nabízí stejné metody, jako objekt typu Redis. Teprve po spuštění metody pipe.execute() se všechny operace (v našem případě dvacet operací PUSH přenesou na server a tam provedou. Server odpoví seznamem výsledků těchto příkazů (tento seznam jen vypíšeme, ale jinak ho můžeme ignorovat).
Test, zda se skutečně změnil obsah objektů pod klíči list1 a list2:
>>> while True: ... item = r.lpop("list1") ... if item is None: ... break ... print(item) ... b'10' b'9' b'8' b'7' b'6' b'5' b'4' b'3' b'2' b'1'
>>> while True: ... item = r.lpop("list2") ... if item is None: ... break ... print(item) ... b'1' b'2' b'3' b'4' b'5' b'6' b'7' b'8' b'9' b'10'
Objekt typu pipeline je ovšem možné použít i pro provedení transakce. Podívejme se na prozatím velmi jednoduchou transakci, která odpovídá demonstračnímu příkladu, s nímž jsme se seznámili v šestnácté kapitole (jednalo se o převod peněžní částky mezi dvěma účty):
>>> r.set("ucet1", 1000.0) True >>> r.set("ucet2", 0.0) True >>> pipe = r.pipeline(transaction=True) >>> pipe.incrbyfloat("ucet1", -150.50) Pipeline<ConnectionPool<Connection<host=127.0.0.1,port=6379,db=0>>> >>> pipe.incrbyfloat("ucet2", 150.50) Pipeline<ConnectionPool<Connection<host=127.0.0.1,port=6379,db=0>>> >>> pipe.execute() [849.5, 150.5] >>> r.get("ucet1") b'849.5' >>> r.get("ucet2") b'150.5'
20. Odkazy na Internetu
- Stránky projektu Redis
https://redis.io/ - Introduction to Redis
https://redis.io/topics/introduction - Try Redis
http://try.redis.io/ - Redis tutorial, April 2010 (starší, ale pěkně udělaný)
https://static.simonwillison.net/static/2010/redis-tutorial/ - Python Redis
https://redislabs.com/lp/python-redis/ - Redis: key-value databáze v paměti i na disku
https://www.zdrojak.cz/clanky/redis-key-value-databaze-v-pameti-i-na-disku/ - Praktický úvod do Redis (1): vaše distribuovaná NoSQL cache
http://www.cloudsvet.cz/?p=253 - Praktický úvod do Redis (2): transakce
http://www.cloudsvet.cz/?p=256 - Praktický úvod do Redis (3): cluster
http://www.cloudsvet.cz/?p=258 - Connection pool
https://en.wikipedia.org/wiki/Connection_pool - Instant Redis Sentinel Setup
https://github.com/ServiceStack/redis-config - How to install REDIS in LInux
https://linuxtechlab.com/how-install-redis-server-linux/ - Redis RDB Dump File Format
https://github.com/sripathikrishnan/redis-rdb-tools/wiki/Redis-RDB-Dump-File-Format - Lempel–Ziv–Welch
https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch - Redis Persistence
https://redis.io/topics/persistence - Redis persistence demystified
http://oldblog.antirez.com/post/redis-persistence-demystified.html - Redis reliable queues with Lua scripting
http://oldblog.antirez.com/post/250 - Ost (knihovna)
https://github.com/soveran/ost - NoSQL
https://en.wikipedia.org/wiki/NoSQL - Shard (database architecture)
https://en.wikipedia.org/wiki/Shard_%28database_architecture%29 - What is sharding and why is it important?
https://stackoverflow.com/questions/992988/what-is-sharding-and-why-is-it-important - What Is Sharding?
https://btcmanager.com/what-sharding/ - Redis clients
https://redis.io/clients - Category:Lua-scriptable software
https://en.wikipedia.org/wiki/Category:Lua-scriptable_software - Seriál Programovací jazyk Lua
https://www.root.cz/serialy/programovaci-jazyk-lua/ - Redis memory usage
http://nosql.mypopescu.com/post/1010844204/redis-memory-usage - Ukázka konfigurace Redisu pro lokální testování
https://github.com/tisnik/presentations/blob/master/redis/redis.conf - Resque
https://github.com/resque/resque - Nested transaction
https://en.wikipedia.org/wiki/Nested_transaction