4. Itâs like Redis Pub/Sub ...
⊠with millionsof concurrentclients
⊠on the Internet
⊠using WebSocketconnections
⊠hosted at
pusher.com
⊠plus extra features (presence,
encryption, debug console âŠ)
12. The Channels box must keep a map of
channel name to clients.
client 2345
user-jim
Channels
donuts
client 234
donuts btc-usd
user-jim
donuts
btc-usd
13. client 2345
user-jim
Channels
donuts
client 234
donuts btc-usd
user-jim
donuts
btc-usd
Letâs count subscriptions.
How many subscriptions are there for the
channel âdonutsâ?
Easy: find the âdonutsâ key of the map, then
count the number of connections.
(The answer is: 2.)
16. client 123.2345
user-jim
socket 123 socket 124 socket 125
donuts
client 123.234
donuts
client 124.23
donuts btc-usd
client 125.1
donutsbtc-usd
user-jim
donuts
btc-usd
donuts
btc-usd
donuts
To scale out: add more processes!
We call these âsocket processesâ.
17. client 123.2345
user-jim
socket 123 socket 124 socket 125
donuts
client 123.234
donuts
client 124.23
donuts btc-usd
client 125.1
donutsbtc-usd
user-jim
donuts
btc-usd
donuts
btc-usd
donuts
redis-pubsub
After adding more processes, where
should you publish to?
These socket processes need to
communicate somehow.
For this, we use Redis.
18. client 123.2345
user-jim
socket 123 socket 124 socket 125
donuts
client 123.234
donuts
client 124.23
donuts btc-usd
client 125.1
donutsbtc-usd
user-jim
donuts
btc-usd
donuts
btc-usd
donuts
redis-pubsub
user-jim
donuts
btc-usd
Specifically, we use the Redis Pub/Sub
feature. Hence, we call this Redis
âredis-pubsubâ.
Notice redis-pubsub looks a lot like a
socket process! Redis Pub/Sub provides a
very similar mapping from âchannel
namesâ to connections.
20. client 123.2345
user-jim
socket 123 socket 124 socket 125
donuts
client 123.234
donuts
client 124.23
donuts btc-usd
client 125.1
donutsbtc-usd
user-jim
donuts
btc-usd
donuts
btc-usd
donuts
redis-pubsub
user-jim
donuts
btc-usd
Letâs count subscriptions again.
How many subscriptions are there for the
channel âdonutsâ? We can ask Redis:
127.0.0.1:6381[2]> pubsub numsub donuts
1) "donuts"
2) "3"
Wrong! Socket 123 has two end-user
subscriptions for âdonutsâ, so the answer
should be â4â.
21. client 123.2345
user-jim
socket 123 socket 124 socket 125
donuts
client 123.234
donuts
client 124.23
donuts btc-usd
client 125.1
donutsbtc-usd
user-jim
donuts
btc-usd
donuts
btc-usd
donuts
redis-pubsub
user-jim
donuts
btc-usd
Instead of counting connections to socket
processes, we must keep separate counters
for the number of connections to clients.
Unfortunately, Redis Pub/Sub doesnât
provide this hypothetical feature, so ...
1 2 4
22. redis-main
client 123.2345
user-jim
socket 123 socket 124 socket 125
donuts
client 123.234
donuts
client 124.23
donuts btc-usd
client 125.1
donutsbtc-usd
user-jim
donuts
btc-usd
donuts
btc-usd
donuts
redis-pubsub
user-jim
donuts
btc-usd
⊠for subscription
counts, we have
another Redis we call
âredis-mainâ.
All socket processes
connect to this redis,
too.
23. redis-main
client 123.2345
user-jim
socket 123 socket 124 socket 125
donuts
client 123.234
donuts
client 124.23
donuts btc-usd
client 125.1
donutsbtc-usd
user-jim
donuts
btc-usd
donuts
btc-usd
donuts
redis-pubsub
user-jim
donuts
btc-usd
user-jim
donuts
btc-usd
1
4
2
channels:count:global
redis-main keeps the
per-channel subscription
counts in a Redis hash
âchannels:count:globalâ.
When a subscription to
âdonutsâ is added or
removed on a socket
box, we do:
HINCRBY channels:count:global donuts 1
HINCRBY channels:count:global donuts -1
27. redis-main
socket 124 socket 125
client 124.23
donuts btc-usd
client 125.1
donuts
donuts
btc-usd
donuts
redis-pubsub
donuts
btc-usd
user-jim
donuts
btc-usd
1
4
2
channels:count:global
Redis-pubsub was able to
clean up, but what about
redis-main? Letâs count
subscriptions again.
How many subscriptions
are there for the channel
âdonutsâ?
Not four, but two! The
per-channel subscription
counters are wrong!
We need to subtract the
two client subscriptions to
âdonutsâ from the counter,
but that number is now
lost ...
28. redis-main
client 123.2345
user-jim
socket 123 socket 124 socket 125
donuts
client 123.234
donuts
client 124.23
donuts btc-usd
client 125.1
donutsbtc-usd
user-jim
donuts
btc-usd
donuts
btc-usd
donuts
redis-pubsub
user-jim
donuts
btc-usd
user-jim
donuts
btc-usd
1
4
2
channels:count:global
user-jim
donuts
btc-usd
1
2
1
channels:count:123
donuts
btc-usd
2
1
channels:count:124
donuts 1
channels:count:125
We must keep the
per-channel connection
counts for each socket
process, so that when
the socket process
dies, its counts can be
subtracted.
For each socket
process, we keep
another Redis hash of
its subscription counts.
For example, socket
process 123 has the
hash
âchannels:count:123â.
31. redis-main
socket 124 socket 125
client 124.23
donuts btc-usd
client 125.1
donuts
donuts
btc-usd
donuts
redis-pubsub
donuts
btc-usd
user-jim
donuts
btc-usd
1
4
2
channels:count:global
user-jim
donuts
btc-usd
1
2
1
channels:count:123
donuts
btc-usd
2
1
channels:count:124
donuts 1
channels:count:125
Redis-pubsub is able to
clean up, as before.
But redis-main still
doesnât do anything!
Why?
Unfortunately,
redis-main doesnât
know the relationship
between the lost
connection and the id
â123â.
Instead, we must
detect dead socket
processes ourselves,
and clean up after
them.
35. redis-main
socket 124 socket 125
client 124.23
donuts btc-usd
client 125.1
donuts
donuts
btc-usd
donuts
redis-pubsub
donuts
btc-usd
125
123
124
17:46
17:35
17:46
socket:process:all
user-jim
donuts
btc-usd
1
4
2
channels:count:global
user-jim
donuts
btc-usd
1
2
1
channels:count:123
donuts
btc-usd
2
1
channels:count:124
donuts 1
channels:count:125
But still
nothing
happens!
Redis-main
doesnât know
about our
socket
process list.
Instead, we
must check it
ourselves ...
36. redis-main
socket 124 socket 125
client 124.23
donuts btc-usd
client 125.1
donuts
donuts
btc-usd
donuts
redis-pubsub
donuts
btc-usd
cleanup-dead
125
123
124
17:46
17:35
17:46
socket:process:all
user-jim
donuts
btc-usd
1
4
2
channels:count:global
user-jim
donuts
btc-usd
1
2
1
channels:count:123
donuts
btc-usd
2
1
channels:count:124
donuts 1
channels:count:125
We do this with
a separate
process,
âcleanup-deadâ.
37. redis-main
socket 124 socket 125
client 124.23
donuts btc-usd
client 125.1
donuts
donuts
btc-usd
donuts
redis-pubsub
donuts
btc-usd
cleanup-dead
125
124
17:46
17:46
socket:process:all
donuts
btc-usd
2
1
channels:count:global
donuts
btc-usd
2
1
channels:count:124
donuts 1
channels:count:125
Cleanup-dead
runs a job every
4 minutes. If a
socket has been
inactive for more
than 10 minutes,
it considers it
dead.
It decrements the
socketâs counts
from the global
counts. Then it
deletes the
socketâs counts.
Then it deletes
the socket from
the âlast seenâ
times.
40. redis
Client C
Local set A
Partial
global index
Application code
Client B
Local set
Partial
global index
Application code
Set C
Set B
Client A
Application
State A
Global
indexes:
A + B + C
Application code
Important
State A
Global
indexes:
A + B + C