At Gawker we are using a combination of Play and Akka to build our backend infrastructure. However, since we are moving more and more towards a distributed event driven architecture, any new JVM technology that facilitates this kind of design is of great interest for us.
What is Reactor?
Reactor tries to be the foundation for asynchronous applications on the JVM. It supports many programming models and styles from observables through PubSub to callbacks. It even comes with its own Future and Promise implementations (and if those ideas sound familiar, that's because there are quite a few of them on the JVM right now).
Demo: a non-blocking Reactor based HTTP server
We are going to implement a simple non-blocking HTTP server using Reactor and NIO, but before we jump right into it, let's clear up a few terms.
In every Reactor application there is at least one reactor instance which is responsible for message sending. Clients can subscribe to a single or multiple channels and be notified if events occur. Clients can also reply to messages.
While events are sent in a fire-and-forget fashion and error handling is managed via callbacks, there is no built-in retry mechanism nor are there any mailboxes. Other notable limitations are the lack of remoting (i.e. message sending is limited to a single JVM) and clustering (i.e. there is no mechanism provided by Reactor to avoid single point of failures or single point of bottlenecks).
But how are messages distributed? That's where dispatchers come in. Each reactor is backed by a single dispatcher. There are four basic dispatchers that come out of the box:
- Event loop (provides a single-threaded event loop)
- RingBuffer (Distruptor based, single-threaded, high throughput)
- Synchronous (useful mainly for testing)
- ThreadPool (backed by a thread pool)
Depending on the use case, we can pick and choose or even implement our own.
The normal reactor workflow consists of creating a reactor instance, setting up listeners (consumers) and implementing message senders (publishers). The default reactor model is relying on callbacks. However, clients can work with Streams (that allow you to process streams of data), Promises (that represent Streams that can be processed only once) and Futures (that act as generic, redeemable values).
On the JVM side, the new IO library (java.nio) was originally introduced in java 4 and was greatly improved in java 7. When it comes to implementing an HTTP server, there are two key components we need to be familiar with:
- SocketChannel (selectable channel for stream-oriented socket listening)
- Selector (responsible for multiplexing events)
The usual workflow consists of creating an event loop (while setting the server to non-blocking), selecting and identifying events and finally, depending on the event type, we need to read and write events.
Now that the basic terminology is out of the way, let's move on to the actual implementation!
You can find the whole project on github here
Here is the end user API we are aiming for:
Notice that the handler method is single-threaded and the request needs to be ended manually. That's because in an evented system we might want to write all content in one go or in chunks, potentially from a different thread (note: as another implementation we could also have returned a Promise of HTTPResponse, but given the verbosity of anonymous classes in java, the current API felt a bit cleaner).
Moving on, the handler method, that is called with the actual user code (DemoHandler), is doing the following: first it sets up a reactor, listening on "channelhandler", then parses the request, and finally calls the handler with both a request and a response object:
The last piece of the puzzle is the event loop. After setting up a fancy while(true) loop, we start the process by selecting an event (key). If the key is OK, we accept the connection and register it as ready-for-read. Once the key is readable, we send the current channel to all clients listening on "channelhandler":
and that's pretty much it (the implementation of the hand-rolled HTTPRequest and HTTPResponse classes were excluded for brevity, but essentially they just contain a simple request parsing and some header bookkeeping).
Now we are going to examine Reactor through a scala lens.
Let's start with our end user code again:
Thanks to SIP-14, as of scala 2.10 there is a Future implementation in the standard library. We take advantage of this in our client API by returning a Future[Response] in our run method. The main intention here is to signal to the end user that we are dealing with an async event.
The event loop in scala looks somewhat different:
Other than the signature changes, notice that we are using Reactor's promise API. This is because in scala we prefer dealing with expressions and functions which result in better composability and less state. That said, using an external Promise API in scala at this point feels very awkward. What's even more troublesome is the fact it's not possible to fully wrap a reactor.core.Promise into a scala.concurrent.Future in a straightforward manner. However, there are a few things we can do. First: we can define a few implicits for Reactor's SAM types and second, we can also create an ExecutorContext from Reactor's dispatchers:
having these helpers in place, we can now use scala.concurrent.Future-s with a reactor:
As you can see, we are passing our special ExecutionContext explicitly as context. This is because we have another execution context in scope. Also note, we take advantage of the implicits we defined earlier when declaring the consumer for failure.
(you can find the whole project on github here)
The are many things to like about reactor:
- the Java API has a modern feel to it: fluent interfaces, static helpers, intuitive names, SAM types, etc.
- comes with a few useful dispatchers out of the box
- small footprint
and a few shortcomings:
- concept overload (promises, streams, observable, pubsub, etc.)
- having many concepts/ideas borrowed from others makes it hard to see the specific use cases where Reactor might be a good fit. For example: why should I use reactor over a pure Distruptor or RxJava based solution?
- somewhat tricky to use it with other JVM languages with overlapping concepts
- limited documentation and examples
- limited functionality compared to competitors
Reactor is a project with great potential, but my feeling is that if you are a scala or a java developer with semi-advanced needs right now, then you are likely much better off with one of the more mature asynchronous solutions out there.
 remoting, clustering, loadbalancing, supervision, service discovery etc.
RxJava, Apache Camel, Netty, Finagle, Disruptor, scala Futures or Akka