- Praktický úvod do Redis (1): vaše distribuovaná NoSQL cache
- Praktický úvod do Redis (2): transakce
- Praktický úvod do Redis (3): cluster
V minulém díle jsme se seznámili se základy práce s Redis, dnes se zaměříme na jedno pokročilejší témata – transakce.
Transakce
První potíž běžné práce s Redis spočívá v tom, že může být situace, kdy chceme nastavit hodnoty dvou klíčů, které spolu souvisí. Tedy nemá smysl nastavit jen jednu, potřebujeme obě nebo nic. Tedy nastavení dvou klíčů musí být jedna atomární operace. To při běžném použití není – každá operace je zvlášť, takže když naše aplikace umře po provedení první, druhou už nedopíše a to není stav, který chceme. Například potřebujeme nastavit mantinely čehosi a definovat minimum a maximum – nemá smysl mít pouze jedno, buď oboje, nebo nic.
V Redis použijeme sekvenci multi, ve které říkáme: následující příkazy zatím neprováděj, ale dej si je do fronty. Následně příkazem exec je provedeme všechny najednou s tím, že Redis garantuje, že tato sekvence nebude přerušena žádným jiným klientem, který by toho skočil. Zajišťujeme tedy sekvenci a atomicitu celé operace:
127.0.0.1:6379> multi OK 127.0.0.1:6379> set min 10 QUEUED 127.0.0.1:6379> set max 30 QUEUED 127.0.0.1:6379> exec 1) OK 2) OK
To ale není hlavní problém – je tu ještě něco důležitějšího. Podívejte se následující kód v Python a zvažte, v čem je problém:
cloudsvet@ubuntu:~$ python Python 2.7.6 (default, Mar 22 2014, 22:59:56) [GCC 4.8.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import redis >>> mujredis = redis.Redis('127.0.0.1') >>> mujredis.set('citac', 1) True >>> novahodnota = int(mujredis.get('citac')) + 1 >>> mujredis.set('citac', novahodnota) True >>> mujredis.get('citac') '2'
Tedy načteme si hodnotu čitače (třeba kolikrát se stala nějaká operace, třeba objednání zboží), přičteme k němu jedničku a tuto hodnotu následně zapíšeme. V čem je problém? Pokud tuhle operaci provádí současně pouze jeden klient, tak samozřejmě v ničem. Co když ale dva klienti udělají přesně to samé a to zrovna v úplně stejném okamžiku? Oba si načtou hodnotu 1, oba přičtou jedničku a oba zapíší dvojku. Co bude uloženo? No dvojka … ale ve skutečnosti se operace stala dvakrát, takže výsledkem má být 3. Jak tomu zabránit? Redis umožňuje sledovat nějaký klíč a při finální exekuci sekvence operací se přesvědčit, že nedošlo k jeho modifikaci. Pokud se to nastalo, je vše v pořádku. Pokud ale hodnotu někdo změnil, vycházíme ze špatného počátečního stavu a operace se neprovede. Musíme to tedy zkusit znova a doufat, že se tento souběh okolností už nebude opakovat. Tak zajistíme transakčně správný výsledek.
V Python k tomu použijeme objekt pipeline a to takto:
cloudsvet@ubuntu:~$ python Python 2.7.6 (default, Mar 22 2014, 22:59:56) [GCC 4.8.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> mujredis.set('citac2', 1) True >>> mujredis.watch('citac2') >>> hodnota = int(mujredis.get('citac2')) + 1 >>> pipe = mujredis.pipeline() >>> pipe.set('citac2', hodnota) Pipeline<ConnectionPool<Connection<host=127.0.0.1,port=6379,db=0>>> >>> pipe.execute() [True] >>> mujredis.get('citac2') '2' >>>
Pokud byste to chtěli zkoušet v redis-cli, tak takto – nejprve nechte sledovat nějaký klíč a set operaci uzavřete do multi/exec. Pokud dojde mezitím ke změně hodnoty klíče, vás zápis bude odmítnut a víte, co máte dělat:
127.0.0.1:6379> set citac 1 OK 127.0.0.1:6379> watch citac OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> set citac 2 QUEUED 127.0.0.1:6379> exec 1) OK
Příklad použití s čitačem je samozřejmě velmi častý, takže Redis vám to zjednodušuje zavedení transakčně korektních příkazů – popsanou sekvenci tak můžete nechat na složitější situace:
127.0.0.1:6379> set citac 1 OK 127.0.0.1:6379> incr citac (integer) 2 127.0.0.1:6379> get citac "2" 127.0.0.1:6379> incrby citac 5 (integer) 7 127.0.0.1:6379> get citac "7" 127.0.0.1:6379> decr citac (integer) 6 127.0.0.1:6379> get citac "6"
Takhle tedy fungují transakce v Redis – nic složitého, ne?