Saturday, July 5, 2014

Implementing a Distributed Counter Service Using Hazelcast, Jersey 2 and Guice

Modern web services are often required to scale horizontally in order to handle growing load.
Here I present an example distributed Counter service, which uses modern technology stack consisting of Hazelcast, Jersey 2 and Guice.

The sample is based on an excellent example posted by Piersy and can be downloaded from here:
http://github.com/rafalrusin/jersey2-guice-example-with-test

The counter is just a shared value across all participating nodes within a cluster. All nodes can query for data and increase it's value by specified delta. Note that Hazelcast handles synchronization within distributed environment. In order to update the state atomically, we use Hazelcast EntryProcessor.

@Singleton
public class CounterService {

    public int increase(final int delta) {
        return (Integer) map.executeOnKey("counterKey", new CounterEntryProcessor(delta));
    }
}

public class CounterEntryProcessor implements EntryProcessor<String, Integer>, EntryBackupProcessor<String, Integer> {
    private final int delta;

    public CounterEntryProcessor(int delta) {
        this.delta = delta;
    }

    @Override
    public Integer process(Map.Entry<String, Integer> entry) {
        int newValue = entry.getValue() + delta;
        entry.setValue(newValue);
        return newValue;
    }
}

The service is just a Guice Singleton. It has an operation called 'increase', which takes a delta and creates EntryProcessor job for Hazelcast to submit to a node, which owns the value at the time and will update it atomically.
Hazelcast is a library, which implements Java Collections in distributed fashion. It handles replication, cluster membership and distributed locking.

Web service is a simple JAXRS REST Resource. Following code invokes Guice service layer from Resouce implementation:

@RequestScoped
@Path("counter")
public class CounterResource {

    private CounterService service;

    @Inject
    public CounterResource(CounterService service) {
        this.service = service;
    }

    @POST
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.TEXT_PLAIN)
    public String increase(String delta) {
        return "" + service.increase(Integer.parseInt(delta));
    }
}

We also have a test case, which invokes the whole stack and does a request to the service. The whole stack is very lightweight. In runs on Jersey with Embedded Tomcat. The test case completes within a few seconds.

In order to run the example, you need to build distribution first (run dist.sh), then start nodes in separate shells and invoke curl POST requests to manipulate counter state. Here's a sample interaction:

./run.sh 8090
./run.sh 8092
...
Jul 05, 2014 10:20:06 AM com.hazelcast.cluster.ClusterService
INFO: [10.0.0.12]:5702 [dev] [3.2.3]

Members [2] {
        Member [10.0.0.12]:5701
        Member [10.0.0.12]:5702 this
}

Jul 05, 2014 10:20:08 AM com.hazelcast.core.LifecycleService
INFO: [10.0.0.12]:5702 [dev] [3.2.3] Address[10.0.0.12]:5702 is STARTED
...

$ curl -d '1' -H 'Content-Type: text/plain;' http://localhost:8092/webapp/api/counter
22
$ curl -d '1' -H 'Content-Type: text/plain;' http://localhost:8090/webapp/api/counter
23
$ curl -d '5' -H 'Content-Type: text/plain;' http://localhost:8090/webapp/api/counter
28

I think that Hazelcast is a very good step forward into distributed computing. For an every day programmer, it's a set of primitives to manipulate in order to implement a distributed system. It is very easy to integrate it into existing project, which could be either JavaEE or standalone app.