Created
August 27, 2019 21:27
-
-
Save sneeu/61a16ebe4ba8dc952f0c4deceadd6b6f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Redis\n", | |
"\n", | |
"An introduction to using the data-structure store, Redis.\n", | |
"\n", | |
"To begin, we need to install `redis` and, optionally, `hiredis`:\n", | |
"\n", | |
"```\n", | |
"pip install redis hiredis\n", | |
"```\n", | |
"\n", | |
"`hiredis` is a parser for the Redis protocol, writen in C to be fast—`redis` includes a protocol parser written in Python.\n", | |
"\n", | |
"## The basics\n", | |
"\n", | |
"### Import\n", | |
"\n", | |
"A simple `import redis` is all we need, `redis` will use `hiredis` automatically if it is installed." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import redis" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Connect\n", | |
"\n", | |
"We supply a `host`, `port`, and `db`. `host` and `port` should be fairly straightforward and by default, Redis starts on port `6379`.\n", | |
"\n", | |
"Similar to other datastores, Redis has the concept of multiple “databases” (by default up to 16) simply numbered `0`–`15`. We’ll connect to database `0`." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"True" | |
] | |
}, | |
"execution_count": 2, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn = redis.Redis(host='localhost', port=6379, db=0)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Set a string value\n", | |
"\n", | |
"Once we have our connection, we can send the [`SET`](https://redis.io/commands/set) command to set a single key, `a-key` to be some string value, `some-value`. `redis` will return `True` to confirm that the value was stored:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"True" | |
] | |
}, | |
"execution_count": 3, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.set('a-key', 'some-value')" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Retrieve a string value\n", | |
"\n", | |
"Once set, a value can be retrieved with the [`GET`](https://redis.io/commands/get) command, `redis` will return the value:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"b'some-value'" | |
] | |
}, | |
"execution_count": 4, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.get('a-key')" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Delete a key\n", | |
"\n", | |
"Finally, we can remove a value using the [`DEL`](https://redis.io/commands/del) command. `redis` uses the name `delete` here because `del` is a Python keyword. `redis` will return the number of keys that have been deleted:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"1" | |
] | |
}, | |
"execution_count": 5, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.delete('a-key')" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Once deleted, `redis` will return `None` when trying to `GET` a key:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"assert rconn.get('a-key') == None" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Simple debugging\n", | |
"\n", | |
"Before we go too much further, I’ll introduce some simple debugging to ease with any issue you may come across.\n", | |
"\n", | |
"### List all keys\n", | |
"\n", | |
"We can check if a key exists with [`EXISTS`](https://redis.io/commands/exists), and list all keys in the current database with [`KEYS`](https://redis.io/commands/keys):" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"rconn.set('another-key', 'some-other-value')\n", | |
"\n", | |
"assert rconn.exists('another-key')\n", | |
"assert not rconn.exists('nowhere-key')\n", | |
"assert rconn.keys() == [b'another-key']" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Lists\n", | |
"\n", | |
"Redis is a data-structure store and as such can store a number of different data-structures. We’ll start with lists.\n", | |
"\n", | |
"List commands usually start with `L`, for left, and in some cases, `R` for (you guessed it) right.\n", | |
"\n", | |
"We can push a value on to the left ([`LPUSH`](https://redis.io/commands/lpush)) or right ([`RPUSH`](https://redis.io/commands/rpush)) end of a list. [`LRANGE`](https://redis.io/commands/lrange) allows us to return some “slice” of the list. Like Python, `LRANGE` supports negative indexing, so `0`, `-1` will give us back the whole list:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"[b'6', b'95']" | |
] | |
}, | |
"execution_count": 8, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.lpush('a-list', 6)\n", | |
"rconn.rpush('a-list', 95)\n", | |
"rconn.lrange('a-list', 0, -1)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"We can insert values before or after a “pivot” value in a list:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"[b'6', b'44', b'95']" | |
] | |
}, | |
"execution_count": 9, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.linsert('a-list', 'BEFORE', '95', 44)\n", | |
"rconn.lrange('a-list', 0, -1)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"And we can change the value at any index:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"[b'9', b'44', b'95']" | |
] | |
}, | |
"execution_count": 10, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.lset('a-list', 0, 9)\n", | |
"rconn.lrange('a-list', 0, -1)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Using a list as a queue or stack\n", | |
"\n", | |
"A list can be used as either a [queue](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)) or a [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) using a combination of `LPUSH`, `RPUSH`, [`RPOP`](https://redis.io/commands/rpop), and [`LPOP`](https://redis.io/commands/lpop).\n", | |
"\n", | |
"The queue:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"b'81'" | |
] | |
}, | |
"execution_count": 11, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.lpush('a-queue', 81)\n", | |
"rconn.lpush('a-queue', 82)\n", | |
"rconn.lpush('a-queue', 83)\n", | |
"rconn.rpop('a-queue')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"b'82'" | |
] | |
}, | |
"execution_count": 12, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.rpop('a-queue')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"b'83'" | |
] | |
}, | |
"execution_count": 13, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.rpop('a-queue')" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"And the stack:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 14, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"b'76'" | |
] | |
}, | |
"execution_count": 14, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.rpush('a-stack', 74)\n", | |
"rconn.rpush('a-stack', 75)\n", | |
"rconn.rpush('a-stack', 76)\n", | |
"rconn.rpop('a-stack')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 15, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"b'75'" | |
] | |
}, | |
"execution_count": 15, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.rpop('a-stack')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"b'74'" | |
] | |
}, | |
"execution_count": 16, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.rpop('a-stack')" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"In some applications we may want to block while we wait for a value to be added to a queue or stack, in those cases, we can use [`BLPOP`](https://redis.io/commands/blpop) (**b**locking **r**ight **pop**) or [`BRPOP`](https://redis.io/commands/brpop)." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Sets\n", | |
"\n", | |
"Redis has two different options for sets: sorted and unsorted. We’ll describe unsorted sets.\n", | |
"\n", | |
"Set commands all start with an `S`, for set.\n", | |
"\n", | |
"We can add values to a set, check the size (or **card**inality), and check a value’s membership of a set:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 17, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"rconn.sadd('users', 'alice')\n", | |
"rconn.sadd('users', 'bob')\n", | |
"rconn.sadd('users', 'carol')\n", | |
"\n", | |
"assert rconn.scard('users') == 3\n", | |
"assert rconn.smembers('users') == {b'alice', b'bob', b'carol'}" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"We can also perform common set operations, difference:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 18, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"rconn.sadd('admins', 'alice')\n", | |
"assert rconn.sdiff('users', 'admins') == {b'bob', b'carol'}" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"And union:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 19, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"rconn.sadd('customers', 'carol')\n", | |
"rconn.sadd('customers', 'dan')\n", | |
"rconn.sadd('customers', 'frank')\n", | |
"assert rconn.sunion('users', 'customers') == {b'alice', b'bob', b'carol', b'dan', b'frank'}" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Sorted sets\n", | |
"\n", | |
"Similarly to sets, a value in a sorted set can only exist once. With a sorted set however, each item has a score which is used to order the members of the set.\n", | |
"\n", | |
"The sorted set commands all begin with `Z`.\n", | |
"\n", | |
"We use [`ZADD`](https://redis.io/commands/zadd) to add an item to a sorted set and can use [`ZRANGEBYSCORE`](https://redis.io/commands/zrangebyscore) to get the members of the set in order." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 20, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"[b'baz', b'bar', b'foo']" | |
] | |
}, | |
"execution_count": 20, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.zadd('scores', {'foo': 10, 'bar': 6, 'baz': 1})\n", | |
"rconn.zrangebyscore('scores', '-inf', '+inf')" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"We can use [`ZINCRBY`](https://redis.io/commands/zincrby) to increase a member’s score:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 21, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"[b'bar', b'foo', b'baz']" | |
] | |
}, | |
"execution_count": 21, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.zincrby('scores', 20.0, 'baz')\n", | |
"rconn.zrangebyscore('scores', '-inf', '+inf')" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Hashes\n", | |
"\n", | |
"A hash is a mapping type, similar to Python’s `dict`. The hash commands begin with an `H`.\n", | |
"\n", | |
"We can add some values to a hash, and get them back:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 22, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"b'alice@example.net'" | |
] | |
}, | |
"execution_count": 22, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.hset('users:alice', 'email', 'alice@example.net')\n", | |
"rconn.hset('users:alice', 'password', '6384e2b2184bcbf58eccf10ca7a6563c')\n", | |
"rconn.hset('users:alice', 'login-count', 0)\n", | |
"\n", | |
"rconn.hget('users:alice', 'email')" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"We can also get multiple values back:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 23, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"[b'alice@example.net', b'6384e2b2184bcbf58eccf10ca7a6563c']" | |
] | |
}, | |
"execution_count": 23, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.hmget('users:alice', ['email', 'password'])" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"And atomically increment a key in the hash:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 24, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"10" | |
] | |
}, | |
"execution_count": 24, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.hincrby('users:alice', 'login-count', 2)\n", | |
"rconn.hincrby('users:alice', 'login-count', 3)\n", | |
"rconn.hincrby('users:alice', 'login-count', 5)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Scaning\n", | |
"\n", | |
"Redis keys (and the database’s “key-space”) can get large over time, to avid having commands block the Redis server on IO, Redis providers [`SCAN`](https://redis.io/commands/scan) commands to iterate over a list, set, hash, or the key-space.\n", | |
"\n", | |
"For example to iterate over a hash, we would use `HSCAN` giving it the `cursor` value of 0:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 25, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"(0,\n", | |
" {b'email': b'alice@example.net',\n", | |
" b'password': b'6384e2b2184bcbf58eccf10ca7a6563c',\n", | |
" b'login-count': b'10'})" | |
] | |
}, | |
"execution_count": 25, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"rconn.hscan('users:alice', 0)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"The return value is a tuple of the `cursor` value for the next iteration and the contents of the iteration. Python’s `redis` package helps us out with `SCAN`s by providing iterators which manage the `cursor` for us:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 26, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"{b'email': b'alice@example.net',\n", | |
" b'password': b'6384e2b2184bcbf58eccf10ca7a6563c',\n", | |
" b'login-count': b'10'}" | |
] | |
}, | |
"execution_count": 26, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"dict(rconn.hscan_iter('users:alice'))" | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.7.3" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment