Redis Essentials - Sample Chapter
Redis Essentials - Sample Chapter
Redis Essentials - Sample Chapter
It's very lightweight and its data types give it an edge over
other competitors.
Next, you will learn how to extend Redis with Lua, get to
know security techniques such as basic authorization,
firewall rules, and SSL encryption, and discover how to
use twemproxy, Redis Sentinel, and Redis Cluster to
scale infrastructures horizontally. At the end of this book,
you will be able to utilize all the essential features of
Redis to optimize your project's performance.
"Community
Experience
Distilled"
Redis Essentials
Redis Essentials
C o m m u n i t y
E x p e r i e n c e
D i s t i l l e d
Redis Essentials
Harness the power of Redis to integrate and manage your
projects efficiently
Hugo Lopes Tavares is a software developer from Brazil who currently works
Having been involved in open source software, he has made a significant impact in
this field. He was a main contributor to pip (the Python package installer), wrote
improvements to CPython and the Python standard library, coauthored Splinter
(a web-testing tool), and contributed to many well-known projects. Some of his
contributions can be found at https://github.com/hltbra.
Additionally, Hugo worked at NSI (Information Systems Research Group), carrying
out research and development on agile methods and software quality for the Brazilian
government. Within his research, he created some testing tools, the most famous of
which are Should-DSL and PyCukes, which are mentioned in Python Testing Cookbook,
Packt Publishing (Should-DSL has its own section in it).
When Hugo is not doing anything related to technology, he is involved in strength
training as an amateur powerlifter.
You can contact him on LinkedIn at https://www.linkedin.com/in/hltbra.
Preface
Redis is the most popular in-memory key-value data store. It is very lightweight and its
data types give it an edge over other competitors. If you need an in-memory database
or a high-performance cache system that is simple to use and highly scalable, Redis is
what you should use.
This book is a fast-paced guide that teaches you the fundamentals of data types,
explains how to manage data through commands, and shares experiences from
big players in the industry.
Preface
Chapter 4, Commands (Where the Wild Things Are), introduces Pub/Sub, transactions, and
pipelines. It also introduces the scripting mechanism, which uses the Lua programming
language to extend Redis. A quick Lua syntax reference is also presented. A great
variety of Redis commands are presented in this chapter, including the administration
commands and data type commands that were not covered in the previous chapters.
This chapter also shows you how to change Redis's configuration to optimize different
data types for memory or performance.
Chapter 5, Clients for Your Favorite Language (Become a Redis Polyglot), shows how to
use Redis with PHP, Python, and Ruby. This chapter highlights the features that
vary more frequently with clients in different languages: blocking commands,
transactions, pipelines, and scripting.
Chapter 6, Common Pitfalls (Avoiding Traps), illustrates some common mistakes
when using Redis in a production environment and related stories from real-world
companies. The pitfalls in this chapter include using the wrong data type for a given
problem, using too much swap space, and using inefficient backup strategies.
Chapter 7, Security Techniques (Guard Your Data), shows how to set up basic security
with Redis, disable and obfuscate commands, protect Redis with firewall rules, and
use client-to-server SSL encryption with stunnel.
Chapter 8, Scaling Redis (Beyond a Single Instance), introduces RDB and AOF
persistence, replication via Redis slaves, and different methods of partitioning
data across different hosts. This chapter also shows how to use twemproxy to
distribute Redis data across different instances transparently.
Chapter 9, Redis Cluster and Redis Sentinel (Collective Intelligence), demonstrates the
differences between Redis Cluster and Redis Sentinel, their goals, and how they
fit into the CAP theorem. It also shows how to set up both Sentinel and Cluster,
their configurations, and what happens in different failure scenarios. Redis Cluster
is covered in more detail, since it is more complex and has different tools for
managing a cluster of instances. Cluster administration is explained via native
Redis commands and the redis-trib tool.
Getting Started
(The Baby Steps)
Redis is a NoSQL (Not only SQL) advanced key-value data store. It is also referred to
as a data structure server because of its powerful data types, such as Strings, Hashes,
Lists, Sets, Sorted Sets, Bitmaps, and HyperLogLogs. By default, Redis saves all data
in the memory, therefore read and write operations are very fast. It can also cause data
to persist in the disk. Data persistence in Redis can be achieved by creating a binary
snapshot of the stored data or a human-readable file with a sequence of all executed
commands over time. These are respectively known as snapshotting and journaling.
Additionally, Redis includes configurable key expiration, transactions, and
publish/subscribe features. It also provides Lua scripting to extend Redis to
create new commands. Combined, these features transform Redis into the Swiss
Army knife of data type storage.
Redis stands for REmote DIctionary Server. It was written in C by Salvatore
Sanfilippo in 2006 and currently has many contributors. There are Redis clients
available for over 30 programming languages. The open source project can be
found at https://github.com/antirez/redis. The official Redis documentation
is also a really good resource of knowledge and can be found at http://redis.io.
Redis is a well-established open source project and has been used in production for
years by big companies, including Twitter, GitHub, Tumblr, Pinterest, Instagram,
Hulu, Flickr, and The New York Times.
This chapter is going to show you how to install Redis, introduce the command-line
interface, introduce a Node.js client for Redis, and then present three data types in
detail: Strings, Lists, and Hashes.
[1]
Redis data types are a very extensive subject. There is enough information to write a
book that just describes how they work. We will present the most relevant and useful
commands for each data type along with real-life use cases in the first two chapters.
Chapter 2, Advanced Data Types (Earning a Black Belt), is going to cover other data
types: Sets, Sorted Sets, Bitmaps, and HyperLogLogs. After this chapter and the next
have explained all data types, Chapter 3, Time Series (A Collection of Observations), will
present a time series implementation that uses multiple data types.
Please note that all data types will be shown with the first letter
capitalized (for example, Strings, Lists, Bitmaps, Sets, Sorted Sets,
and HyperLogLogs) so that we can distinguish between a Redis
data type and other existing terms.
Installation
At the time of writing this book, the stable version of Redis was 3.0. All examples
presented in this book will work with this version, but it is very likely that newer
versions of Redis are going to work as well. Redis is very strict in terms of backward
compatibility, so it provides API stability between minor versions. We recommend
that you install the latest version of Redis to get the recent bug fixes and performance
improvements. Most of the content in this book will remain useful even if you work
with a more recent version.
Officially, Redis can be compiled and used on Linux, OS X, OpenBSD, NetBSD,
and FreeBSD.
Redis is not officially supported on Windows. However, the Microsoft Open Tech
group develops and maintains a Windows port targeting Win64 architecture, which
can be found at https://github.com/MSOpenTech/redis. We are not going to
cover Windows installation or guarantee that the examples presented in this book
will work on Windows.
[2]
Chapter 1
On OS X, you will need Xcode and Command Line Tools Package installed. After the
required build tools are installed, open a terminal window and execute the following
commands:
$
$
$
$
curl -O http://download.redis.io/releases/redis-3.0.2.tar.gz
tar xzvf redis-3.0.2.tar.gz
cd redis-3.0.2
sudo make install
Every time you see a dollar sign ($) at the beginning of a code block, it means we are
executing the command in a terminal window.
Another way to install Redis is by using package managers,
such as yum, apt, or brew. Make sure your package manager
has Redis 3.0 or later available.
Since no configuration was specified in this example, Redis will use default
configurations.
[3]
It will output its PID (process ID) and the port that the clients should connect to,
which is 6379 by default.
Important note:
The following conventions will be used in this book for redis-cli:
The next snippet shows how to connect to the Redis server using redis-cli.
Once connected, we use the SET command to create a key with a string value
and then the GET command to read the key value:
$ redis-cli
127.0.0.1:6379> SET philosopher "socrates"
OK
127.0.0.1:6379> GET philosopher
"socrates"
127.0.0.1:6379>
[4]
Chapter 1
The HELP command is useful for learning about command syntax. It displays the
command parameters with a summary and examples. See the following example:
$ redis-cli
127.0.0.1:6379> HELP SET
SET key value [EX seconds] [PX milliseconds] [NX|XX]
summary: Set the string value of a key
since: 1.0.0
group: string
The KEYS command is also useful, as it returns all stored keys that match a pattern
(it is a glob-style pattern, like the Unix shell glob pattern). In the following code, all
stored key names that start with the letter "p" are returned:
$ redis-cli
127.0.0.1:6379> KEYS p*
1) "philosopher"
The redis-cli is a great tool for debugging and testing commands, but making real
examples and applications using redis-cli is impractical. This book is going to use the
JavaScript language and Node.js to support examples and explanations. We chose
JavaScript because of its current popularity. The Node.js website (https://nodejs.
org) provides binaries for Mac OS X, Windows, and Linux, which makes installation
of Node.js really simple. Keep in mind that this is not a JavaScript book; we are going
to use basic features of the language in our examples. If you do not know how to
code in JavaScript, do not worry. A quick syntax reference is presented, and it should
be enough to understand all the examples in this book.
You can reproduce all the samples presented here in your favorite
language. Redis will produce the same results regardless of the
programming language.
Installing Node.js
Download and install Node.js from its website using the available binary packages.
At the time of writing this book, the latest version of Node.js was 0.12.4. All examples
are guaranteed to work with this version.
Node.js comes with a package manager called Node Package Manager (NPM),
which is responsible for managing and installing all Node.js dependencies and
libraries. Think of it as pip for Python or cpan for Perl.
[5]
We recommend that you create a folder called redis-essentials to save all the files and
libraries necessary for running the examples. We also recommend that you create
one folder for each chapter of this book for organization purposes.
All Node.js examples in this book require the library redis, which can be installed
with NPM:
$ cd redis-essentials
$ npm install redis
NPM will create a folder called node_modules. This is where the redis client is installed.
Conditional statements:
if (myAge > 29) {
console.log("I am not in my twenties anymore!");
} else {
console.log("I am still in my twenties!");
}
Defining a function:
function nameOfMyFunction(argument1, argument2) {
console.log(argument1, argument2);
}
Executing a function:
nameOfMyFunction("First Value", "Second Value");
[6]
Chapter 1
Callbacks in JavaScript:
var friends = ["Karalyn", "Patrik", "Bernardo"];
friends.forEach(function (name, index) {
console.log(index + 1, name); // 1 Karalyn, 2 Patrik, 3 Bernardo
});
[7]
If you want to know more about JavaScript syntax and features, we recommend the
Mozilla Developer Network website at https://developer.mozilla.org/en-US/
docs/Web/JavaScript.
Please note that all the code snippets in this book will have inline
comments with numbers. After the code is presented, it will be
explained by referencing those numbers.
[8]
Chapter 1
Run hello.js with the node command (node is the Node.js interpreter):
$ node hello.js
Reply: Hello World using Node.js and Redis
Strings
Strings are the most versatile data types in Redis because they have many commands
and multiple purposes. A String can behave as an integer, float, text string, or bitmap
based on its value and the commands used. It can store any kind of data: text (XML,
JSON, HTML, or raw text), integers, floats, or binary data (videos, images, or audio
files). A String value cannot exceed 512 MB of text or binary data.
The following are some use cases for Strings:
[9]
The EXPIRE command adds an expiration time (in seconds) to a given key. After that
time, the key is automatically deleted. It returns 1 if the expiration is set successfully
and 0 if the key does not exist or cannot be set.
The TTL (Time To Live) command returns one of the following:
$ redis-cli
127.0.0.1:6379>
OK
127.0.0.1:6379>
(integer) 1
127.0.0.1:6379>
"Chapter 1"
127.0.0.1:6379>
(integer) 3
127.0.0.1:6379>
[ 10 ]
Chapter 1
(integer) -2
127.0.0.1:6379> GET current_chapter
(nil)
127.0.0.1:6379>
The commands INCR and INCRBY have very similar functionality. INCR increments
a key by 1 and returns the incremented value, whereas INCRBY increments a key
by the given integer and returns the incremented value. DECR and DECRBY are the
opposites of INCR and INCRBY. The only difference is that DECR and DECRBY
decrements a key.
The command INCRBYFLOAT increments a key by a given float number and returns
the new value. INCRBY, DECRBY, and INCRBYFLOAT accept either a positive or a
negative number:
$ redis-cli
127.0.0.1:6379>
OK
127.0.0.1:6379>
(integer) 101
127.0.0.1:6379>
(integer) 106
127.0.0.1:6379>
(integer) 105
127.0.0.1:6379>
(integer) 5
127.0.0.1:6379>
"5"
127.0.0.1:6379>
"7.4"
The preceding commands shown are atomic, which means that they increment/
decrement and return the new value as a single operation. It is not possible for two
different clients to execute the same command at the same time and get the same
resultno race conditions happen with those commands.
For example, if the counter key is 1 and two different clients (A and B) increment
their counters at the same time with INCR, client A will receive the value 2 and
client B will receive 3.
Redis is single threaded, which means that it always executes one
command at a time. Sometimes, commands are mentioned as atomic,
which means that a race condition will never happen when multiple
clients try to perform operations on the same key at the same time.
[ 11 ]
To complete this example, we will need two keys in Redis for each article.
We have already defined our first key to store the headline of each article.
Observe this key name structure: article:<id>:headline. The second key name will
have a similar structure: article:<id>:votes. This nomenclature is important in order
to create abstractions. The IDs may be passed around, and even if the key format
changes, the application logic will remain the same. Also, it is easy to extend the
application if other metadata (URL, summary, and so on) needs to be stored.
Our code will have three functions: the first increments the number of votes in an
article by 1, the second decrements the number of votes in an article by 1, and the
third displays the article headline and the number of votes. All three functions
(upVote, downVote, and showResults) require the article ID as the argument. Perform
the following set of steps:
Create a file called articles-popularity.js in the chapter 1 folder where all of the code
from this section should be saved:
var redis = require("redis"); // 1
var client = redis.createClient(); // 2
function upVote(id) { // 3
var key = "article:" + id + ":votes"; // 4
client.incr(key); // 5
}
[ 12 ]
Chapter 1
1. Use the MGET command to pass an array of keys and a callback function.
For every key that does not hold a String value or does not exist, the value
null is returned.
In the anonymous function, the argument replies has two values: index 0,
which has the headline, and index 1, which has the number of votes.
2. Display a message with the article headline and number of votes.
[ 13 ]
Note:
The Node.js client that we are using is strictly asynchronous. All Redis
commands have an optional callback function for handling errors and
replies from the Redis server.
In the previous MGET example, the only way to handle the key values
is by passing a callback to client.mget().
Please make sure you fully understand the idea of callbacks mentioned
before. This is necessary in order to understand other examples using
Node.js.
It is time to call our functions upVote, downVote, and showResults. Add the following
to articles-popularity.js too:
upVote(12345); // article:12345 has 1
upVote(12345); // article:12345 has 2
upVote(12345); // article:12345 has 3
upVote(10001); // article:10001 has 1
upVote(10001); // article:10001 has 2
downVote(10001); // article:10001 has
upVote(60056); // article:60056 has 1
vote
votes
votes
vote
votes
1 vote
vote
showResults(12345);
showResults(10001);
showResults(60056);
client.quit();
[ 14 ]
Chapter 1
Lists
Lists are a very flexible data type in Redis because they can act like a simple
collection, stack, or queue. Many event systems use Redis's Lists as their queue
because Lists' operations ensure that concurrent systems will not overlap popping
items from a queueList commands are atomic. There are blocking commands in
Redis's Lists, which means that when a client executes a blocking command in an
empty List, the client will wait for a new item to be added to the List. Redis's Lists
are linked lists, therefore insertions and deletions from the beginning or the end
of a List run in O(1), constant time.
The task of accessing an element in a List runs in O(N), linear time, but accessing
the first or last element always runs in constant time.
A List can be encoded and memory optimized if it has less elements than the
list-max-ziplist-entries configuration and if each element is smaller than the
configuration list-max-ziplist-value (in bytes). Chapter 4, Commands (Where the
Wild Things Are) provides more details on these configurations.
The maximum number of elements a List can hold is 232-1, which means there can
be more than 4 billion elements per List.
Some real-world use cases of Lists are as follows:
Event queue: Lists are used in many tools, including Resque, Celery,
and Logstash
Storing most recent user posts: Twitter does this by storing the latest tweets
of a user in a List
In this section, we will show you some List commands using the redis-cli, and then
present a generic task queue system in Node.js.
The command LLEN returns the length of a List. The command LINDEX returns
the element in a given index (indices are zero-based). Elements in a List are always
accessed from left to right, which means that index 0 is the first element, index 1
is the second element, and so on. It is possible to use negative indices to access the
tail of the List, in which -1 is the last element, -2 is penultimate element, and so on.
LINDEX does not modify a List:
$ redis-cli
127.0.0.1:6379> LLEN books
(integer) 3
127.0.0.1:6379> LINDEX books 1
"Clean Code"
The command LRANGE returns an array with all elements from a given index range,
including the elements in both the start and end indices. As we mentioned previously,
indices are zero-based and can be positive or negative. See the following example:
$ redis-cli
127.0.0.1:6379> LRANGE books 0 1
1) "Peopleware"
2) "Clean Code"
127.0.0.1:6379> LRANGE books 0 -1
1) "Peopleware"
2) "Clean Code"
3) "Code Complete"
The command LPOP removes and returns the first element of a List. The command
RPOP removes and returns the last element of a List. Unlike LINDEX, both LPOP
and RPOP modify the List:
$ redis-cli
127.0.0.1:6379> LPOP books
"Peopleware"
127.0.0.1:6379> RPOP books
"Code Complete"
127.0.0.1:6379> LRANGE books 0 -1
1) "Clean Code"
[ 16 ]
Chapter 1
Create a file called queue.js in the chapter 1 folder with the following code:
function Queue(queueName, redisClient) { // 1
this.queueName = queueName; // 2
this.redisClient = redisClient; // 3
this.queueKey = 'queues:' + queueName; // 4
// zero means no timeout
this.timeout = 0; // 5
}
1. Create a function called Queue, which receives a queue name and the Redis
client object as parameters.
2. Save queueName as a property.
3. Save redisClient as a property.
4. Set the property queueKey to the proper Redis key name, based on the
function parameter.
5. Set the property timeout to zero, which means that when List commands are
executed, they will have no timeout.
We need to implement three methods to perform queue operations: size, push, and pop.
The first method we are going to create is size:
Queue.prototype.size = function(callback) { // 1
this.redisClient.llen(this.queueKey, callback); // 2
};
1. Create the Queue method push that expects one argument. This argument can
be anything that can be represented as a string.
2. Execute LPUSH by passing the queue key name and the data argument.
[ 17 ]
As this is a generic queue system and Redis lists only store bytes, we assume that all
of the data that is sent to the queue can be transformed into a JavaScript string. If you
want to make it more generic, you can use JSON serialization and store the serialized
string. The previous example used LPUSH because we were implementing a queue,
and by definition, items are inserted at the front of the queue and removed from the
end of the queue. A helpful way to remember this is FIFO (First In, First Out)we
went from left to right.
The implementation of the pop method is as follows:
Queue.prototype.pop = function(callback) { // 1
this.redisClient.brpop(this.queueKey, this.timeout, callback); // 2
};
Chapter 1
Queue.prototype.push = function(data) {
this.redisClient.lpush(this.queueKey, data);
};
Queue.prototype.pop = function(callback) {
this.redisClient.brpop(this.queueKey, this.timeout, callback);
};
exports.Queue = Queue; // 1
redis = require("redis");
client = redis.createClient();
queue = require("./queue"); // 1
logsQueue = new queue.Queue("logs", client); // 2
MAX = 5;
(var i = 0 ; i < MAX ; i++) { // 3
logsQueue.push("Hello world #" + i); // 4
}
console.log("Created " + MAX + " logs"); // 5
client.quit();
1. Require the module queue, which we've already created and saved as queue.js.
2. Create an instance of the function Queue defined in the queue.js file.
3. Create a loop that runs five times.
4. Push some logs into the logs queue.
5. Print the number of logs created.
Execute the producer file to push logs into the queue:
$ node producer-worker.js
Created 5 logs
redis = require("redis");
client = redis.createClient();
queue = require("./queue"); // 1
logsQueue = new queue.Queue("logs", client); // 2
[ 19 ]
world #0
world #1
world #2
world #3
world #4
[ 20 ]
Chapter 1
This file will run indefinitely. More messages can be added to the queue by executing
producer-worker.js again in a different terminal, and the consumer will continue reading
from the queue as soon as new items are added.
The example shown in this section is not reliable enough to deploy to production.
If anything goes wrong with the callbacks that pop from the queue, items may be
popped but not properly handled. There is no such thing as a retry or any way to
track failures.
A good way of solving the reliability problem is to use an additional queue. Each
element that is popped from the queue goes to this additional queue. You must
remove the item from this extra queue only if everything has worked correctly.
You can monitor this extra queue for stuck elements in order to retry them or
create failure alerts. The command RPOPLPUSH is very suitable for this situation,
because it does a RPOP in a queue, then does a LPUSH in a different queue, and
finally returns the element, all in a single stepit is an atomic command.
Hashes
Hashes are a great data structure for storing objects because you can map fields to
values. They are optimized to use memory efficiently and look for data very fast.
In a Hash, both the field name and the value are Strings. Therefore, a Hash is a
mapping of a String to a String.
Previously, in the String example, we used two separate keys to represent an article
headline and its votes (article:<id>:headline and article:<id>:votes). It is more semantic
to use a Hash in that case because the two fields belong to the same object (that is,
the article).
Another big advantage of Hashes is that they are memory-optimized. The optimization
is based on the hash-max-ziplist-entries and hash-max-ziplist-value configurations.
Chapter 4, Commands (Where the Wild Things Are), provides more details on these
configurations.
Internally, a Hash can be a ziplist or a hash table. A ziplist is a dually linked list
designed to be memory efficient. In a ziplist, integers are stored as real integers
rather than a sequence of characters. Although a ziplist has memory optimizations,
lookups are not performed in constant time. On the other hand, a hash table has
constant-time lookup but is not memory-optimized.
[ 21 ]
This section is going to show the most used Hash commands using redis-cli, and
then present an application that stores movie metadata in Node.js (similar to the
http://www.imdb.com website).
The command HGET retrieves a field from a Hash. The command HMGET retrieves
multiple fields at once:
127.0.0.1:6379> HGET movie "title"
"The Godfather"
127.0.0.1:6379> HMGET movie "title" "watchers"
1) "The Godfather"
2) "10000003"
Chapter 1
It is possible to retrieve only the field names or field values of a Hash with the
commands HKEYS and HVALS respectively.
In the next section, we are going to use Hashes to implement a voting system
similar to the one presented with Strings.
[ 24 ]
Chapter 1
showDetails(123);
showDetails(456);
client.quit();
[ 25 ]
Summary
This chapter began with information about Redis's history and some of its design
decisions. We explained how to install Redis and demonstrated that the redis-cli
tool can be a very powerful tool for debugging and learning Redis.
Some examples in this book that require a programming language are implemented
in Node.js. Therefore, a quick reference to JavaScript's syntax and Node.js installation
were shown.
Redis data types is an extensive subject, and it has been split into two chapters.
This chapter explained how Strings, Lists, and Hashes work. The next chapter will
cover Sets, Sorted Sets, Bitmaps, and HyperLogLogs and give practical examples.
[ 26 ]
www.PacktPub.com
Stay Connected: