An asynchronous implementation of the Redis Serialization Protocol (RESP) in Java.
This is not a Redis client, although it might become one with time, but rather a library specifically for the protocol to allow Redis clients to be built using it for various JVM languages.
Java 8 or higher.
jresp
provides implementations for each type specified as part of the Redis Serialization Protocol (RESP), specifically: simple strings, errors, integers, bulk strings, and arrays. All these implement the RespType
interface.
It also provides a way of communicating with a Redis server called Connection
. These are created using the Client
.
When creating a Connection
an object/closure implementing Responses
needs to be provided. Messages are written using the write
method on Connection
; each response from the server will result in responseReceived
on the implementation of Responses
.
A higher-level Pool
can be used for connection pooling.
Example:
Client c = new Client("localhost", 6379);
Pool p = new Pool(c);
p.getShared().write(new Ary(Arrays.asList(new BulkStr("PING"))), resp -> System.out.println("Got: " + resp));
This should print: Got: PONG
The final line of that example looks a bit verbose. This is because communication between client and server takes place using RESP objects; each command to Redis is a RESP array containing one or more bulk strings for the command name and arguments.
JRESP implements implicit pipelining. Multiple commands are sent by calling write
multiple times, the commands are serialized and written to a buffer. The buffer will send data across the wire to Redis when the socket signals it is ready for more data, as much data as is possible, comprising multiple commands, will be sent in one request.
Each response will be an implementation of RespType
, the exact type will depend on the Redis command called, see the Redis command documentation for details.
Every RespType
can be converted to the corresponding Java type by calling unwrap
. E.g a BulkStr
becomes a String
. This is a recursive process for RESP arrays, the array becomes a List
and each element of the array has unwrap
called in turn.
jresp
is not a Redis client. It has no knowledge of the specific Redis commands or responses. Nor how to handle the special-case commands like MONITOR
, it is low-level enough that it doesn't have to worry about all these things, it is just a conduit for passing serialised RESP data from client to server and back.
There are pre-existing Redis clients for Java, both synchronous and asynchronous. jresp
was specifically intended to be the backend of multiple Redis clients for various JVM languages.
As of the time of writing, it is used for redis-async
, a async Redis client for Clojure. I have written about the development of jresp
and redis-async
here, and here.
So how does it perform compared to existing Java Redis clients?
I have adapted some basic tests which were originally developed for Stefan Wille's test for a Redis client in Crystal. In my case I'm only interested in comparing against other Java clients, so I only test against Jedis.
DISCLAIMER: this test only measures what it tests, Jedis has more features which are obviously not tested here.
The test simply sends one million SET
commands to Redis, timing until the last response is received. In all cases the test is repeated ten times within the same VM, and the time is taken of the last of the ten iterations.
As a baseline I use Jedis without pipelining. Doing any kind of bulk Redis operation without Redis is obviously a bad idea, but I wanted a time for it just to show how much faster the pipelined version was. The source for this test is here.
One million SET
s took: 44,091ms.
Next is the same thing pipelined. JRESP pipelines automatically, so no specific changes were needed to enable it; the Jedis test needed to be slightly modified to make pipelining explicit.
One million SET
s took Jedis: 1,369ms. The same took JRESP: 1,226ms.
JRESP is approximately (on this one measure alone) 10% faster than Jedis; which, given the asynchronous nature makes sense. Although until it is tested, it was unknown what, if any, overhead the need to manage the state would add; but it seems that the advantage of asynchronicity outweighs the overhead of state.
What is the advantage of asynchronicity? Jedis will be spending time first queueing, then writing the million commands to an OutputStream
, and only when that has finished will it start to read and parse the results. JRESP on the other-hand, will start writing data to the socket pretty much immediately, and start parsing the response when the first response becomes available; in other tests on similar volumes as much as 80% of the responses have been parsed by the time the final command has been written.
The final test was to send one million commands using Jedis, but this time as 1,000 batches of 1,000 commands. The theory here is to maximise the similarity as much as possible with the default JRESP behaviour. There is no JRESP equivalent for this test for that reason, it is not possible to control exactly how may individual batches will be used; the batching works on the cycle of the socket becoming available for writing.
On this final test, Jedis completed the million SET
s in 1,270ms.
This brings performance of the two into the "so close it makes no difference" range. But the key advantage of JRESP is that the code needs no modification to work like this, it automatically pipelines the commands in an efficient way.
The biggest take away is that performance of JRESP is quite good. In practice, if you need a Java Redis client then you wouldn't use JRESP because it's not a full client. But it does mean it's a solid foundation for Redis clients in other JVM languages.
(I may add full client functionality to JRESP eventually, or maybe use it as the core for specific but distinct project.)
- Ensure that closed connections (i.e. server goes down) are known, and that appropriate signals are delivered upstream.
- Document pub/sub facilities and connection pool more.
- Handling of stopped connections pools, etc.
- Redis clustering.
- Create a full Java client (optional).
- Tests regarding dropped connections.
Copyright 2015 Ben Ashford
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.