In previous article we looked at Lettuce as a Java client.
In this section we will deep dive into using Jedis as a Java client.

 

Jedis is a blazingly small and sane Redis java client.

1.0 Maven/Gradle Dependencies

1.1 Maven

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
 <groupId>redis.clients</groupId>
 <artifactId>jedis</artifactId>
 <version>2.9.0</version>
</dependency>

For using Jedis connection pool, add following dependency also

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency>
 <groupId>org.apache.commons</groupId>
 <artifactId>commons-pool2</artifactId>
 <version>2.5.0</version>
</dependency>

1.2 Gradle

// https://mvnrepository.com/artifact/redis.clients/jedis
compile group: 'redis.clients', name: 'jedis', version: '2.9.0'

For using Jedis connection pool, add following dependency also

// https://mvnrepository.com/artifact/org.apache.commons/commons-pool2
compile group: 'org.apache.commons', name: 'commons-pool2', version: '2.5.0'

 

2.0 Objects / Serializers to be used in code examples

In the below code examples, we will use a Person and Address objects.
The structure of these classes are given below

public class Person implements Serializable {
    private String name;
    private int age;
    private Address address;


    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address=" + address +
                '}';
    }
}

class Address implements Serializable {
    private String city;
    private String stateCode;
    private String country;

    public Address(String city, String stateCode, String country) {
        this.city = city;
        this.stateCode = stateCode;
        this.country = country;
    }

    @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                ", stateCode='" + stateCode + '\'' +
                ", country='" + country + '\'' +
                '}';
    }
}

 

Jedis can only work with String and byte arrays.

Hence if we want to save Objects into Redis
– we need to serialize Object to byte arrays while setting values.
– we need to deserialize the byte array back to Objects when we retrieve values from Redis.

For serialization/deserialization, we will make use of org.apache.commons.lang.SerializationUtils for serialization/deserialization.

This is only for demonstration purpose and we are free to choose any other mechanism for serialization and deserialization.
In fact in a later section we will also show how to use Kryo serializer.

//Serializes an Object to byte array
private static byte[] serialize(Serializable obj) {
    return SerializationUtils.serialize(obj);
}

//Deserializes a byte array back to Object
private static Object deserialize(byte[] bytes) {
    return SerializationUtils.deserialize(bytes);
}

3.0 Standalone Jedis Client

Jedis isn’t thread-safe and the same Jedis instance shouldn’t be used from different threads. This section is only for demonstration purpose.

Ideally we should use a Jedis connection pool for our applications.

3.1 Creating Jedis Connection

Just create a new instance of redis.clients.jedis.Jedis

 //Specify the host, port, connection timeout (in milliseconds), ssl
 Jedis jedisClient = new Jedis("localhost", 6379, 12000, false);

 

3.2 String operations : GET/SET

String strKey = "user:email:sizeLimit";

//DEL command
jedisClient.del(strKey);

//SET command
jedisClient.set(strKey, "1024");

//GET operation
String strValue = jedisClient.get(strKey);
System.out.println(strKey + " = " + strValue); //will print 1024

//INCRBY command
jedisClient.incrBy(strKey, 1024);
strValue = jedisClient.get(strKey);
System.out.println(strKey + " = " + strValue); //will print 2048

//EXPIRE command
jedisClient.expire(strKey, 120);

//TTL command
System.out.println("TTL for " + strKey + " = " + jedisClient.ttl(strKey));

3.3 Saving bytes using GET/SET

When working with byte array, both the key and the value need to be send as byte array.
We cannot use String keys for byte operations.

//Working with byte data instead of Strings with SET/GET Operations
String strKey2 = "employee:1000";

//Get the byte array for the Key
byte[] strKey2bytes = serialize(strKey2);

// DEL command
jedisClient.del(strKey2);

//Create a person object
Person person1 = new Person("John Doe", 35, new Address("San Francisco", "CA", "US"));

// SET command - to add the person object to Redis cache
jedisClient.set(strKey2bytes, serialize(person1));

// Fetch the object back from cache
Person p4 = (Person)deserialize(jedisClient.get(strKey2bytes));
System.out.println("Getting bytes data back from GET operation = " + p4);

3.4 Working with lists

//List Operations
String lKey = "user:skills";

//DEL command
jedisClient.del(lKey);

//LPUSH command
jedisClient.lpush(lKey, "Redis", "Scala", "Java");

//RPUSH command
jedisClient.rpush(lKey, "Mongo", "Elasticsearch", "MySQL", "Jenkins");

//LRANGE command
List<String> skills = jedisClient.lrange(lKey, 0, -1);
System.out.println(lKey + " = " + skills);

//RPOP command
jedisClient.rpop(lKey);

//LRANGE command again.
skills = jedisClient.lrange(lKey, 0, -1);
System.out.println(lKey + " = " + skills);

3.5 Working with Hashes

String hKey = "employees";

//Get the byte array for the hash key
byte[] hKeyBytes = serialize(hKey);

//DEL command
jedisClient.del(hKey);

Person person1 = new Person("John Doe", 35, new Address("San Francisco", "CA", "US"));
Person person2 = new Person("Sonal", 21, new Address("Portland", "OR", "US"));
Person person3 = new Person("Jamie", 18, new Address("Texas", "OH", "US"));

//We can serialize object to bytes and write it
//We have to pass all arguments as byte array - key, field and value
jedisClient.hset(hKeyBytes, serialize("1000"), serialize(person1));
jedisClient.hset(hKeyBytes, serialize("1001"), serialize(person2));

//Or we can write objects as String
//We have to pass all arguments as String - key, field and value
jedisClient.hset(hKey, "1002", person3.toString());

//Get person object back from hash
//Key and field name should be passed as byte array
Person p1 = (Person)deserialize(jedisClient.hget(hKeyBytes, serialize("1000")));
System.out.println(p1);

//Get String value back from hash
//Key and field name should be passed as String
String p3 = jedisClient.hget(hKey, "1002");
System.out.println(p3);

 

3.6 Shutting down the connection

jedisClient.close();

 

4.0 Using Jedis Connection Pool

Jedis isn’t thread-safe and the same Jedis instance shouldn’t be used from different threads. To overcome the overhead of multiple Jedis instances and connection maintenance, we can useJedisPool which is thread-safe and can be stored in a static variable and shared among threads.

JedisPool can be configured using a JedisPoolConfig, which extends from Apache Common Pool’s GenericObjectPoolConfig.

 

4.1 Creating a connection pool and configuring it

//Create a new JedisPoolConfig
JedisPoolConfig config = new JedisPoolConfig();
//We can set max pool size, max idle size, and other properties
config.setMaxTotal(50);
config.setMaxIdle(15);


//Create a new Jedis Pool
//Set the host, port, connection timeout, password and ssl mode arguments
JedisPool jedisPool = new JedisPool(
        config, "localhost", 6379, Protocol.DEFAULT_TIMEOUT, null, false);

Now the pool is created with max connection size of 50.

 

4.2 Printing pool statistics

We will use following method to print Jedis Pool Statistics

private static void printConnectionPoolDetails(JedisPool jedisPool) {
    System.out.println("Active = " + jedisPool.getNumActive() +
            ", Idle = " +  jedisPool.getNumIdle() + ", Waiting = " +  jedisPool.getNumWaiters());
}

 

4.3 Getting / Releasing connection to pool

Let’s see how we can get connection from pool and release it back.

// Will print Active = 0, Idle = 0, Waiting = 0
printConnectionPoolDetails(jedisPool);

//Get a Jedis connection from the pool
Jedis jedisConnection = jedisPool.getResource();

// Will print Active = 1, Idle = 0, Waiting = 0
printConnectionPoolDetails(jedisPool);


//Perform some operations on the connection
...
...

//Use Jedis.close() to close this connection and return to the Pool
//Do not use JedisPool.returnResource() directly
jedisConnection.close();

Now if we print the Pool statistics, we can see that the connection has been freed and returned to Idle state

// Will print Active = 0, Idle = 1, Waiting = 0
printConnectionPoolDetails(jedisPool);

 

4.4 Fetching connections in bulk and monitoring pool statistics

Now let’s try to create connections in bulk and release them also in bulk.

But before that we will define few ‘helper’ methods that we will use in our code.

//Sleep the current thread for specified seconds
private static void sleep(int sleepInSeconds) {
    try {
        Thread.sleep(sleepInSeconds*1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

 

//Open specified number of connections in the CURRENT THREAD
//And store the references in the Jedis array
private static void openConnectionsInCurrentThread(JedisPool jedisPool, Jedis[] connections, int size) {
    for (int i = 0; i < size; i++) {
        connections[i] = jedisPool.getResource();
    }
}

 

//Close specified number of connections and return them to the pool
private static void closeConnectionsInCurrentThread(Jedis[] connections, int size) {
    for (int i = 0; i < size; i++) {
        connections[i].close();
    }
}

 

//Open specified number of connections in the NEW THREAD
//And store the references in the Jedis array
private static ExecutorService openConnectionsInNewThread(JedisPool jedisPool, int size) {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            try {
                jedisPool.getResource();
            } catch (Exception e) {
                System.out.print("Exception while getting connection = " + e.toString());
            }
        }
    };

    ExecutorService executor = Executors.newFixedThreadPool(2*size);

    for (int i = 0; i <size ; i++) {
        executor.execute(r);
    }
    return executor;
}

 

Now we will try to open connections in bulk and try to exceed the max pool size and see what happens.

//Create an array to hold all the Jedis connections
Jedis[] connections = new Jedis[100];

//Open 25 new Connections
openConnectionsInCurrentThread(jedisPool, connections, 25);

// Will print Active = 25, Idle = 0, Waiting = 0
printConnectionPoolDetails(jedisPool);

//Now close all the 25 connections
closeConnectionsInCurrentThread(connections, 25);

Now if we print pool statistics it will show 15 connections as Idle, although a total of 25 connections were released. This is because we had set the maxIdle attribute of JedisPoolConfig to 15 .

// Will print Active = 0, Idle = 15, Waiting = 0
printConnectionPoolDetails(jedisPool);

Open new connections again

//Open 40 new Connections
openConnectionsInCurrentThread(jedisPool, connections, 40);

// Will print Active = 40, Idle = 0, Waiting = 0
printConnectionPoolDetails(jedisPool);

 

Now let’s try to exceed the pool max size.

Connection pool has only 10 connections left. We will try to open 15 connections, but will create them in new thread, so that current thread does not blocks waiting for connections.

//Create 15 new connections in new thread
ExecutorService executor = openConnectionsInNewThread(jedisPool, 15);
executor.shutdown();

We will let the current thread sleep for few seconds, and then print the pool statistics.
We can now see that there are 50 connections in Active state and 5 in Idle.

//Let current thread sleep for 5 seconds
sleep(5);

// Will print Active = 50, Idle = 0, Waiting = 5
printConnectionPoolDetails(jedisPool);

Now we will close 2 Active connections

//Close 2 connections
closeConnectionsInCurrentThread(connections, 2);

// Will print Active = 50, Idle = 0, Waiting = 3
printConnectionPoolDetails(jedisPool);

 

4.5 Closing a Connection Pool

jedisPool.close();

 

 

 

5.0 Using Jedis with Kyro serializer Java Objects

Coming soon….

 

 

 

 

 

 

 

 

 

References

https://github.com/xetorthio/jedis

https://redislabs.com/lp/redis-java/

https://dzone.com/articles/3-ways-to-use-redis-hash-in-java