Redis连接池是用于管理和维护与Redis服务器之间的连接的组件。它在与Redis进行通信的应用程序中发挥关键作用,具有以下重要作用:
总之,Redis连接池的主要作用是提高性能、降低资源消耗、管理连接状态和复用连接,从而确保与Redis服务器的高效和稳定通信。在高并发的应用中,合适的连接池配置对于维护系统的稳定性和性能至关重要。
当考虑选择适当的Redis连接池时,更详细的对比可以涵盖各个方面,包括性能、配置、可维护性和适用场景等。以下是对Jedis和Lettuce这两个常见的Java Redis客户端连接池的更详细对比:
性能:
Jedis:
Lettuce:
连接池配置:
Jedis:
Lettuce:
可维护性:
Jedis:
Lettuce:
适用场景:
Jedis:
Lettuce:
生态系统集成:
Jedis:
Lettuce:
综上所述,如果你的应用需要处理高并发或需要异步编程支持,Lettuce可能是更好的选择。而如果你的应用相对简单,或者在传统Java框架下运行,Jedis也可以考虑。无论你选择哪个,都需要根据具体的需求来配置和测试连接池以确保最佳性能和稳定性。
要在Spring Boot项目中整合Redis连接池,你可以使用Spring Data Redis来简化整合过程。下面是整合Redis连接池的一般步骤:
pom.xml
文件中添加Spring Data Redis的依赖:<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
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
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
,然后使用它来执行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,你可以按照以下方式进行配置:
在pom.xml
中添加Jedis的依赖:
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
dependency>
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可能会引发上述问题,需要谨慎考虑是否选择它