springboot整合Redis中连接池jedis与lettuce的对比和实现
为什么要使用Redis连接池
Redis连接池是用于管理和维护与Redis服务器之间的连接的组件。它在与Redis进行通信的应用程序中发挥关键作用,具有以下重要作用:
- 提高性能: Redis连接池可以维护一组可重用的连接,减少了在每次需要与Redis服务器通信时创建和销毁连接的开销。这有助于提高应用程序的性能,因为连接的创建和销毁通常是相对昂贵的操作。
- 降低资源消耗: 连接池可以控制连接的数量,确保不会创建过多的连接,从而降低了内存和CPU资源的消耗。这对于保护Redis服务器和应用程序服务器免于不必要的负载很重要。
- 连接复用: 连接池可以重用现有的连接,而不是每次请求都创建新连接。这减少了连接的建立和销毁次数,提高了效率。
- 连接管理: 连接池可以管理连接的状态,包括检查连接的可用性、维护连接的健康状态,以及自动重新连接失败的连接。这有助于确保应用程序与Redis之间的稳定连接。
- 连接超时控制: 连接池可以配置连接的超时时间,以确保在一段时间内没有活动的连接会被关闭,从而释放资源并防止连接泄漏。
- 并发控制: 连接池可以限制同时使用的连接数量,以避免过多的并发请求对Redis服务器的影响。这有助于平滑地处理并发负载。
- 性能优化: 连接池可以优化连接的复用,减少连接的闲置时间,以确保连接保持在最佳状态,以及快速响应请求。
总之,Redis连接池的主要作用是提高性能、降低资源消耗、管理连接状态和复用连接,从而确保与Redis服务器的高效和稳定通信。在高并发的应用中,合适的连接池配置对于维护系统的稳定性和性能至关重要。
jedis与lettuce的区别
当考虑选择适当的Redis连接池时,更详细的对比可以涵盖各个方面,包括性能、配置、可维护性和适用场景等。以下是对Jedis和Lettuce这两个常见的Java Redis客户端连接池的更详细对比:
-
性能:
-
Jedis:
- Jedis在低并发情况下性能表现良好,因为它使用阻塞I/O。
- 在高并发场景下,Jedis的性能可能受到限制,因为每个连接都是阻塞的,需要等待IO操作完成。
-
Lettuce:
- Lettuce使用非阻塞I/O,因此在高并发环境下性能更好,能够充分利用系统资源。
- 它支持异步操作和响应式编程,使其在异步编程中表现出色。
-
-
连接池配置:
-
Jedis:
- Jedis的连接池配置相对简单,需要手动设置最大连接数、最大空闲连接数、连接超时等参数。
- 连接池的管理需要手动实现。
-
Lettuce:
- Lettuce提供了更丰富的连接池配置选项,包括连接池的行为、拓扑刷新等。
- 它内置了一个高性能的连接池,不需要手动管理连接池。
-
-
可维护性:
-
Jedis:
- Jedis相对较简单,容易上手,但需要手动管理连接池和错误处理。
- 它的社区支持较少,维护可能相对困难。
-
Lettuce:
- Lettuce具有更多的功能和可维护性,有更好的文档和社区支持。
- 它内置了一些高级功能,如拓扑刷新和响应式编程。
-
-
适用场景:
-
Jedis:
- 适用于简单的应用或者低并发环境。
- 对于传统的同步编程需求,它可以胜任。
-
Lettuce:
- 适用于高并发、高吞吐量的应用。
- 对于异步和响应式编程需求,特别适用。
-
-
生态系统集成:
-
Jedis:
- Jedis在一些旧版的Java框架中有更好的集成支持。
- 在一些需要使用旧版框架的项目中可能更适合。
-
Lettuce:
- Lettuce在现代Java框架中有更好的集成支持,特别是Spring框架。
- 在使用Spring Boot等现代Java技术的项目中更为流行。
-
综上所述,如果你的应用需要处理高并发或需要异步编程支持,Lettuce可能是更好的选择。而如果你的应用相对简单,或者在传统Java框架下运行,Jedis也可以考虑。无论你选择哪个,都需要根据具体的需求来配置和测试连接池以确保最佳性能和稳定性。
二者在springboot中的实现
Lettuce
要在Spring Boot项目中整合Redis连接池,你可以使用Spring Data Redis来简化整合过程。下面是整合Redis连接池的一般步骤:
- 添加依赖: 首先,在你的Spring Boot项目的
pom.xml
文件中添加Spring Data Redis的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置Redis连接属性: 在
application.properties
或application.yml
中配置Redis连接属性。以下是一个示例配置:
# redis配置,如果是单个节点就不需要看注释的
#spring.redis.cluster.nodes=localhost:6399,localhost:6396
spring.redis.host=localhost
spring.redis.port=6396
# 如果没有密码的话就可以不填
spring.redis.password=
#spring.redis.cluster.max-redirects=3
spring.redis.timeout=10000
# 连接到 Redis 哨兵
#spring.redis.sentinel.master=mymaster
#spring.redis.sentinel.nodes=192.168.1.75:26379,192.168.1.75:26378,192.168.1.75:26377
- 创建Redis配置类: 创建一个Java配置类,用于配置Redis连接池和RedisTemplate。例如:
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("开始创建redis模板对象");
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
// 设置redis连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置 redis key 的序列化器,可以解决乱码问题
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 设置 redis 值的序列化器,可以解决乱码问题
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
return redisTemplate;
}
}
这个配置类使用Lettuce作为Redis连接池,你可以根据需要自定义连接池的属性。
- 使用RedisTemplate: 在你的Service或Controller中注入
RedisTemplate
,然后使用它来执行Redis操作。例如:
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);
}
}
这样,你就可以在Spring Boot项目中成功整合Redis连接池,并使用RedisTemplate
执行各种Redis操作。确保根据你的实际需求适当调整配置和操作。
Jedis
如果你想使用Jedis,你可以按照以下方式进行配置:
- 添加Jedis依赖:
在pom.xml
中添加Jedis的依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
- 创建Jedis连接池:
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("初始化Redis连接池JedisPool成功!地址: " + host + ":" + port);
return jedisPool;
} catch (Exception e) {
log.error("初始化Redis连接池JedisPool异常:" + e.getMessage());
}
return null;
}
/**
* description: 创建jedisConnectionFactory工厂
*
* @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: 创建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;
}
}
这个配置类使用Jedis作为Redis连接池,你可以根据需要自定义连接池的属性。在JedisConnectionFactory
中,你可以设置连接池的参数,例如最大连接数和最大空闲连接数。
- 工具类实现
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 {
/**
* 静态注入JedisPool连接池
* JedisUtil直接调用静态方法即可
*/
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("获取Jedis资源异常:" + e.getMessage());
}
}
/**
* description: 释放资源
*
* @author bo
* @date 2022/7/22 9:06 AM
*/
public static void closePool() {
try {
jedisPool.close();
} catch (Exception e) {
throw new BusinessException("释放Jedis资源异常:" + e.getMessage());
}
}
/**
* description: 获取对象
*
* @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()) {
assert jedis != null;
byte[] bytes = jedis.get(key.getBytes());
if (!Assert.isEmpty(bytes)) {
return JSON.parse(bytes);
}
} catch (Exception e) {
throw new BusinessException("获取Redis键值getObject方法异常: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()) {
assert jedis != null;
return jedis.set(key.getBytes(), JSONObject.toJSONString(value).getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new BusinessException("设置Redis键值setObject方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
}
}
/**
* description: 过期时间
*
* @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()) {
assert jedis != 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("设置Redis键值setObject方法异常: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()) {
assert jedis != null;
return jedis.get(key);
} catch (Exception e) {
throw new BusinessException("获取Redis键值getJson方法异常: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()) {
assert jedis != null;
return jedis.set(key, value);
} catch (Exception e) {
throw new BusinessException("设置Redis键值setJson方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
}
}
/**
* description: 删除指定的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()) {
assert jedis != null;
jedis.del(key);
} catch (Exception e) {
log.error("redis删除 【key={}】出现异常: {}", key, e.getMessage());
throw new BusinessException("redis删除" + key + "出现异常");
}
}
/**
* description: 批量删除指定的key
* @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()) {
assert jedis != null;
jedis.del(key);
} catch (Exception e) {
log.error("redis删除 【keys= [{}]】出现异常: {}", key, e.getMessage());
throw new BusinessException("redis删除" + Arrays.toString(key) + "出现异常:" + e.getMessage());
}
}
/**
* description: 推送消息
*
* @param channel,message 消息通道,消息体
* @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("redis发布消息出现异常: {}", e.getMessage());
throw new BusinessException("redis发布消息出现异常:" + e.getMessage());
}
}
/**
* 监听消息通道
*
* @param jedisPubSub 对象
* @param channels 消息通道
*/
public static void subscribe(JedisPubSub jedisPubSub, String... channels) {
try (Jedis jedis = getJedis()) {
assert jedis != null;
jedis.subscribe(jedisPubSub, channels);
} catch (Exception e) {
log.error("redis监听消息出现异常: {}", e.getMessage());
throw new BusinessException("redis监听消息出现异常:" + e.getMessage());
}
}
}
这里重点说明一下
try-with-resources
try-with-resources
是Java 7引入的一个重要语言特性,它用于自动管理资源(如文件、网络连接、数据库连接等),确保在代码块执行完毕后资源得到正确关闭和释放。这个特性有助于减少资源泄漏的风险,提高了代码的可读性和可维护性。
以下是关于try-with-resources
的重点:
-
语法:
try-with-resources
使用try
块包裹一段代码,其中的资源在try
块开始时初始化,并在try
块结束时自动关闭和释放。资源初始化通过AutoCloseable
接口的实现类完成,这些类需要在资源关闭时执行close()
方法。这通常包括Java标准库中的一些类,如InputStream
、OutputStream
、Reader
、Writer
、Socket
、Jedis
等。 -
资源自动关闭: 在
try-with-resources
块结束时,不需要手动调用资源的close()
方法,它们会自动被调用。这确保了资源的正确关闭,无论代码块是否正常执行或抛出异常。 -
多资源管理: 可以在同一个
try-with-resources
块中管理多个资源。这些资源的初始化和关闭顺序与它们在try
块中的声明顺序相反。 -
异常处理: 如果在
try
块中抛出异常,try-with-resources
会首先关闭所有已初始化的资源,然后再抛出异常。这确保了资源被正确关闭,不会造成资源泄漏。 -
适用范围:
try-with-resources
适用于实现了AutoCloseable
接口的类。如果你自定义了一个资源类,你可以让它实现AutoCloseable
接口并重写close()
方法,以便在try-with-resources
中使用。
下面是一个简单的示例,演示了如何在try-with-resources
中使用文件读取资源:
try (FileReader fileReader = new FileReader("example.txt")) {
int data;
while ((data = fileReader.read()) != -1) {
// 处理文件内容
}
} catch (IOException e) {
// 处理异常
}
在这个示例中,FileReader
是一个实现了AutoCloseable
接口的资源类。当try-with-resources
块结束时,fileReader
会自动关闭,无需手动调用close()
方法。
总之,try-with-resources
是一项重要的Java语言特性,有助于简化资源管理、降低资源泄漏的风险,并提高代码的可读性和可维护性。它在处理需要手动关闭的资源时非常有用,包括文件、数据库连接、网络连接以及其他需要明确关闭的资源。
建议
一句话,建议使用Lettuce
使用Jedis作为Redis客户端在某些情况下可能会引发一些问题,这些问题包括:
-
性能问题: Jedis在高并发情况下可能性能不如Lettuce。因为Jedis使用阻塞I/O,每个连接都是阻塞的,这可能会导致性能瓶颈,特别是在大量并发请求同时到达时。
-
连接管理: Jedis需要手动管理连接池和连接的状态。这意味着你需要自己配置连接池参数、处理连接的创建和销毁,以及管理连接的健康状态。这可能会导致连接泄漏或连接不正确关闭的问题。
-
线程安全性: Jedis的实例不是线程安全的,这意味着在多线程环境下需要进行额外的同步处理,以确保正确的并发访问。这增加了开发的复杂性。
-
异常处理: Jedis的异常处理需要开发人员进行细致的处理。例如,在连接过期或发生网络问题时,需要捕获和处理异常,以确保应用程序的稳定性。
-
社区支持: 相对于Lettuce,Jedis的社区支持相对较少。这意味着你可能不容易找到相关问题的解决方案或获取更新的维护和支持。
-
不适用于响应式编程: 如果你的应用需要使用响应式编程模型,Jedis可能不是最佳选择,因为它不支持响应式操作。Lettuce在这方面更适合。
-
版本兼容性: Jedis的一些旧版本可能不兼容新版本的Redis服务器,因此在升级Redis时需要格外注意。
虽然Jedis仍然是一个功能齐全的Redis客户端,并且在某些情况下仍然可以满足需求,但在高并发、高性能和现代应用需求方面,Lettuce通常更被推荐,因为它提供了更多的功能、更好的性能,并且更适合现代的Java编程模型。因此,使用Jedis可能会引发上述问题,需要谨慎考虑是否选择它