- Storing objects in Redis with Hashes
- HSET: Sets an individual field/value pair wtihin a hash.
- HGETALL: Returns all of the field/value pairs of a specified key.
- HGET: Returns the value associated with a specified field in the hash stored at a specified key.
- HSCAN: Incrementally iterates over the field/value pairs of a Hash
- HINCRBY: Increments the number stored at a specified field in the hash stored at a sepcified key by an increment value.
- Uploading an image into a Hash Field
- Transactions
- Pub/Sub
For our Hash segment we'll be using the Redis team's pets as examples.
HSET: Sets an individual field/value pair wtihin a hash.
One of Justin's pet chickens is laying eggs! Update the specific hash to contain the new field value pair laying_eggs: True
>HSET user:justin:pet:josie laying_eggs True
(integer) 1
Remember that Redis Hashes - like Redis - are schemaless. We are not bound to maintaining similar field/value pairs among our data types.
HGETALL: Returns all of the field/value pairs of a specified key.
Lets check out the entry for Steve's dog, Honey:
>HGETALL user:steve:pet:honey
1) "name"
2) "Honey"
3) "age"
4) "5"
5) "weight"
6) "71"
7) "number_of_legs"
8) "4"
9) "species"
10) "dog"
11) "breed"
12) "Greyhound"
13) "favourite_toy"
14) "Cucumber Squeaker"
HGETALL
is considered a dangerous command as returning all of the field/value pairs of a hash could potentially block the Redis instance if there is a high quantity of data to return.HGET
andHSCAN
is preferred.
HGET: Returns the value associated with a specified field in the hash stored at a specified key.
Find the favourite_toy field of Rachel's cat named Sputnik:
>HGET user:rachel:pet:sputnik
"Moppy Ball"
HSCAN: Incrementally iterates over the field/value pairs of a Hash
HSCAN
will iterate through a specified hash and return the field/value pairs it traverses as well as a cursor position of where it stopped before terminating. You would then call the HSCAN
command again with this returned cursor; this then continues scanning where it previously left off. Note that because Redis is memory optimized, smaller data types and aggregations may return the entire data type it is scanning. It is only when a data type reaches a larger threshold (and thus is converted into a hash table) will you need to use a cursor.
HINCRBY: Increments the number stored at a specified field in the hash stored at a sepcified key by an increment value.
Increment the weight of KyleO's dog Denali by 3 pounds:
>HINCRBY user:kyleO:pet:denali weight 3
(integer) 45
Note that the return value is the updated value of the field weight. To decrement a field, use negative numbers
Lets store an image in one of our hashes. To accomplish this, we'll store a base64 encoded copy of an image into an image
field as a string.
From the command-line, not the redis-cli:
$ redis-cli -x HSET user:simon:pet:spot image < base64 spot.JPG
To fetch and decode our image, we'll again call a redis-cli command from the regular command-line:
$ redis-cli HGET user:simon:pet:spot image > spot_image.base64
$ base64 -d spot_image.base64 > spot_image.jpg
Now lets open spot_image.jpg
and see Spot at play:
$ open spot_image.jpg
MULTI, EXEC, DISCARD and WATCH are the foundation of the transaction structure in Redis. They allow the execution of a group of commands in a single step, with two important guarantees:
-
All the commands in a transaction are serialized and executed sequentially.
-
Either all of the commands or none are processed, so a Redis transaction is also atomic.
This prevents 'in-between' conditions when values are not what they should be when being evaluated by other clients. Lets try an example with our pet Hashes from before.
What happens when two of the pets swap toys, such as when Rachel's cat Sputnik and Justin's chicken Josie swap a Moppy Ball and a Cherry Tomato? Lets evaluate the necessary commands and states of each step:
1. Here are the starting values of the two relevant Hash objects:
key | field | value |
---|---|---|
user:rachel:pet:sputnik |
favourite_toy |
"Moppy Ball" |
user:justin:pet:josie |
favourite_toy |
"Cherry Tomato" |
2. Update Sputnik's toy to a "Cherry Tomato":
>HSET user:rachel:pet:sputnik favourite_toy "Cherry Tomato"
Resulting values of both keys:
key | field | value |
---|---|---|
user:rachel:pet:sputnik |
favourite_toy |
"Cherry Tomato" |
user:justin:pet:josie |
favourite_toy |
"Cherry Tomato" |
Notice that at this point, both keys have the value "Cherry Tomato"
. This isn't quite right, as there is only one toy to swap from Josie the chicken.
Both Sputnik and Josie can't have the same toy at the same time. This applies similarly to our data - we don't want anyone accessing inaccurate data while in the middle of our multiple-command transaction.
3. Set Josie's favorite toy value to `"Moppy Ball"`:
>HSET user:justin:pet:josie favourite_toy "Moppy Ball"
Resulting values of both keys:
key | field | value |
---|---|---|
user:rachel:pet:sputnik |
favourite_toy |
"Cherry Tomato" |
user:justin:pet:josie |
favourite_toy |
"Moppy Ball" |
The MULTI
and EXEC
command structure ensure that all commands within will be executed one after another, without any interruption by any other client commands. This ensures that during the moment of duplicate values, there will be no accidental access.
Here's an example using our toy-swap scenario:
> MULTI
OK
TX)> HSET user:rachel:pet:sputnik favourite_toy "Cherry Tomato"
QUEUED
TX)> HSET user:justin:pet:josie favourite_toy "Moppy Ball"
QUEUED
TX)> EXEC
1) (integer) 0
2) (integer) 0
After the MULTI
command is called, an OK
is returned to verify that all following commands will be added to a multi-command transaction. You can see this after every subsequent command with the return value of QUEUED
.
To end the transaction, you enter the EXEC
command, which will execute every command in succession starting directly after MULTI
.
This particular transaction will swap the toy values sequentially with no interruption from any other client request to the Redis instance. Also with transactions, both key's values will be successfully swapped or not at all; that is a guarantee from the transaction structure. If there is an I/O error or server crash, the entire transaction will be cancelled.
WATCH is used to provide a check-and-set (CAS) behavior to Redis transactions.
WATCH
ed keys are monitored in order to detect changes against them. If at least one watched key is modified before the EXEC command, the whole transaction aborts, and EXEC returns a Null reply to notify that the transaction failed.
As an example, lets have a separate key for each pet user:nava:pet:gingit:times_fed_today
that is normally set to 0
. When the pet has been fed a meal, the value is incremented. What happens if two clients attempt to increment the field times_fed_today
at the same time, when the value is 0
? The pet may be quite excited to receive two meals sequentially, but this is a race condition and isn't desirable.
When we WATCH
a key before a transaction, the transaction will abort IF the WATCH
ed key is altered.
Here is an example using WATCH
:
> WATCH user:nava:pet:gingit:times_fed_today
OK
> MULTI
OK
(TX)> INCR user:nava:pet:gingit:times_fed_today
QUEUED
(TX)> EXEC
1) (integer) 1
Here is another example of WATCH
ing the key when another client updates the key:
> WATCH user:nava:pet:gingit:times_fed_today
OK
> MULTI
OK
(TX)> incr user:nava:pet:gingit:times_fed_today
QUEUED
(TX)> exec
(nil)
Notice that a (nil)
is returned instead of the new value of the key. This indicates that the key has been altered since the WATCH
command was initially called, and will allow you to take the necessary steps to address the change in the value.
Note that the
WATCH
command does is not currently designed for use with Hash fields. By storing a single value associated with the keyusr:nava:pet:gingit
in a String type key, we can use theWATCH
command
DISCARD
aborts all commands that are queued in a current transaction and unwatches all keys that WATCH
was called upon.
From the documentation linked above:
SUBSCRIBE, UNSUBSCRIBE and PUBLISH implement the Publish/Subscribe messaging paradigm where (citing Wikipedia) senders (publishers) are not programmed to send their messages to specific receivers (subscribers). Rather, published messages are characterized into channels, without knowledge of what (if any) subscribers there may be.
This structure allows Redis to PUBLISH
a message without any intended recipient to a specified channel from any client. An client that SUBSCRIBE
s to a specified channel will then receive a message sent specifically to that channel.
Imagine we have an aircraft just touched down at an airoport and will be arriving at a specific gate. This event should notify the following systems and departments within the airport:
- Jet bridge team
- Fuel tanker team to refill the Jet
- Baggage claim carousel to display the flight number
These four services will all set off different activities within the airport when a specific flight lands. They will SUBSCRIBE
to any messages from the flight, which PUBLISH
es messages as they occur.
Here is one possible scenario where three different clients are subscribed to a specific channel arrivals
and will receive any message published:
Jet Bridge client:
SUBSCRIBE arrivals
Fuel Tanker client:
SUBSCRIBE arrivals
Baggage client:
SUBSCRIBE arrivals
Arrival client:
PUBLISH arrivals "UA001:43:4"
What we see with this:
- One publisher sends a message to to a channel
- Multiple subscribers will receive a message to a channel they subscribe to.
- This has many uses with Redis, as it is fast and requires no extra keys or data.