Memcached-like Application
In this tutorial we will create a memcached-like client-server application 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, let's take a look at the initial ActiveJ RPC implementation of these modules, since our application will be built using them:
- MemcacheServerModule exports an RpcServer to handle
get
andput
requests. To launch multiple servers on a single JVM, we provide the MemcacheMultiServerModule with integrated WorkerPools. - MemcacheClientModule sets a Rendezvous Hashing Strategy for request arrangement among shards of servers.
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 the the lightning-fast Dependency Injection library ActiveJ Inject and the ActiveJ Launcher to manage the application lifecycle:
public class MemcacheLikeServer extends Launcher {
@Inject
WorkerPool.Instances<RpcServer> instances;
@Provides
WorkerPool workerPool(WorkerPools workerPools) {
return workerPools.createPool(3);
}
@Provides
DefiningClassLoader classLoader() {
return DefiningClassLoader.create();
}
@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 are extending Launcher, we need to override 2 methods: the getModule() method to provide ServiceGraphModule and the run() method 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 {
@Inject
RawMemcacheClientAdapter client;
@Inject
NioReactor reactor;
@Provides
NioReactor reactor() {
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");
}
@Provides
DefiningClassLoader classLoader() {
return DefiningClassLoader.create();
}
@Override
protected Module getModule() {
return ModuleBuilder.create()
.install(ServiceGraphModule.create())
.install(MemcacheClientModule.create())
.install(ConfigModule.builder()
.withEffectiveConfigLogger()
.build())
.build();
}
@Override
protected void run() throws ExecutionException, InterruptedException {
String message = "Hello, Memcached Server";
System.out.println();
CompletableFuture<Void> future = reactor.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 a Rendezvous Hashing Strategy to achieve distribution of requests between
shards of servers, in the client we need to provide the addresses of these shards -
9001
,9002
, and9003
ports. - In the Reactor we ask to
put
a message in the current i of the bytes[i] array, andget
it back from the corresponding cell. - Thus, the client will perform these operations asynchronously for the three shards, so we end up with a disordered output block.
- RawMemcacheClientAdapter
is a manually created class. It uses RawMemcacheClient (an implementation of MemcacheClient) and defines the logic of the
put
andget
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 that 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