A few weeks ago I started thinking about mixing together the Play! Framework's facilities for handling reactive data streams with Redis' pub/sub messaging system. This is part of a larger project I'm working on which will allow operating on Scala types which persist to a Redis store.
This post covers my first pass at using Redis pub/sub as the source and destination of iteratees and enumerators. The solution presented here is a bit hack-ish for me and I'm working on refining it but I wanted to get my initial pass at the problem out in the open.
Let's start with a quick review of Redis's pub/sub features. Any client can publish to a channel and any client can subscribe. Channels aren't anything special, just a key in the Redis store. A client subscribed to that key will block until it unsubscribes.
Before I go on to describe iteratees and enumerators, let's take a look at the architecture we'll end up with at the end of this post. Remember that we're building a distributed chat room.
Before I go on to describe iteratees and enumerators, let's take a look at the architecture we'll end up with at the end of this post. Remember that we're building a distributed chat room.
Multiple instances of Play!, one Redis store. First, the user in the top left sends a message ("Hi!") to the chat room (green). Second, the Play! instance that user is on receives the message over a WebSockets connection in an enumerator. Two iteratees have been applied on that enumerator, one to publish the message to the Redis store and another to publish it to the other users subscribed to this specific instance (blue).
Finally, the Redis store pushes the message out to all subscribed clients (red). These clients will receive the message and push it into a channel (a type of push enumerator) which will then by sent onwards to whichever chat users might be subscribed.
The important thing here is that these operations are all asynchronous and non-blocking. Moreover, the use of Redis pub/sub allows this architecture to scale horizontally—just add more Play! instances.
Now, it's not perfect. Given the current state of Redis clients for Scala, this only works if each instance is subscribed to a single channel, using it for some sort of multi-room chat would not scale as easily. I'm hopeful that an asynchronous Redis client will appear in the future.
The source code for this little demo is available at https://github.com/ryantanner/websocketchat-redis. Only a few modifications were made to Play!'s existing WebSocket chat demo, a testament to the flexibility of the enumerator/iteratee paradigm.
