Skip to content

Jedis

This section is aimed at Java users, focusing on how to elegantly use Jedis to write applications that are both aesthetically pleasing and error-resistant.

What is Jedis?

Jedis is the most commonly used open-source Redis client for Java. It's lightweight, simple in implementation, and highly stable. Its method parameter names closely match those in the official documentation, minimizing the learning curve. Unlike some clients that rename methods, Jedis allows developers to refer directly to official Redis commands.

Using Jedis with Connection Pools

Java applications are typically multithreaded, meaning we rarely use Jedis directly. Instead, we utilize Jedis's connection pool—JedisPool. Since Jedis objects are not thread-safe, we must acquire a Jedis object from the pool, use it, and then return it.

Here's how it looks in code:

java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class JedisTest {
  public static void main(String[] args) {
    JedisPool pool = new JedisPool();
    Jedis jedis = pool.getResource(); // Acquire a Jedis object
    doSomething(jedis);
    jedis.close(); // Return the connection
  }

  private static void doSomething(Jedis jedis) {
    // code it here
  }
}

Handling Exceptions

The above code has a potential issue: if doSomething throws an exception, the Jedis object will not be returned to the pool. If this occurs multiple times, all connections in the pool may become occupied, leading to new requests being blocked and potentially causing the application to freeze.

To avoid this situation, we should use a try-with-resources statement:

java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class JedisTest {
  public static void main(String[] args) {
    JedisPool pool = new JedisPool();
    try (Jedis jedis = pool.getResource()) { // Automatically close when done
      doSomething(jedis);
    }
  }

  private static void doSomething(Jedis jedis) {
    // code it here
  }
}

With this setup, the Jedis object will always be returned to the pool (except in cases of infinite loops), preventing application freezes.

Enforcing Best Practices

In a large team, not all developers may remember to use try-with-resources. To enforce this in code, we can create an interface and a wrapper around JedisPool:

java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

interface CallWithJedis {
  void call(Jedis jedis);
}

class RedisPool {
  private JedisPool pool;

  public RedisPool() {
    this.pool = new JedisPool();
  }

  public void execute(CallWithJedis caller) {
    try (Jedis jedis = pool.getResource()) {
      caller.call(jedis);
    }
  }
}

public class JedisTest {
  public static void main(String[] args) {
    RedisPool redis = new RedisPool();
    redis.execute(jedis -> {
      // do something with jedis
    });
  }
}

In this setup, the RedisPool class hides the JedisPool, preventing direct calls to getResource and ensuring that Jedis objects are always returned.

Simplifying with Lambda Expressions

Writing a callback class each time can be cumbersome. With Java 8, we can use lambda expressions to simplify the code:

java
public class JedisTest {
  public static void main(String[] args) {
    Redis redis = new Redis();
    redis.execute(jedis -> {
      // do something with jedis
    });
  }
}

Handling Variable Modification in Lambdas

However, Java does not allow modification of variables from the enclosing scope in lambdas. For example, the following code will cause a compilation error:

java
public class JedisTest {
  public static void main(String[] args) {
    Redis redis = new Redis();
    long count = 0;
    redis.execute(jedis -> {
      count = jedis.zcard("codehole"); // This will cause an error
    });
    System.out.println(count);
  }
}

To resolve this, we can define a Holder class to wrap mutable variables:

java
class Holder<T> {
  private T value;

  public Holder() {}

  public Holder(T value) {
    this.value = value;
  }

  public void setValue(T value) {
    this.value = value;
  }

  public T getValue() {
    return value;
  }
}

public class JedisTest {
  public static void main(String[] args) {
    Redis redis = new Redis();
    Holder<Long> countHolder = new Holder<>();
    redis.execute(jedis -> {
      long count = jedis.zcard("codehole");
      countHolder.setValue(count);
    });
    System.out.println(countHolder.getValue());
  }
}

Implementing a Retry Mechanism

Jedis does not provide a built-in retry mechanism. If there are network issues, it can lead to widespread errors. To add a retry mechanism, we can modify the RedisPool class:

java
class Redis {
  private JedisPool pool;

  public Redis() {
    this.pool = new JedisPool();
  }

  public void execute(CallWithJedis caller) {
    Jedis jedis = pool.getResource();
    try {
      caller.call(jedis);
    } catch (JedisConnectionException e) {
      caller.call(jedis);  // Retry once
    } finally {
      jedis.close();
    }
  }
}

This example retries the operation once, but you can extend this to retry multiple times, keeping in mind not to retry indefinitely.

Jedis has loaded