Comparison and implementation of springboot integrating connection pool jedis and lettuce in Redis

Comparison and implementation of springboot integrating connection pool jedis and lettuce in Redis

  • Why use Redis connection pool
  • The difference between jedis and lettuce
  • Implementation of both in springboot
    • Lettuce
    • Jedis
  • suggestion

Why use Redis connection pool

The Redis connection pool is a component used to manage and maintain connections to the Redis server. It plays a key role in applications that communicate with Redis and has the following important roles:

  1. Improving performance: The Redis connection pool can maintain a set of reusable connections, reducing the overhead of creating and destroying connections each time it needs to communicate with the Redis server. This helps improve application performance, since the creation and destruction of connections are typically relatively expensive operations.
  2. Reduce resource consumption: The connection pool can control the number of connections to ensure that too many connections are not created, thereby reducing the consumption of memory and CPU resources. This is important to protect Redis servers and application servers from unnecessary load.
  3. Connection reuse: Connection pooling can reuse existing connections instead of creating new connections for each request. This reduces the number of connection establishment and destruction times and improves efficiency.
  4. Connection management: The connection pool can manage the status of the connection, including checking the availability of the connection, maintaining the health status of the connection, and automatically reconnecting failed connections. This helps ensure a stable connection between the application and Redis.
  5. Connection timeout control: The connection pool can configure the timeout period of the connection to ensure that no active connections will be closed within a period of time, thereby releasing resources and preventing connection leaks.
  6. Concurrency control: The connection pool can limit the number of connections used at the same time to avoid the impact of too many concurrent requests on the Redis server. This helps handle concurrent loads smoothly.
  7. Performance Optimization: The connection pool can optimize the reuse of connections and reduce the idle time of connections to ensure that the connection remains in the best state and responds quickly to requests.

In short, the main function of the Redis connection pool is to improve performance, reduce resource consumption, manage connection status and reuse connections, thereby ensuring efficient and stable communication with the Redis server. In high-concurrency applications, appropriate connection pool configuration is crucial to maintaining system stability and performance.

The difference between jedis and lettuce

When considering choosing an appropriate Redis connection pool, a more detailed comparison can cover various aspects, including performance, configuration, maintainability, and applicable scenarios. Here is a more detailed comparison of Jedis and Lettuce, two common Java Redis client connection pools:

  1. Performance:

    • Jedis:

      • Jedis performs well in low concurrency situations because it uses blocking I/O.
      • In high concurrency scenarios, the performance of Jedis may be limited because each connection is blocked and needs to wait for the IO operation to complete.
    • Lettuce:

      • Lettuce uses non-blocking I/O, so it performs better in high-concurrency environments and can make full use of system resources.
      • It supports asynchronous operations and reactive programming, making it excellent in asynchronous programming.
  2. Connection pool configuration:

    • Jedis:

      • The connection pool configuration of Jedis is relatively simple, and you need to manually set parameters such as the maximum number of connections, the maximum number of idle connections, and connection timeout.
      • Management of connection pools needs to be implemented manually.
    • Lettuce:

      • Lettuce provides richer connection pool configuration options, including connection pool behavior, topology refresh, etc.
      • It has a built-in high-performance connection pool and does not require manual management of the connection pool.
  3. Maintainability:

    • Jedis:

      • Jedis is relatively simple and easy to use, but requires manual management of connection pools and error handling.
      • It has less community support and can be relatively difficult to maintain.
    • Lettuce:

      • Lettuce has more features and maintainability, and has better documentation and community support.
      • It has some advanced features built-in, such as topology refreshing and reactive programming.
  4. Applicable scenarios:

    • Jedis:

      • Suitable for simple applications or low concurrency environments.
      • For traditional synchronous programming needs, it does the job.
    • Lettuce:

      • Suitable for high-concurrency, high-throughput applications.
      • Especially useful for asynchronous and reactive programming needs.
  5. Ecosystem integration:

    • Jedis:

      • Jedis has better integrated support in some older Java frameworks.
      • It may be more suitable in some projects that need to use older versions of the framework.
    • Lettuce:

      • Lettuce has better integration support in modern Java frameworks, especially the Spring framework.
      • It is more popular in projects using modern Java technologies such as Spring Boot.

To sum up, if your application needs to handle high concurrency or needs asynchronous programming support, Lettuce may be a better choice. And if your application is relatively simple, or runs under a traditional Java framework, Jedis can also be considered. No matter which one you choose, the connection pool will need to be configured and tested based on your specific needs to ensure optimal performance and stability.

Implementation of both in springboot

Lettuce

To integrate Redis connection pooling in a Spring Boot project, you can use Spring Data Redis to simplify the integration process. The following are the general steps for integrating Redis connection pools:

  1. Add dependencies: First, add the Spring Data Redis dependency in the pom.xml file of your Spring Boot project:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. Configure Redis connection properties: Configure Redis connection properties in application.properties or application.yml. Here is an example configuration:
# redis configuration, if it is a single node, there is no need to read comments.
#spring.redis.cluster.nodes=localhost:6399,localhost:6396
spring.redis.host=localhost
spring.redis.port=6396
# If there is no password, you can leave it blank.
spring.redis.password=
#spring.redis.cluster.max-redirects=3
spring.redis.timeout=10000
# Connect to Redis Sentinel
#spring.redis.sentinel.master=mymaster
#spring.redis.sentinel.nodes=192.168.1.75:26379,192.168.1.75:26378,192.168.1.75:26377
  1. Create a Redis configuration class: Create a Java configuration class to configure the Redis connection pool and RedisTemplate. For example:
package com.todoitbo.baseSpringbootDasmart.config;
  
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
  
/**
 * @author xiaobo
 */
@Configuration
@Slf4j
public class RedisConfiguration {<!-- -->
  
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {<!-- -->
  
        log.info("Start creating redis template object");
  
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
  
        //Set the redis connection factory object
        redisTemplate.setConnectionFactory(redisConnectionFactory);
  
        //Set the serializer of redis key to solve the problem of garbled characters
        redisTemplate.setKeySerializer(new StringRedisSerializer());
  
        //Set the serializer for redis values to solve the problem of garbled characters
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
  
        return redisTemplate;
    }
}

This configuration class uses Lettuce as the Redis connection pool. You can customize the properties of the connection pool as needed.

  1. Use RedisTemplate: Inject RedisTemplate into your Service or Controller, and then use it to perform Redis operations. For example:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class MyService {<!-- -->

    private final RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public MyService(RedisTemplate<String, Object> redisTemplate) {<!-- -->
        this.redisTemplate = redisTemplate;
    }

    public void addToRedis(String key, Object value) {<!-- -->
        redisTemplate.opsForValue().set(key, value);
    }

    public Object getFromRedis(String key) {<!-- -->
        return redisTemplate.opsForValue().get(key);
    }
}

In this way, you can successfully integrate the Redis connection pool in your Spring Boot project and use RedisTemplate to perform various Redis operations. Make sure to adjust the configuration and operation appropriately to your actual needs.

Jedis

If you want to use Jedis, you can configure it as follows:

  1. Add Jedis dependency:

Add Jedis dependencies in pom.xml:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
  1. Create a Jedis connection pool:
package com.todoitbo.tallybookdasmart.config;
  
  
import cn.hutool.core.text.CharSequenceUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
  
/**
 * @author xiaobo
 * @date 2022/7/25
 */
@Slf4j
@Configuration
@EnableAutoConfiguration
public class JedisConfig {<!-- -->
  
    @Value("${spring.redis.host}")
    private String host;
  
    @Value("${spring.redis.port}")
    private int port;
  
    @Value("${spring.redis.password}")
    private String password;
  
    @Value("${spring.redis.timeout}")
    private int timeout;
  
    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;
  
    @Value("${spring.redis.jedis.pool.max-wait}")
    private int maxWait;
  
    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;
  
    @Value("${spring.redis.jedis.pool.min-idle}")
    private int minIdle;
  
    @Value("${spring.redis.database}")
    private int db;
  
    @Bean
    public JedisPool redisPoolFactory() {<!-- -->
        try {<!-- -->
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            jedisPoolConfig.setMaxIdle(maxIdle);
            jedisPoolConfig.setMaxWaitMillis(maxWait);
            jedisPoolConfig.setMaxTotal(maxActive);
            jedisPoolConfig.setMinIdle(minIdle);
            String pwd = CharSequenceUtil.isBlank(password) ? null : password;
            JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, pwd, db);
            log.info("Initialization of Redis connection pool JedisPool successful! Address: " + host + ":" + port);
            return jedisPool;
        } catch (Exception e) {<!-- -->
            log.error("Exception in initializing Redis connection pool JedisPool:" + e.getMessage());
        }
        return null;
    }
  
    /**
     * description: Create jedisConnectionFactory factory
     *
     * @author bo
     * @date 2023/4/9 14:49
     */
    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {<!-- -->
        RedisStandaloneConfiguration redisStandaloneConfiguration =
                new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setDatabase(db);
        redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
        redisStandaloneConfiguration.setPort(port);
        return new JedisConnectionFactory(redisStandaloneConfiguration);
    }
  
    /**
     * description: Create RedisMessageListenerContainer
     * * @author bo
     * @version 1.0
     * @date 2023/4/9 14:50
     */
    @Bean
    public RedisMessageListenerContainer container(JedisConnectionFactory jedisConnectionFactory) {<!-- -->
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(jedisConnectionFactory);
        return container;
    }
}

This configuration class uses Jedis as the Redis connection pool. You can customize the properties of the connection pool as needed. In JedisConnectionFactory, you can set the parameters of the connection pool, such as the maximum number of connections and the maximum number of idle connections.

  1. Tool class implementation
package com.todoitbo.tallybookdasmart.utils;
  
  
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.todoitbo.tallybookdasmart.exception.BusinessException;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPubSub;
  
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
  
  
/**
 * @author xiaobo
 */
@Slf4j
@Component
public class JedisUtil {<!-- -->
  
    /**
     * Static injection of JedisPool connection pool
     * JedisUtil can directly call the static method
     */
    private static JedisPool jedisPool;
  
    private static final String RETURN_OK = "OK";
  
    @Autowired
    public void setJedisPool(JedisPool jedisPool) {<!-- -->
        JedisUtil.jedisPool = jedisPool;
    }
  
  
    /**
     * description: getJedis
     * @return redis.clients.jedis.Jedis
     * @author bo
     * @date 2022/7/22 9:06 AM
     */
     public static synchronized Jedis getJedis() {<!-- -->
        try {<!-- -->
            if (jedisPool != null) {<!-- -->
                return jedisPool.getResource();
            } else {<!-- -->
                return null;
            }
        } catch (Exception e) {<!-- -->
            throw new BusinessException("Exception in getting Jedis resources:" + e.getMessage());
        }
    }
  
    /**
     * description: Release resources
     *
     * @author bo
     * @date 2022/7/22 9:06 AM
     */
     public static void closePool() {<!-- -->
        try {<!-- -->
            jedisPool.close();
        } catch (Exception e) {<!-- -->
            throw new BusinessException("Exception in releasing Jedis resources:" + e.getMessage());
        }
    }
  
  
    /**
     * description: Get the object
     *
     * @param key k
     * @return java.lang.Object
     * @author bo
     * @date 2022/7/22 9:07 AM
     */
     public static Object getObject(String key) {<!-- -->
        try (Jedis jedis = getJedis()) {<!-- -->
            assertjedis != null;
            byte[] bytes = jedis.get(key.getBytes());
            if (!Assert.isEmpty(bytes)) {<!-- -->
                return JSON.parse(bytes);
            }
        } catch (Exception e) {<!-- -->
            throw new BusinessException("Exception in getting Redis key value getObject method: key=" + key + " cause=" + e.getMessage());
        }
        return null;
    }
  
    /**
     * description: setObject
     * @param key,Object k v
     * @return java.lang.String
     * @author bo
     * @date 2022/7/22 9:09 AM
     */
     public static String setObject(String key, Object value) {<!-- -->
        try (Jedis jedis = getJedis()) {<!-- -->
            assertjedis != null;
            return jedis.set(key.getBytes(), JSONObject.toJSONString(value).getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {<!-- -->
            throw new BusinessException("Exception in setting Redis key value setObject method: key=" + key + " value=" + value + " cause=" + e.getMessage());
        }
    }
  
  
    /**
     * description: Expiration time
     *
     * @param key,Object,long k,v,mm
     * @return java.lang.String
     * @author bo
     * @date 2022/7/22 9:11 AM
     */
     public static String setObject(String key, Object value, long expiretime) {<!-- -->
        String result;
        try (Jedis jedis = getJedis()) {<!-- -->
            assertjedis != null;
            result = jedis.set(key.getBytes(), JSON.toJSONString(value).getBytes(StandardCharsets.UTF_8));
            if (RETURN_OK.equals(result)) {<!-- -->
                jedis.pexpire(key.getBytes(), expiretime);
            }
            return result;
        } catch (Exception e) {<!-- -->
            throw new BusinessException("Exception in setting Redis key value setObject method: key=" + key + " value=" + value + " cause=" + e.getMessage());
        }
    }
  
  
    /**
     * description: getJson
     * @param key k
     * @return java.lang.String
     * @author bo
     * @date 2022/7/22 9:12 AM
     */
     public static String getJson(String key) {<!-- -->
        try (Jedis jedis = getJedis()) {<!-- -->
            assertjedis != null;
            return jedis.get(key);
        } catch (Exception e) {<!-- -->
            throw new BusinessException("Exception in getting Redis key value getJson method: key=" + key + " cause=" + e.getMessage());
        }
    }
  
    /**
     * description: setJson * * @param key,Object k,v
     * @return java.lang.String
     * @author bo
     * @date 2022/7/22 9:12 AM
     */
     public static String setJson(String key, String value) {<!-- -->
        try (Jedis jedis = getJedis()) {<!-- -->
            assertjedis != null;
            return jedis.set(key, value);
        } catch (Exception e) {<!-- -->
            throw new BusinessException("Set Redis key value setJson method exception: key=" + key + " value=" + value + " cause=" + e.getMessage());
        }
    }
  
    /**
     * description: Delete the specified key
     * @param key k
     * @throws Exception e
     * @author bo
     * @date 2022/7/22 1:32 PM
     */
     public static void deleteKey(@NonNull String key) throws Exception {<!-- -->
        try (Jedis jedis = getJedis()) {<!-- -->
            assertjedis != null;
            jedis.del(key);
        } catch (Exception e) {<!-- -->
            log.error("redis deletion [key={}] exception occurred: {}", key, e.getMessage());
            throw new BusinessException("redis delete" + key + "Exception occurred");
        }
    }
  
    /**
     * description: Delete specified keys in batches
     * @param key k
     * @throws Exception e
     * @author bo
     * @date 2022/7/22 1:33 PM
     */
     public static void deleteKeys(String... key) throws Exception {<!-- -->
        try (Jedis jedis = getJedis()) {<!-- -->
            assertjedis != null;
            jedis.del(key);
        } catch (Exception e) {<!-- -->
            log.error("redis deletion [keys= [{}]] exception occurred: {}", key, e.getMessage());
            throw new BusinessException("redis delete" + Arrays.toString(key) + "An exception occurred: " + e.getMessage());
        }
    }
  
    /**
     * description: push message
     *
     * @param channel,message message channel, message body
     * @author bo
     * @date 2022/7/22 1:34 PM
     */ public static void publish(String channel, String message) {<!-- -->
        try (Jedis jedis = new Jedis()) {<!-- -->
            jedis.publish(channel, message);
        } catch (Exception e) {<!-- -->
            log.error("An exception occurred when publishing messages in redis: {}", e.getMessage());
            throw new BusinessException("An exception occurred when redis published the message:" + e.getMessage());
        }
  
    }
  
    /**
     * Monitor message channel
     *
     * @param jedisPubSub object
     * @param channels message channel
     */
    public static void subscribe(JedisPubSub jedisPubSub, String... channels) {<!-- -->
        try (Jedis jedis = getJedis()) {<!-- -->
            assertjedis != null;
            jedis.subscribe(jedisPubSub, channels);
        } catch (Exception e) {<!-- -->
            log.error("An exception occurred in redis listening message: {}", e.getMessage());
            throw new BusinessException("An exception occurred in redis listening message:" + e.getMessage());
        }
    }
  
}

Here is a brief explanation of try-with-resources

try-with-resources is an important language feature introduced in Java 7. It is used to automatically manage resources (such as files, network connections, database connections, etc.) to ensure that the resources are correctly obtained after the code block is executed. Close and release. This feature helps reduce the risk of resource leaks and improves code readability and maintainability.

Here are the key points about try-with-resources:

  1. Syntax: try-with-resources Use a try block to wrap a piece of code with resources at the beginning of the try block Initialized, and automatically closed and released at the end of the try block. Resource initialization is completed through implementation classes of the AutoCloseable interface. These classes need to execute the close() method when the resource is closed. This usually includes some classes in the Java standard library, such as InputStream, OutputStream, Reader, Writer, Socket, Jedis, etc.

  2. Resources are automatically closed: At the end of the try-with-resources block, there is no need to manually call the close() method of the resources, they will be automatically closed. transfer. This ensures proper closing of the resource regardless of whether the code block executes normally or throws an exception.

  3. Multiple resource management: Multiple resources can be managed in the same try-with-resources block. These resources are initialized and closed in the reverse order of their declaration in the try block.

  4. Exception handling: If an exception is thrown in a try block, try-with-resources will first close all initialized resources and then throw Exception occurred. This ensures that resources are closed properly and no resource leaks occur.

  5. Scope of application: try-with-resources is applicable to classes that implement the AutoCloseable interface. If you customize a resource class, you can make it implement the AutoCloseable interface and override the close() method so that it can be used in try-with-resources used in code>.

Here is a simple example that demonstrates how to use a file to read resources in try-with-resources:

try (FileReader fileReader = new FileReader("example.txt")) {<!-- -->
    int data;
    while ((data = fileReader.read()) != -1) {<!-- -->
        // Process file content
    }
} catch (IOException e) {<!-- -->
    // Handle exceptions
}

In this example, FileReader is a resource class that implements the AutoCloseable interface. When the try-with-resources block ends, the fileReader will automatically close without manually calling the close() method.

In short, try-with-resources is an important Java language feature that helps simplify resource management, reduce the risk of resource leaks, and improve code readability and maintainability. It is useful when dealing with resources that need to be closed manually, including files, database connections, network connections, and other resources that need to be closed explicitly.

Suggestions

In a word, it is recommended to use Lettuce

Using Jedis as a Redis client may cause some problems in certain situations, including:

  1. Performance issues: Jedis may not perform as well as Lettuce under high concurrency conditions. Because Jedis uses blocking I/O, each connection is blocking, which can cause a performance bottleneck, especially when a large number of concurrent requests arrive at the same time.

  2. Connection management: Jedis needs to manually manage the connection pool and the status of the connection. This means you need to configure the connection pool parameters yourself, handle the creation and destruction of connections, and manage the health of the connections. This can cause issues with connection leaks or connections being improperly closed.

  3. Thread safety: Instances of Jedis are not thread-safe, which means additional synchronization is required in a multi-threaded environment to ensure correct concurrent access. This increases development complexity.

  4. Exception handling: Jedis’ exception handling requires careful handling by developers. For example, when a connection expires or a network problem occurs, exceptions need to be caught and handled to ensure the stability of the application.

  5. Community support: Compared to Lettuce, Jedis has relatively little community support. This means it may not be easy for you to find solutions to related issues or get updated maintenance and support.

  6. Not suitable for reactive programming: If your application needs to use a reactive programming model, Jedis may not be the best choice because it does not support reactive operations. Lettuce is more suitable in this regard.

  7. Version Compatibility: Some old versions of Jedis may not be compatible with new versions of Redis servers, so you need to pay special attention when upgrading Redis.

While Jedis is still a full-featured Redis client and can still suffice in some cases, Lettuce is generally recommended because it offers more functionality when it comes to high concurrency, performance, and modern application needs. , better performance, and more suitable for modern Java programming models. Therefore, using Jedis may cause the above problems, and you need to carefully consider whether to choose it