In this guide we will create a remote key-value storage using RPC. App will have 2 basic operations: “put” and “get”
During writing distributed application the common concern is what protocol to use for communication. There are two main options:
While HTTP is more popular and well-specified, it has some overhead. When performance is significant aspect of application, you should use something faster than HTTP. And for this purpose Datakernel Framework has RPC module which is based on fast serialzers and custom optimized communication protocol, which enables to greatly improve application performance.
Firstly, create a folder for application and build an appropriate project structure:
Next, configure your pom.xml file. We will need the following dependencies: rpc, boot and some logger. So your pom.xml should look like following:
Since we have two basic operations to implement (put and get), let’s first write down classes that will be used for communication between client and server. Specifically, PutRequest, PutResponse, GetRequest and GetResponse. Instances of these classes will be serialized using fast DataKernel Serializer, but to enable serializer to work, we should provide some meta information about this classes using appropriate annotations. The basic rules are:
Use @Serialize annotation with order number on getter of property. Ordering provides better compatibility in case when classes are changed
Use @Deserialize annotation with property name (which should be same as in getter) in constructor
Use @SerializeNullable on properties that can have null values
Thereby, classes for communication should look like following:
Next, let’s write a simple implementation of key-value storage:
Now, let’s write down a guice module for RPC server using Datakernel Boot, that will handle “get” and “put” requests (Note: if you are not familiar with Datakernel Boot, please take a look at Hello World HTTP Server Tutorial)
As you can see, in order to properly create RpcServer we should indicate all the classes which will be sent between client and server, and specify appropriate RequestHandler for each request class.
Since Java 1.8 they could be expressed as lambdas, which are seen as third arguments in those lines:
Launcher for RPC server:
Now, let’s write RPC client. In order to create RPC client we should again indicate all the classes that will be used for communication and specify RpcStrategy. There is a whole bunch of strategies in RPC module (such as single-server, first-available, round-robin, sharding and so on) and the nice thing about them: all strategies can be combined. For example, if you want to dispatch requests between 2 shards, and each shard actually contains main and reserve servers, you can easily tell RPC client to dispatch request in a proper way using the following code:
But since we have only one server, we will just use single-server strategy:
Let’s also build RpcClientLauncher. In run() we will consider command line arguments and make appropriate requests to RpcServer.
As you can see, sendRequest method returns a CompletionStage, at which we could listen for its results asynchronously with lambdas.
Contratulation! We’ve finished writing code for this app. Let’s now complile it. In order to do it go to project root directory and enter the following command: