Redis for the Everyday Developer

196
Ross Tuck Redis For The Everyday Developer Confoo March 1 st , 2013

description

As presented at Confoo 2013. More than some arcane NoSQL tool, Redis is a simple but powerful swiss army knife you can begin using today. This talk introduces the audience to Redis and focuses on using it to cleanly solve common problems. Along the way, we'll see how Redis can be used as an alternative to several common PHP tools.

Transcript of Redis for the Everyday Developer

Page 1: Redis for the Everyday Developer

Ross Tuck

Redis For The Everyday Developer

ConfooMarch 1st, 2013

Page 2: Redis for the Everyday Developer

Who Am I?

Page 3: Redis for the Everyday Developer

Ross Tuck

Page 4: Redis for the Everyday Developer

Team Lead at IbuildingsCodemonkeyHTTP nutHat guy

Page 5: Redis for the Everyday Developer

@rosstuck

Page 6: Redis for the Everyday Developer

Boring.

Page 8: Redis for the Everyday Developer

NoSQL

Page 9: Redis for the Everyday Developer

NoNoSQL

Page 10: Redis for the Everyday Developer

Evaluation

Page 11: Redis for the Everyday Developer

• Created by Salvatore Sanfilippo ( @antirez )• Sponsored by VMware• 4 years old• redis.io

Page 12: Redis for the Everyday Developer

“Redis is an open source, advanced key-value store.”

Page 13: Redis for the Everyday Developer

“It is often referred to as a data structure server...”

Page 14: Redis for the Everyday Developer

Defancypants

Page 15: Redis for the Everyday Developer

Server A Server B

Redis

Page 16: Redis for the Everyday Developer

In-Memory

Page 17: Redis for the Everyday Developer

In-Memory

Page 18: Redis for the Everyday Developer

The Cool Stuff

Page 19: Redis for the Everyday Developer

Fast.

Page 20: Redis for the Everyday Developer

Demo.

Page 21: Redis for the Everyday Developer

Oh wait, it's already done.

Page 22: Redis for the Everyday Developer

120,000~ ops per sec

Page 23: Redis for the Everyday Developer

Flexible.

Page 24: Redis for the Everyday Developer

(optionally)Persistent.

Page 25: Redis for the Everyday Developer

ReplicationPub/Sub

TransactionsScripting

SlicesDices

Makes julienne fries

Page 26: Redis for the Everyday Developer

“Well, Ross, that sounds cool...”

Page 27: Redis for the Everyday Developer
Page 28: Redis for the Everyday Developer

MySQL Redis

+

Page 29: Redis for the Everyday Developer

MySQL+

Redis

4ever

Page 30: Redis for the Everyday Developer

Setup

Page 31: Redis for the Everyday Developer

apt-get install redis-server

Easy but old

Page 32: Redis for the Everyday Developer

http://redis.io/download

Page 33: Redis for the Everyday Developer

PHP Libraries

• 5.3+: Predis

• 5.2: Predis or Rediska

• C Extension: phpredis

• redis-cli

Page 34: Redis for the Everyday Developer

$client = new \Predis\Client();

Page 36: Redis for the Everyday Developer

PHPMySQL

Memcache

Page 37: Redis for the Everyday Developer

PHPMySQL

Memcache

Page 38: Redis for the Everyday Developer

PHPMySQLRedis

Page 39: Redis for the Everyday Developer

It's about 80% cases.

Page 40: Redis for the Everyday Developer

Use Case #1: Caching

Page 41: Redis for the Everyday Developer

Redis has commands.

Page 42: Redis for the Everyday Developer

Commands have parameters.

Page 43: Redis for the Everyday Developer

Think functions.

Page 44: Redis for the Everyday Developer

$client->commandGoesHere($params, $go, $here);

Page 45: Redis for the Everyday Developer

SET

Page 46: Redis for the Everyday Developer

$client->set('ross:mood', 'nervous');

// Later

$client->get('ross:mood'); // returns “nervous”

Page 47: Redis for the Everyday Developer

$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

Page 48: Redis for the Everyday Developer

$client->set('ross:mood', 'nervous');

$client->expire('ross:mood', 5);

Page 49: Redis for the Everyday Developer

$client->setex('ross:mood', 5, 'nervous');

Page 50: Redis for the Everyday Developer

Great for caching

Page 51: Redis for the Everyday Developer

Ideal for sessions

Page 52: Redis for the Everyday Developer

Use Case #2: Simple Data

Page 53: Redis for the Everyday Developer

Search...

omg cheezburgers in the lunchroomtoday. Better hurry if u want 1!!! ^_^

How do I store this?

Page 54: Redis for the Everyday Developer

key value

Page 55: Redis for the Everyday Developer

key value

homepage_message omg cheezburgers...

Page 56: Redis for the Everyday Developer

key value

homepage_message omg cheezburgers...

tps_reports new cover pages on...

Page 57: Redis for the Everyday Developer

You already know it.

Page 58: Redis for the Everyday Developer

$client->set('home:message', 'cheezburgers...');

$client->get('home:message');

Page 59: Redis for the Everyday Developer

EqualEasier

More Fun

Page 60: Redis for the Everyday Developer

Use Case #3: Hit Counters

Page 61: Redis for the Everyday Developer
Page 62: Redis for the Everyday Developer

increment

Page 63: Redis for the Everyday Developer

$client->incr('page:42:views');

$client->incr('page:42:views');

$client->incr('page:42:views');

// 1

// 2

// 3

Page 64: Redis for the Everyday Developer

Redis is hard ;)

Page 65: Redis for the Everyday Developer

How is this better?

Page 66: Redis for the Everyday Developer
Page 67: Redis for the Everyday Developer

Fast?

Page 68: Redis for the Everyday Developer

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

Page 69: Redis for the Everyday Developer

Fast enough.

Page 70: Redis for the Everyday Developer

$client->incr('cookies:eaten');

$client->incrBy('cookies:eaten', 2);

$client->incrByFloat('cookies:eaten', 0.5); version 2.6+

Page 71: Redis for the Everyday Developer

Use Case #4: Latest News

Page 72: Redis for the Everyday Developer
Page 73: Redis for the Everyday Developer

“It is often referred to as a data structure server...”

Page 74: Redis for the Everyday Developer

“...since keys can contain strings, hashes, lists, sets and sorted sets.”

Page 75: Redis for the Everyday Developer

$redis = array();

Page 76: Redis for the Everyday Developer

GenericHashListSetSorted Set

Page 77: Redis for the Everyday Developer

GenericHashListSetSorted Set

// strings and numbers

$redis['ross:mood'] = "happy";

$redis['foo'] = 9;

Page 78: Redis for the Everyday Developer

GenericHashListSetSorted Set

// associative array

$redis['foo']['name'] = 'Bob';

$redis['foo']['age'] = 31;

Objects, forms, records

Page 79: Redis for the Everyday Developer

GenericHashListSetSorted Set

// not associative

$redis['foo'][] = 'zip';

$redis['foo'][] = 'zap';

Lists, stacks, queues

Page 80: Redis for the Everyday Developer

GenericHashListSetSorted Set

// No dupes, no order

shuffle(

array_unique($redis['foo'])

);

Relations, stats, matching

Page 81: Redis for the Everyday Developer

GenericHashListSetSorted Set

// Ordered by *score*

array_unique($redis['foo']);

Curing cancer, world peaceSets but with order or scores

Page 82: Redis for the Everyday Developer

Y U NO STOPY U NO STOP

LISTING THINGSLISTING THINGS

Page 83: Redis for the Everyday Developer

GenericHashListSetSorted Set

Page 84: Redis for the Everyday Developer

GenericHashListSetSorted Set

Page 85: Redis for the Everyday Developer

// Code

$client->lpush('news:latest', 'Aliens Attack!');

// Redis

['Aliens Attack!']

Page 86: Redis for the Everyday Developer

// Redis

['Takei 2016', 'Aliens Attack!']

// 2 hours later...

$client->lpush('news:latest', 'Takei 2016');

Page 87: Redis for the Everyday Developer

// That evening...

$client->lpush('news:latest', 'Eggs = Cancer!');

// Redis

['Eggs = Cancer!', 'Takei 2016', 'Aliens Attack!']

Page 88: Redis for the Everyday Developer

Recap

Page 89: Redis for the Everyday Developer

// 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!']

Page 90: Redis for the Everyday Developer

Getting it back out?

Page 91: Redis for the Everyday Developer

$client->lrange('news:latest', 0, 1);

End Index

Start Index

Page 92: Redis for the Everyday Developer

var_dump(

$client->lrange('news:latest', 0, 1)

);

array(2) {

[0]=> string(14) "Eggs = Cancer!"

[1]=> string(10) "Takei 2016"

}

Page 93: Redis for the Everyday Developer

That's it.Really.

Page 94: Redis for the Everyday Developer
Page 95: Redis for the Everyday Developer

What about size?

Page 96: Redis for the Everyday Developer

$client->lpush('news:latest', 'Free Jetpacks!');

$client->lpush('news:latest', 'Elvis Found!');

$client->lpush('news:latest', 'Takei Wins!');

//...and so on...

Page 97: Redis for the Everyday Developer

ltrim

Page 98: Redis for the Everyday Developer

$client->ltrim('news:latest', 0, 2);

// Only the three latest stories remain!

Page 99: Redis for the Everyday Developer

Cron

Page 100: Redis for the Everyday Developer

or simpler...

Page 101: Redis for the Everyday Developer

$client->lpush('news:latest', 'Cats Leave Euro');

$client->ltrim('news:latest', 0, 2);

Page 102: Redis for the Everyday Developer

Great option for notifications

Page 103: Redis for the Everyday Developer
Page 104: Redis for the Everyday Developer
Page 105: Redis for the Everyday Developer
Page 106: Redis for the Everyday Developer

Use Case #5: Tricksy Caching

Page 107: Redis for the Everyday Developer
Page 108: Redis for the Everyday Developer

SELECT * FROM Articles

INNER JOIN Authors ON (complicated joins)

-- More joins

WHERE (complicated logic)

LIMIT 0, 20

Page 109: Redis for the Everyday Developer

SELECT Articles.id FROM Articles

INNER JOIN Authors ON (complicated joins)

-- More joins

WHERE (complicated logic)

Page 110: Redis for the Everyday Developer

$client->lpush('search:a17f3', $ids);

Page 111: Redis for the Everyday Developer

$client->lrange('search:a17f3', $limit, $offset);

Page 112: Redis for the Everyday Developer

SELECT * FROM Articles

INNER JOIN Authors ON (complicated joins)

-- More joins

WHERE Articles.id IN (1, 2, 3)

Page 113: Redis for the Everyday Developer
Page 114: Redis for the Everyday Developer

Use Case #6: Recently Viewed

Page 115: Redis for the Everyday Developer
Page 116: Redis for the Everyday Developer

GenericHashListSetSorted Set

Page 117: Redis for the Everyday Developer

GenericHashListSetSorted Set

No duplicates

Page 118: Redis for the Everyday Developer

GenericHashListSetSorted Set

Needs to be ordered

Page 119: Redis for the Everyday Developer

Just Right

GenericHashListSetSorted Set

Page 120: Redis for the Everyday Developer

zadd

Page 121: Redis for the Everyday Developer

$client->zadd('mykey', 1, 'mydata');

Any integer

or float

Page 122: Redis for the Everyday Developer

$client->zadd('recent', 1, '/p/first');

Page 123: Redis for the Everyday Developer

$client->zadd('recent', time(), '/p/first');

Page 124: Redis for the Everyday Developer

$client->zadd('recent', 1338020901, '/p/first');

$client->zadd('recent', 1338020902, '/p/second');

$client->zadd('recent', 1338020903, '/p/third');

$client->zadd('recent', 1338020904, '/p/fourth');

Page 125: Redis for the Everyday Developer

Reading it back out?

Page 126: Redis for the Everyday Developer

array(3) {

[0]=> string(8) "/p/first"

[1]=> string(9) "/p/second"

[2]=> string(8) "/p/third"

}

$client->zrange('recent', 0, 2);

Page 127: Redis for the Everyday Developer

$client->zrevrange('recent', 0, 2);

Reverse

array(3) {

[0]=> string(9) "/p/fourth"

[1]=> string(8) "/p/third"

[2]=> string(9) "/p/second"

}

Page 128: Redis for the Everyday Developer

Duplicates?

Page 129: Redis for the Everyday Developer

$client->zadd('recent', 1338020901, '/p/first');

// later...

$client->zadd('recent', 1338020928, '/p/first');

Page 130: Redis for the Everyday Developer

array(3) {

[0]=> string(8) "/p/first"

[1]=> string(9) "/p/fourth"

[2]=> string(8) "/p/third"

}

$client->zrevrange('recent', 0, 2);

Page 131: Redis for the Everyday Developer

Cool.

Page 132: Redis for the Everyday Developer

Other things we can do?

Page 133: Redis for the Everyday Developer

$client->zrangebyscore('recent', $low, $high);

Page 134: Redis for the Everyday Developer

$yesterday = time()-(60*60*24);

$client->zrangebyscore('recent', $yesterday, '+inf');

Page 135: Redis for the Everyday Developer

Intersections

Page 136: Redis for the Everyday Developer

zinterstore

Page 137: Redis for the Everyday Developer

$client->zinterstore('omg', 2, 'recent', 'favorite');

$client->zrange('omg', 0, 4);

Page 138: Redis for the Everyday Developer

Deletion

Page 139: Redis for the Everyday Developer

zrem

Page 140: Redis for the Everyday Developer

zremrangebyscore

Page 141: Redis for the Everyday Developer

$yesterday = time()-(60*60*24);

$client->zremrangebyscore(

'recent', '-inf', $yesterday

);

Page 142: Redis for the Everyday Developer

We can do a lot.

Page 143: Redis for the Everyday Developer

Scores can be anything.

Page 144: Redis for the Everyday Developer

Use Case #7: Sharing Data

Page 145: Redis for the Everyday Developer

Redis

PHP

Node.js Python

Page 146: Redis for the Everyday Developer

• 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

Page 147: Redis for the Everyday Developer

$client = new \Predis\Client();

$client->set('foo', 'bar');

Page 148: Redis for the Everyday Developer

var redis = require("redis");

var client = redis.createClient();

client.get("foo", redis.print);

Page 149: Redis for the Everyday Developer

Step Further...

Page 150: Redis for the Everyday Developer

Pub/Sub

Page 151: Redis for the Everyday Developer

$client->publish('bids:42', '$13.01');

Page 152: Redis for the Everyday Developer

client.on("message", function (channel, message) {

console.log(channel + "= " + message);

});

client.subscribe("bids:42");

// prints “bids:42= $13.01”

Page 153: Redis for the Everyday Developer

Not everydayyet

Page 154: Redis for the Everyday Developer

Use Case #8: Worker Queues

Page 155: Redis for the Everyday Developer

$client->lpush('jobs:pending', 'clear_cache');

Page 156: Redis for the Everyday Developer

$client->lpush('jobs:pending', '{"do":"email", …}');

Page 157: Redis for the Everyday Developer

$client->lpush('jobs:pending', 'job:45');

Page 158: Redis for the Everyday Developer

// worker

$client = new \Predis\Client(array(

'read_write_timeout' => -1

));

do { $job = $client->brpop('jobs:pending', 0);

doJob($job);

} while(true);

Page 159: Redis for the Everyday Developer

This will work.

Page 160: Redis for the Everyday Developer

However...

Page 161: Redis for the Everyday Developer

Things break.

Page 162: Redis for the Everyday Developer

Things break.

Page 163: Redis for the Everyday Developer

Clients break.

Page 164: Redis for the Everyday Developer

Clients break.

Page 165: Redis for the Everyday Developer

Clients crash.

Page 166: Redis for the Everyday Developer

do { $job = $client->brpop('jobs:pending', 0);

doJob($job);

} while(true);

Page 167: Redis for the Everyday Developer

Multiple keys is the redis way.

Page 168: Redis for the Everyday Developer

'jobs:pending'

'jobs:working'

Page 169: Redis for the Everyday Developer

What we need is: blockingrpoplpush

Page 170: Redis for the Everyday Developer

brpoplpush

No, really.

Page 171: Redis for the Everyday Developer

do {

$job = $client->brpoplpush(

'jobs:pending', 'jobs:working', 0

);

doJob($job);

} while(true);

Page 172: Redis for the Everyday Developer

do {

$job = $client->brpoplpush(

'jobs:pending', 'jobs:working', 0

);

if(doJob($job)) {

$client->lrem('jobs:working', 0, $job);

}

} while(true);

Page 173: Redis for the Everyday Developer

Use Case #9: Scripted Commands

Page 174: Redis for the Everyday Developer

Use Case #9: Impressing People

Page 175: Redis for the Everyday Developer

$client->set('jesse', 'dude');

$client->set('chester', 'sweet');

Page 176: Redis for the Everyday Developer

Lua

Page 177: Redis for the Everyday Developer

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

Page 178: Redis for the Everyday Developer

// jesse: dude

// chester: sweet

EVAL 'local first...' 2 jesse chester

// jesse: sweet

// chester: dude

Page 179: Redis for the Everyday Developer

Eval != Evil

*Offer only appliesin Redis-Land.

Void where pedantic.

Page 180: Redis for the Everyday Developer

Don't over do it.

Page 181: Redis for the Everyday Developer

Reusing scripts?

Page 182: Redis for the Everyday Developer

SCRIPT LOAD 'local first...'

// 591d1b681192f606d8cb658e1e173e771a90e60e

Page 183: Redis for the Everyday Developer

EVAL 'local first...' 2 jesse chester

Page 184: Redis for the Everyday Developer

EVALSHA 591d1... 2 jesse chester

Page 185: Redis for the Everyday Developer

Sweet.

Page 186: Redis for the Everyday Developer

Epilogue

Page 187: Redis for the Everyday Developer

Simple = Powerful

Page 188: Redis for the Everyday Developer

Fun.

Page 189: Redis for the Everyday Developer

Keep it in RAM.

Page 190: Redis for the Everyday Developer

Extensible + Ecosystem

Page 191: Redis for the Everyday Developer

Great docs.Use them.

Page 192: Redis for the Everyday Developer

Bad for the DB?Might be good for Redis.

Page 193: Redis for the Everyday Developer

Bad for the DB?Might be good for Redis.

Page 194: Redis for the Everyday Developer
Page 195: Redis for the Everyday Developer

@svdgraaf

QuickMeme

IAEA Kotaku

Alltheragefaces

Page 196: Redis for the Everyday Developer

http://joind.in/7971