Redis for the Everyday Developer
-
Upload
ross-tuck -
Category
Technology
-
view
8.886 -
download
3
description
Transcript of Redis for the Everyday Developer
Ross Tuck
Redis For The Everyday Developer
ConfooMarch 1st, 2013
Who Am I?
Ross Tuck
Team Lead at IbuildingsCodemonkeyHTTP nutHat guy
@rosstuck
Boring.
NoSQL
NoNoSQL
Evaluation
• Created by Salvatore Sanfilippo ( @antirez )• Sponsored by VMware• 4 years old• redis.io
“Redis is an open source, advanced key-value store.”
“It is often referred to as a data structure server...”
Defancypants
Server A Server B
Redis
In-Memory
In-Memory
The Cool Stuff
Fast.
Demo.
Oh wait, it's already done.
120,000~ ops per sec
Flexible.
(optionally)Persistent.
ReplicationPub/Sub
TransactionsScripting
SlicesDices
Makes julienne fries
“Well, Ross, that sounds cool...”
MySQL Redis
+
MySQL+
Redis
4ever
Setup
apt-get install redis-server
Easy but old
http://redis.io/download
PHP Libraries
• 5.3+: Predis
• 5.2: Predis or Rediska
• C Extension: phpredis
• redis-cli
$client = new \Predis\Client();
The Surprise Ending
PHPMySQL
Memcache
PHPMySQL
Memcache
PHPMySQLRedis
It's about 80% cases.
Use Case #1: Caching
Redis has commands.
Commands have parameters.
Think functions.
$client->commandGoesHere($params, $go, $here);
SET
$client->set('ross:mood', 'nervous');
// Later
$client->get('ross:mood'); // returns “nervous”
$client->set('ross:mood', 'nervous');
$client->expire('ross:mood', 5);
// 4 seconds later...
$client->get('ross:mood'); // “nervous”
// 5 seconds later...
$client->get('ross:mood'); // null
$client->set('ross:mood', 'nervous');
$client->expire('ross:mood', 5);
$client->setex('ross:mood', 5, 'nervous');
Great for caching
Ideal for sessions
Use Case #2: Simple Data
Search...
omg cheezburgers in the lunchroomtoday. Better hurry if u want 1!!! ^_^
How do I store this?
key value
key value
homepage_message omg cheezburgers...
key value
homepage_message omg cheezburgers...
tps_reports new cover pages on...
You already know it.
$client->set('home:message', 'cheezburgers...');
$client->get('home:message');
EqualEasier
More Fun
Use Case #3: Hit Counters
increment
$client->incr('page:42:views');
$client->incr('page:42:views');
$client->incr('page:42:views');
// 1
// 2
// 3
Redis is hard ;)
How is this better?
Fast?
redis-benchmark
====== INCR ======
10000 requests completed in 0.08 seconds
50 parallel clients
3 bytes payload
keep alive: 1
100.00% <= 0 milliseconds
119047.62 requests per second
Fast enough.
$client->incr('cookies:eaten');
$client->incrBy('cookies:eaten', 2);
$client->incrByFloat('cookies:eaten', 0.5); version 2.6+
Use Case #4: Latest News
“It is often referred to as a data structure server...”
“...since keys can contain strings, hashes, lists, sets and sorted sets.”
$redis = array();
GenericHashListSetSorted Set
GenericHashListSetSorted Set
// strings and numbers
$redis['ross:mood'] = "happy";
$redis['foo'] = 9;
GenericHashListSetSorted Set
// associative array
$redis['foo']['name'] = 'Bob';
$redis['foo']['age'] = 31;
Objects, forms, records
GenericHashListSetSorted Set
// not associative
$redis['foo'][] = 'zip';
$redis['foo'][] = 'zap';
Lists, stacks, queues
GenericHashListSetSorted Set
// No dupes, no order
shuffle(
array_unique($redis['foo'])
);
Relations, stats, matching
GenericHashListSetSorted Set
// Ordered by *score*
array_unique($redis['foo']);
Curing cancer, world peaceSets but with order or scores
Y U NO STOPY U NO STOP
LISTING THINGSLISTING THINGS
GenericHashListSetSorted Set
GenericHashListSetSorted Set
// Code
$client->lpush('news:latest', 'Aliens Attack!');
// Redis
['Aliens Attack!']
// Redis
['Takei 2016', 'Aliens Attack!']
// 2 hours later...
$client->lpush('news:latest', 'Takei 2016');
// That evening...
$client->lpush('news:latest', 'Eggs = Cancer!');
// Redis
['Eggs = Cancer!', 'Takei 2016', 'Aliens Attack!']
Recap
// Code
$client->lpush('news:latest', 'Aliens Attack!');
$client->lpush('news:latest', 'Takei 2016');
$client->lpush('news:latest', 'Eggs = Cancer!');
// Redis
['Eggs = Cancer!', 'Takei 2016', 'Aliens Attack!']
Getting it back out?
$client->lrange('news:latest', 0, 1);
End Index
Start Index
var_dump(
$client->lrange('news:latest', 0, 1)
);
array(2) {
[0]=> string(14) "Eggs = Cancer!"
[1]=> string(10) "Takei 2016"
}
That's it.Really.
What about size?
$client->lpush('news:latest', 'Free Jetpacks!');
$client->lpush('news:latest', 'Elvis Found!');
$client->lpush('news:latest', 'Takei Wins!');
//...and so on...
ltrim
$client->ltrim('news:latest', 0, 2);
// Only the three latest stories remain!
Cron
or simpler...
$client->lpush('news:latest', 'Cats Leave Euro');
$client->ltrim('news:latest', 0, 2);
Great option for notifications
Use Case #5: Tricksy Caching
SELECT * FROM Articles
INNER JOIN Authors ON (complicated joins)
-- More joins
WHERE (complicated logic)
LIMIT 0, 20
SELECT Articles.id FROM Articles
INNER JOIN Authors ON (complicated joins)
-- More joins
WHERE (complicated logic)
$client->lpush('search:a17f3', $ids);
$client->lrange('search:a17f3', $limit, $offset);
SELECT * FROM Articles
INNER JOIN Authors ON (complicated joins)
-- More joins
WHERE Articles.id IN (1, 2, 3)
Use Case #6: Recently Viewed
GenericHashListSetSorted Set
GenericHashListSetSorted Set
No duplicates
GenericHashListSetSorted Set
Needs to be ordered
Just Right
GenericHashListSetSorted Set
zadd
$client->zadd('mykey', 1, 'mydata');
Any integer
or float
$client->zadd('recent', 1, '/p/first');
$client->zadd('recent', time(), '/p/first');
$client->zadd('recent', 1338020901, '/p/first');
$client->zadd('recent', 1338020902, '/p/second');
$client->zadd('recent', 1338020903, '/p/third');
$client->zadd('recent', 1338020904, '/p/fourth');
Reading it back out?
array(3) {
[0]=> string(8) "/p/first"
[1]=> string(9) "/p/second"
[2]=> string(8) "/p/third"
}
$client->zrange('recent', 0, 2);
$client->zrevrange('recent', 0, 2);
Reverse
array(3) {
[0]=> string(9) "/p/fourth"
[1]=> string(8) "/p/third"
[2]=> string(9) "/p/second"
}
Duplicates?
$client->zadd('recent', 1338020901, '/p/first');
// later...
$client->zadd('recent', 1338020928, '/p/first');
array(3) {
[0]=> string(8) "/p/first"
[1]=> string(9) "/p/fourth"
[2]=> string(8) "/p/third"
}
$client->zrevrange('recent', 0, 2);
Cool.
Other things we can do?
$client->zrangebyscore('recent', $low, $high);
$yesterday = time()-(60*60*24);
$client->zrangebyscore('recent', $yesterday, '+inf');
Intersections
zinterstore
$client->zinterstore('omg', 2, 'recent', 'favorite');
$client->zrange('omg', 0, 4);
Deletion
zrem
zremrangebyscore
$yesterday = time()-(60*60*24);
$client->zremrangebyscore(
'recent', '-inf', $yesterday
);
We can do a lot.
Scores can be anything.
Use Case #7: Sharing Data
Redis
PHP
Node.js Python
• ActionScript• C• C#• C++• Clojure• Common Lisp• Erlang• Fancy• Go• Haskell• haXe• Io
• Java• Lua• Node.js• Objective-C• Perl• PHP• Pure Data• Python• Ruby• Scala• Smalltalk• Tcl
$client = new \Predis\Client();
$client->set('foo', 'bar');
var redis = require("redis");
var client = redis.createClient();
client.get("foo", redis.print);
Step Further...
Pub/Sub
$client->publish('bids:42', '$13.01');
client.on("message", function (channel, message) {
console.log(channel + "= " + message);
});
client.subscribe("bids:42");
// prints “bids:42= $13.01”
Not everydayyet
Use Case #8: Worker Queues
$client->lpush('jobs:pending', 'clear_cache');
$client->lpush('jobs:pending', '{"do":"email", …}');
$client->lpush('jobs:pending', 'job:45');
// worker
$client = new \Predis\Client(array(
'read_write_timeout' => -1
));
do { $job = $client->brpop('jobs:pending', 0);
doJob($job);
} while(true);
This will work.
However...
Things break.
Things break.
Clients break.
Clients break.
Clients crash.
do { $job = $client->brpop('jobs:pending', 0);
doJob($job);
} while(true);
Multiple keys is the redis way.
'jobs:pending'
'jobs:working'
What we need is: blockingrpoplpush
brpoplpush
No, really.
do {
$job = $client->brpoplpush(
'jobs:pending', 'jobs:working', 0
);
doJob($job);
} while(true);
do {
$job = $client->brpoplpush(
'jobs:pending', 'jobs:working', 0
);
if(doJob($job)) {
$client->lrem('jobs:working', 0, $job);
}
} while(true);
Use Case #9: Scripted Commands
Use Case #9: Impressing People
$client->set('jesse', 'dude');
$client->set('chester', 'sweet');
Lua
local first = redis.call('get', KEYS[1]);
local second = redis.call('get', KEYS[2]);
redis.call('set', KEYS[1], second);
redis.call('set', KEYS[2], first);
return {first, second};
var arguments
// jesse: dude
// chester: sweet
EVAL 'local first...' 2 jesse chester
// jesse: sweet
// chester: dude
Eval != Evil
*Offer only appliesin Redis-Land.
Void where pedantic.
Don't over do it.
Reusing scripts?
SCRIPT LOAD 'local first...'
// 591d1b681192f606d8cb658e1e173e771a90e60e
EVAL 'local first...' 2 jesse chester
EVALSHA 591d1... 2 jesse chester
Sweet.
Epilogue
Simple = Powerful
Fun.
Keep it in RAM.
Extensible + Ecosystem
Great docs.Use them.
Bad for the DB?Might be good for Redis.
Bad for the DB?Might be good for Redis.
@svdgraaf
QuickMeme
IAEA Kotaku
Alltheragefaces
http://joind.in/7971