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
Like!! Thank you for publishing this awesome article.
LikeLike