Skip to main content

Memcached-like Application

In this guide we will create a memcached-like client-server application that is based on the RPC communication protocol and ActiveJ technologies.

You can find full example sources on GitHub.

Memcached Client and Server Modules#

First of all, consider the initial ActiveJ RPC implementation of these modules since our application will be built with their help:

note

This implementation covers only the basic usage. You may add more features as your application requires.

Create Client and Server#

Let's write our own MemcacheLikeServer server. We will also use ActiveJ Launcher to manage application lifecycle and lightning-fast Dependency Injection library ActiveJ Inject:

public class MemcacheLikeServer extends Launcher {
@Inject
WorkerPool.Instances<RpcServer> instances;
@Provides
WorkerPool workerPool(WorkerPools workerPools) {
return workerPools.createPool(3);
}
@Provides
Config config() {
return Config.create()
.with("memcache.buffers", "4")
.with("memcache.bufferCapacity", "64mb");
}
@Override
protected Module getModule() {
return ModuleBuilder.create()
.install(ServiceGraphModule.create())
.install(MemcacheMultiServerModule.create())
.install(WorkerPoolModule.create())
.build();
}
@Override
protected void run() throws Exception {
awaitShutdown();
}
public static void main(String[] args) throws Exception {
MemcacheLikeServer server = new MemcacheLikeServer();
server.launch(args);
}
}

Since we extend Launcher, we need to override 2 methods: getModule to provide ServiceGraphModule and run to describe the main logic of the example.

  • The number of server shards equals the number of workerPools
  • As for the "memcached" functionality - we specify the number of buffers and their capacity in the Config
  • Config is used to set up everything MemcacheMultiServerModule needs to handle upcoming requests.

Our MemcacheLikeClient will create put and get requests:

public class MemcacheLikeClient extends Launcher {
@Provides
Eventloop eventloop() {
return Eventloop.create();
}
@Provides
RawMemcacheClientAdapter rawMemcacheClientAdapter(RawMemcacheClient client) {
return new RawMemcacheClientAdapter(client);
}
@Provides
Config config() {
return Config.create()
.with("protocol.compression", "false")
.with("client.addresses", "localhost:9000, localhost:9001, localhost:9002");
}
@Inject
RawMemcacheClientAdapter client;
@Inject
Eventloop eventloop;
@Override
protected Module getModule() {
return ModuleBuilder.create()
.install(ServiceGraphModule.create())
.install(MemcacheClientModule.create())
.install(ConfigModule.create()
.withEffectiveConfigLogger())
.build();
}
@Override
protected void run() throws ExecutionException, InterruptedException {
String message = "Hello, Memcached Server";
System.out.println();
CompletableFuture<Void> future = eventloop.submit(() ->
sequence(
() -> Promises.all(range(0, 25).mapToObj(i ->
client.put(i, message))),
() -> Promises.all(range(0, 25).mapToObj(i ->
client.get(i).whenResult(res -> System.out.println(i + " : " + res))))));
future.get();
System.out.println();
}
public static void main(String[] args) throws Exception {
Launcher client = new MemcacheLikeClient();
client.launch(args);
}
}
  • Since MemcacheClientModule uses Rendezvous Hashing Strategy to achieve agreement for requests between shards of servers, in client we need to provide the addresses of these shards - 9001, 9002, and 9003 ports.
  • In the Eventloop we ask to put a message in the current i of the bytes[i] array, and get it back from the corresponding cell.
  • So the client will perform these operations asynchronously for three shards, therefore we will receive a disordered output block as a result.
  • RawMemcacheClientAdapter is a manually created class. It uses RawMemcacheClient (a MemcacheClient implementation) and defines the logic of put and get methods for our MemcacheLikeClient.

Running the application#

  • First, open and run the MemcacheLikeServer to launch the server;
  • To launch the client, run main() method of MemcacheLikeClient;

You will see roughly the same output in the server's console (... omits similar messages):

Server on port #9000 accepted message!
Server on port #9000 accepted message!
...
Server on port #9000 accepted message!
Server on port #9002 accepted message!
Server on port #9002 accepted message!
...
Server on port #9002 accepted message!
Server on port #9001 accepted message!
...
Server on port #9001 accepted message!

In the client console you will see a similar output confirming the client received asynchronous responses from the server:

0 : Hello, Memcached Server
3 : Hello, Memcached Server
4 : Hello, Memcached Server
...
11 : Hello, Memcached Server
13 : Hello, Memcached Server
...
20 : Hello, Memcached Server
21 : Hello, Memcached Server
24 : Hello, Memcached Server
...
17 : Hello, Memcached Server
19 : Hello, Memcached Server