前言
在实际项目开发过程中,相信很多人都有用到过 redis 这个NoSQL,这篇文章就详细讲讲springboot如何整合 redis
Redis 简介
简单介绍下Redis:
Redis是一个开源的使用 ANSI C语言编写,支持网络,可基于内存也可持久化的日志型,Key-Value数据库,并提供了多种语言的 API ,相比
Memcached
它支持存储的类型相对更多 (字符,哈希,集合,有序集合,列表等),同时Redis是线程安全的。
Redis 连接池简介
在后面 springboot 整合 redis 的时候会用到连接池,所以这里先来介绍下 Redis中的连接池:
客户端连接 Redis 使用的是 TCP协议,直连的方式每次需要建立 TCP连接,而连接池的方式是可以预先初始化好客户端连接,所以每次只需要从 连接池借用即可,而借用和归还操作是在本地进行的,只有少量的并发同步开销,远远小于新建TCP连接的开销。另外,直连的方式无法限制 redis客户端对象的个数,在极端情况下可能会造成连接泄漏,而连接池的形式可以有效的保护和控制资源的使用。
下面以Jedis客户端为例,再来总结下 客户端直连方式和连接池方式的对比
优点 | 缺点 | |
---|---|---|
直连 | 简单方便,适用于少量长期连接的场景 | 1. 存在每次新建/关闭TCP连接开销 2. 资源无法控制,极端情况下出现连接泄漏 3. Jedis对象线程不安全(Lettuce对象是线程安全的) |
连接池 | 1. 无需每次连接生成Jedis对象,降低开销 2. 使用连接池的形式保护和控制资源的使用 | 相对于直连,使用更加麻烦,尤其在资源的管理上需要很多参数来保证,一旦规划不合理也会出现问题 |
Jedis vs Lettuce
redis官方提供的java client有如图所示几种:
比较突出的是 Lettuce 和 jedis。Lettuce 和 jedis 的都是连接 Redis Server的客户端,Jedis 在实现上是直连 redis server,多线程环境下非线程安全,除非使用连接池,为每个 redis实例增加 物理连接。
Lettuce 是 一种可伸缩,线程安全,完全非阻塞的Redis客户端,多个线程可以共享一个RedisConnection,它利用Netty NIO 框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序。
在 springboot 1.5.x版本的默认的Redis客户端是 Jedis
实现的,springboot 2.x版本中默认客户端是用 lettuce
实现的。
Jedis 和 Lettuce 是 Java 操作 Redis 的客户端。在 Spring Boot 1.x 版本默认使用的是 jedis ,而在 Spring Boot 2.x 版本默认使用的就是Lettuce。关于 Jedis 跟 Lettuce 的区别如下:
- Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接
- Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
springboot 2.0 通过 lettuce集成Redis服务
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
application.properties配置文件
-
spring.redis.host=localhost
-
spring.redis.port=6379
-
spring.redis.password=root
-
# 连接池最大连接数(使用负值表示没有限制) 默认为8
-
spring.redis.lettuce.pool.max-active=8
-
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认为-1
-
spring.redis.lettuce.pool.max-wait=-1ms
-
# 连接池中的最大空闲连接 默认为8
-
spring.redis.lettuce.pool.max-idle=8
-
# 连接池中的最小空闲连接 默认为 0
-
spring.redis.lettuce.pool.min-idle=0
-
复制代码
自定义 RedisTemplate
默认情况下的模板只能支持 RedisTemplate<String,String>
,只能存入字符串,很多时候,我们需要自定义 RedisTemplate ,设置序列化器,这样我们可以很方便的操作实例对象。如下所示:
-
@Configuration
-
public class RedisConfig {
-
@Bean
-
public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) {
-
RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
-
redisTemplate.setKeySerializer(new StringRedisSerializer());
-
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
-
redisTemplate.setConnectionFactory(connectionFactory);
-
return redisTemplate;
-
}
-
}
-
复制代码
定义测试实体类
-
public class User implements Serializable {
-
private static final long serialVersionUID = 4220515347228129741L;
-
private Integer id;
-
private String username;
-
private Integer age;
-
public User(Integer id, String username, Integer age) {
-
this.id = id;
-
this.username = username;
-
this.age = age;
-
}
-
public User() {
-
}
-
//getter/setter 省略
-
}
-
复制代码
测试
-
@RunWith(SpringRunner.class)
-
@SpringBootTest
-
public class RedisTest {
-
private Logger logger = LoggerFactory.getLogger(RedisTest.class);
-
@Autowired
-
private RedisTemplate<String, Serializable> redisTemplate;
-
@Test
-
public void test() {
-
String key = "user:1";
-
redisTemplate.opsForValue().set(key, new User(1,"pjmike",20));
-
User user = (User) redisTemplate.opsForValue().get(key);
-
logger.info("uesr: "+user.toString());
-
}
-
}
-
复制代码
springboot 2.0 通过 jedis 集成Redis服务
导入依赖
因为 springboot2.0中默认是使用 Lettuce来集成Redis服务,spring-boot-starter-data-redis
默认只引入了 Lettuce
包,并没有引入 jedis
包支持。所以在我们需要手动引入 jedis
的包,并排除掉 lettuce
的包,pom.xml配置如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
application.properties配置
使用jedis的连接池
-
spring.redis.host=localhost
-
spring.redis.port=6379
-
spring.redis.password=root
-
spring.redis.jedis.pool.max-idle=8
-
spring.redis.jedis.pool.max-wait=-1ms
-
spring.redis.jedis.pool.min-idle=0
-
spring.redis.jedis.pool.max-active=8
配置 JedisConnectionFactory
因为在 springoot 2.x版本中,默认采用的是 Lettuce实现的,所以无法初始化出 Jedis的连接对象 JedisConnectionFactory
,所以我们需要手动配置并注入:
-
public class RedisConfig {
-
@Bean
-
JedisConnectionFactory jedisConnectionFactory() {
-
JedisConnectionFactory factory = new JedisConnectionFactory();
-
return factory;
-
}
-
}
但是启动项目后发现报出了如下的异常:
redis连接失败,springboot2.x通过以上方式集成Redis并不会读取配置文件中的 spring.redis.host
等这样的配置,需要手动配置,如下:
@Configuration
public class RedisConfig2 {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Bean
public RedisTemplate<String, Serializable> redisTemplate(JedisConnectionFactory connectionFactory) {
RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(jedisConnectionFactory());
return redisTemplate;
}
@Bean
public JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(host);
config.setPort(port);
config.setPassword(RedisPassword.of(password));
JedisConnectionFactory connectionFactory = new JedisConnectionFactory(config);
return connectionFactory;
}
}
通过以上方式就可以连接上 redis了,不过这里要提醒的一点就是,在springboot 2.x版本中 JedisConnectionFactory
设置连接的方法已过时,如图所示:
在 springboot 2.x
版本中推荐使用 RedisStandaloneConfiguration
类来设置连接的端口,地址等属性
其他例子
2.1 配置文件
lettuce的默认配置已经基本满足需求了,如果有需要可以自行配置
-
##端口号
-
server.port=8888
-
# Redis数据库索引(默认为0)
-
spring.redis.database=0
-
# Redis服务器地址
-
spring.redis.host=localhost
-
# Redis服务器连接端口
-
spring.redis.port=6379
-
# Redis服务器连接密码(默认为空)
-
spring.redis.password=
-
##连接池最大连接数(使用负值表示没有限制) 默认8
-
#spring.redis.lettuce.pool.max-active=8
-
## 连接池中的最大空闲连接 默认8
-
#spring.redis.lettuce.pool.max-idle=8
-
## 连接池中的最小空闲连接 默认0
-
#spring.redis.lettuce.pool.min-idle=0
-
## 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
-
#spring.redis.lettuce.pool.max-wait=-1
-
# 连接超时时间(毫秒)
-
spring.redis.timeout=200
2.2 RredisTemplate 配置
2.2.1 RredisTemplate自动配置
在引入redis的依赖后,RredisTemplate会自动配置,可以直接注入RedisTemplate使用。
-
@Configuration
-
@ConditionalOnClass(RedisOperations.class)
-
@EnableConfigurationProperties(RedisProperties.class)
-
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
-
public class RedisAutoConfiguration {
-
@Bean
-
@ConditionalOnMissingBean(name = "redisTemplate")
-
public RedisTemplate<Object, Object> redisTemplate(
-
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
-
RedisTemplate<Object, Object> template = new RedisTemplate<>();
-
template.setConnectionFactory(redisConnectionFactory);
-
return template;
-
}
-
@Bean
-
@ConditionalOnMissingBean
-
public StringRedisTemplate stringRedisTemplate(
-
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
-
StringRedisTemplate template = new StringRedisTemplate();
-
template.setConnectionFactory(redisConnectionFactory);
-
return template;
-
}
-
}
Spring Boot 自动帮我们在容器中生成了一个RedisTemplate和一个StringRedisTemplate。但是,这个RedisTemplate的泛型是<Object,Object>。这样在写代码就很不方便,要写好多类型转换的代码。
因为有@ConditionalOnMissingBean(name = "redisTemplate")
注解,所以如果Spring容器中有一个name 为redisTemplate 的 RedisTemplate 对象那么这个自动配置的RedisTemplate就不会实例化。
2.2.2 配置一个RredisTemplate
我们需要一个泛型为<String,Object>形式的RedisTemplate,并且设置这个RedisTemplate在数据存在Redis时key及value的序列化方式(默认使用的JdkSerializationRedisSerializer 这样的会导致我们通过redis desktop manager显示的我们key跟value的时候显示不是正常字符)。
-
@Configuration
-
public class RedisConfig {
-
@Bean
-
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
-
RedisTemplate<String,Object> template = new RedisTemplate <>();
-
template.setConnectionFactory(factory);
-
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
-
ObjectMapper om = new ObjectMapper();
-
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
-
jackson2JsonRedisSerializer.setObjectMapper(om);
-
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
-
// key采用String的序列化方式
-
template.setKeySerializer(stringRedisSerializer);
-
// hash的key也采用String的序列化方式
-
template.setHashKeySerializer(stringRedisSerializer);
-
// value序列化方式采用jackson
-
template.setValueSerializer(jackson2JsonRedisSerializer);
-
// hash的value序列化方式采用jackson
-
template.setHashValueSerializer(jackson2JsonRedisSerializer);
-
template.afterPropertiesSet();
-
return template;
-
}
-
}
注意
方法名一定要叫redisTemplate 因为@Bean注解是根据方法名配置这个bean的name的,覆盖默认配置
2.3 Redis 操作的工具类
下面这个工具类包含Redis的一些基本操作,大家可以参考
-
/**
-
* redis 工具类
-
*
-
* @author simon
-
* @date 2018-11-28 10:35
-
**/
-
@Component
-
public class RedisUtils {
-
/**
-
* 注入redisTemplate bean
-
*/
-
@Autowired
-
private RedisTemplate <String,Object> redisTemplate;
-
/**
-
* 指定缓存失效时间
-
*
-
* @param key 键
-
* @param time 时间(秒)
-
* @return
-
*/
-
public boolean expire(String key, long time) {
-
try {
-
if (time > 0) {
-
redisTemplate.expire(key, time, TimeUnit.SECONDS);
-
}
-
return true;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* 根据key获取过期时间
-
*
-
* @param key 键 不能为null
-
* @return 时间(秒) 返回0代表为永久有效
-
*/
-
public long getExpire(String key) {
-
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
-
}
-
/**
-
* 判断key是否存在
-
*
-
* @param key 键
-
* @return true 存在 false不存在
-
*/
-
public boolean hasKey(String key) {
-
try {
-
return redisTemplate.hasKey(key);
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* 删除缓存
-
*
-
* @param key 可以传一个值 或多个
-
*/
-
@SuppressWarnings("unchecked")
-
public void del(String... key) {
-
if (key != null && key.length > 0) {
-
if (key.length == 1) {
-
redisTemplate.delete(key[0]);
-
} else {
-
redisTemplate.delete(CollectionUtils.arrayToList(key));
-
}
-
}
-
}
-
// ============================String(字符串)=============================
-
/**
-
* 普通缓存获取
-
*
-
* @param key 键
-
* @return 值
-
*/
-
public Object get(String key) {
-
return key == null ? null : redisTemplate.opsForValue().get(key);
-
}
-
/**
-
* 普通缓存放入
-
*
-
* @param key 键
-
* @param value 值
-
* @return true成功 false失败
-
*/
-
public boolean set(String key, Object value) {
-
try {
-
redisTemplate.opsForValue().set(key, value);
-
return true;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* 普通缓存放入并设置时间
-
*
-
* @param key 键
-
* @param value 值
-
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
-
* @return true成功 false 失败
-
*/
-
public boolean set(String key, Object value, long time) {
-
try {
-
if (time > 0) {
-
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
-
} else {
-
set(key, value);
-
}
-
return true;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* 递增
-
*
-
* @param key 键
-
* @param delta 要增加几(大于0)
-
* @return
-
*/
-
public long incr(String key, long delta) {
-
if (delta < 0) {
-
throw new RuntimeException("递增因子必须大于0");
-
}
-
return redisTemplate.opsForValue().increment(key, delta);
-
}
-
/**
-
* 递减
-
*
-
* @param key 键
-
* @param delta 要减少几(小于0)
-
* @return
-
*/
-
public long decr(String key, long delta) {
-
if (delta < 0) {
-
throw new RuntimeException("递减因子必须大于0");
-
}
-
return redisTemplate.opsForValue().increment(key, -delta);
-
}
-
// ================================Hash(哈希)=================================
-
/**
-
* HashGet
-
*
-
* @param key 键 不能为null
-
* @param item 项 不能为null
-
* @return 值
-
*/
-
public Object hget(String key, String item) {
-
return redisTemplate.opsForHash().get(key, item);
-
}
-
/**
-
* 获取hashKey对应的所有键值
-
*
-
* @param key 键
-
* @return 对应的多个键值
-
*/
-
public Map <Object, Object> hmget(String key) {
-
return redisTemplate.opsForHash().entries(key);
-
}
-
/**
-
* HashSet
-
*
-
* @param key 键
-
* @param map 对应多个键值
-
* @return true 成功 false 失败
-
*/
-
public boolean hmset(String key, Map <String, Object> map) {
-
try {
-
redisTemplate.opsForHash().putAll(key, map);
-
return true;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* HashSet 并设置时间
-
*
-
* @param key 键
-
* @param map 对应多个键值
-
* @param time 时间(秒)
-
* @return true成功 false失败
-
*/
-
public boolean hmset(String key, Map <String, Object> map, long time) {
-
try {
-
redisTemplate.opsForHash().putAll(key, map);
-
if (time > 0) {
-
expire(key, time);
-
}
-
return true;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* 向一张hash表中放入数据,如果不存在将创建
-
*
-
* @param key 键
-
* @param item 项
-
* @param value 值
-
* @return true 成功 false失败
-
*/
-
public boolean hset(String key, String item, Object value) {
-
try {
-
redisTemplate.opsForHash().put(key, item, value);
-
return true;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* 向一张hash表中放入数据,如果不存在将创建
-
*
-
* @param key 键
-
* @param item 项
-
* @param value 值
-
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
-
* @return true 成功 false失败
-
*/
-
public boolean hset(String key, String item, Object value, long time) {
-
try {
-
redisTemplate.opsForHash().put(key, item, value);
-
if (time > 0) {
-
expire(key, time);
-
}
-
return true;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* 删除hash表中的值
-
*
-
* @param key 键 不能为null
-
* @param item 项 可以使多个 不能为null
-
*/
-
public void hdel(String key, Object... item) {
-
redisTemplate.opsForHash().delete(key, item);
-
}
-
/**
-
* 判断hash表中是否有该项的值
-
*
-
* @param key 键 不能为null
-
* @param item 项 不能为null
-
* @return true 存在 false不存在
-
*/
-
public boolean hHasKey(String key, String item) {
-
return redisTemplate.opsForHash().hasKey(key, item);
-
}
-
/**
-
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
-
*
-
* @param key 键
-
* @param item 项
-
* @param by 要增加几(大于0)
-
* @return
-
*/
-
public double hincr(String key, String item, double by) {
-
return redisTemplate.opsForHash().increment(key, item, by);
-
}
-
/**
-
* hash递减
-
*
-
* @param key 键
-
* @param item 项
-
* @param by 要减少记(小于0)
-
* @return
-
*/
-
public double hdecr(String key, String item, double by) {
-
return redisTemplate.opsForHash().increment(key, item, -by);
-
}
-
// ============================Set(集合)=============================
-
/**
-
* 根据key获取Set中的所有值
-
*
-
* @param key 键
-
* @return
-
*/
-
public Set <Object> sGet(String key) {
-
try {
-
return redisTemplate.opsForSet().members(key);
-
} catch (Exception e) {
-
e.printStackTrace();
-
return null;
-
}
-
}
-
/**
-
* 根据value从一个set中查询,是否存在
-
*
-
* @param key 键
-
* @param value 值
-
* @return true 存在 false不存在
-
*/
-
public boolean sHasKey(String key, Object value) {
-
try {
-
return redisTemplate.opsForSet().isMember(key, value);
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* 将数据放入set缓存
-
*
-
* @param key 键
-
* @param values 值 可以是多个
-
* @return 成功个数
-
*/
-
public long sSet(String key, Object... values) {
-
try {
-
return redisTemplate.opsForSet().add(key, values);
-
} catch (Exception e) {
-
e.printStackTrace();
-
return 0;
-
}
-
}
-
/**
-
* 将set数据放入缓存
-
*
-
* @param key 键
-
* @param time 时间(秒)
-
* @param values 值 可以是多个
-
* @return 成功个数
-
*/
-
public long sSetAndTime(String key, long time, Object... values) {
-
try {
-
Long count = redisTemplate.opsForSet().add(key, values);
-
if (time > 0)
-
expire(key, time);
-
return count;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return 0;
-
}
-
}
-
/**
-
* 获取set缓存的长度
-
*
-
* @param key 键
-
* @return
-
*/
-
public long sGetSetSize(String key) {
-
try {
-
return redisTemplate.opsForSet().size(key);
-
} catch (Exception e) {
-
e.printStackTrace();
-
return 0;
-
}
-
}
-
/**
-
* 移除值为value的
-
*
-
* @param key 键
-
* @param values 值 可以是多个
-
* @return 移除的个数
-
*/
-
public long setRemove(String key, Object... values) {
-
try {
-
Long count = redisTemplate.opsForSet().remove(key, values);
-
return count;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return 0;
-
}
-
}
-
// ===============================List(列表)=================================
-
/**
-
* 获取list缓存的内容
-
*
-
* @param key 键
-
* @param start 开始
-
* @param end 结束 0 到 -1代表所有值
-
* @return
-
*/
-
public List <Object> lGet(String key, long start, long end) {
-
try {
-
return redisTemplate.opsForList().range(key, start, end);
-
} catch (Exception e) {
-
e.printStackTrace();
-
return null;
-
}
-
}
-
/**
-
* 获取list缓存的长度
-
*
-
* @param key 键
-
* @return
-
*/
-
public long lGetListSize(String key) {
-
try {
-
return redisTemplate.opsForList().size(key);
-
} catch (Exception e) {
-
e.printStackTrace();
-
return 0;
-
}
-
}
-
/**
-
* 通过索引 获取list中的值
-
*
-
* @param key 键
-
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
-
* @return
-
*/
-
public Object lGetIndex(String key, long index) {
-
try {
-
return redisTemplate.opsForList().index(key, index);
-
} catch (Exception e) {
-
e.printStackTrace();
-
return null;
-
}
-
}
-
/**
-
* 将list放入缓存
-
*
-
* @param key 键
-
* @param value 值
-
* @return
-
*/
-
public boolean lSet(String key, Object value) {
-
try {
-
redisTemplate.opsForList().rightPush(key, value);
-
return true;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* 将list放入缓存
-
*
-
* @param key 键
-
* @param value 值
-
* @param time 时间(秒)
-
* @return
-
*/
-
public boolean lSet(String key, Object value, long time) {
-
try {
-
redisTemplate.opsForList().rightPush(key, value);
-
if (time > 0)
-
expire(key, time);
-
return true;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* 将list放入缓存
-
*
-
* @param key 键
-
* @param value 值
-
* @return
-
*/
-
public boolean lSet(String key, List <Object> value) {
-
try {
-
redisTemplate.opsForList().rightPushAll(key, value);
-
return true;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* 将list放入缓存
-
*
-
* @param key 键
-
* @param value 值
-
* @param time 时间(秒)
-
* @return
-
*/
-
public boolean lSet(String key, List <Object> value, long time) {
-
try {
-
redisTemplate.opsForList().rightPushAll(key, value);
-
if (time > 0)
-
expire(key, time);
-
return true;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* 根据索引修改list中的某条数据
-
*
-
* @param key 键
-
* @param index 索引
-
* @param value 值
-
* @return
-
*/
-
public boolean lUpdateIndex(String key, long index, Object value) {
-
try {
-
redisTemplate.opsForList().set(key, index, value);
-
return true;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return false;
-
}
-
}
-
/**
-
* 移除N个值为value
-
*
-
* @param key 键
-
* @param count 移除多少个
-
* @param value 值
-
* @return 移除的个数
-
*/
-
public long lRemove(String key, long count, Object value) {
-
try {
-
Long remove = redisTemplate.opsForList().remove(key, count, value);
-
return remove;
-
} catch (Exception e) {
-
e.printStackTrace();
-
return 0;
-
}
-
}
-
}
具体测试部分就省略了。
SpringBoot2.X整合Redis(单机+集群+多数据源)-Lettuce版
Redis 三大客户端
简介
Jedis:是Redis 老牌的Java实现客户端,提供了比较全面的Redis命令的支持,
Redisson:实现了分布式和可扩展的Java数据结构。
Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
优点:
Jedis:比较全面的提供了Redis的操作特性
Redisson:促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如分布式锁,分布式集合,可通过Redis支持延迟队列
Lettuce:主要在一些分布式缓存框架上使用比较多
可伸缩:
Jedis:使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。
Redisson:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作
Lettuce:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作
pipeline 的支持
jedis 通过一定的改造后可以支持pipeline, 具体可以看 Redis 批量操作之 pipeline
但是 Lettuce 的pipeline行为很奇怪. 在 Spring RedisTemplate 中的 executePipelined 方法中的情况:
-
有时完全是一条一条命令地发送
-
有时全合并几条命令发送
-
但跟完全 pipeline 的方式不同, 测试多次, 但没发现有一次是完整 pipeline 的
-
复制代码
所以如果需要使用pipeline的话, 建议还是使用Jedis
Lettuce 接入
单机版
配置文件
-
host: 192.168.131.118
-
port: 4884
-
password: dsgs548
-
database: 0
-
# lettuce简单配置
-
lettuce:
-
pool:
-
# 最大活跃链接数 默认8
-
max-active: 5
-
# 最大空闲连接数 默认8
-
max-idle: 10
-
# 最小空闲连接数 默认0
-
min-idle: 0
-
复制代码
redis配置类
-
@Configuration
-
public class RedisConfig {
-
@Bean
-
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
-
RedisTemplate<String, Object> template = new RedisTemplate<>();
-
template.setConnectionFactory(factory);
-
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
-
ObjectMapper om = new ObjectMapper();
-
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
-
jackson2JsonRedisSerializer.setObjectMapper(om);
-
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
-
// key采用String的序列化方式
-
template.setKeySerializer(stringRedisSerializer);
-
// hash的key也采用String的序列化方式
-
template.setHashKeySerializer(stringRedisSerializer);
-
// value序列化方式采用jackson
-
template.setValueSerializer(jackson2JsonRedisSerializer);
-
// hash的value序列化方式采用jackson
-
template.setHashValueSerializer(jackson2JsonRedisSerializer);
-
template.afterPropertiesSet();
-
return template;
-
}
-
}
-
复制代码
直接引入RedisTemplate 即可, 单机版比较简单
集群版+多数据源
配置文件
-
spring:
-
redis:
-
cluster:
-
nodes: 192.168.131.118:4883,192.168.131.118:4884,192.168.131.118:4885
-
# nodes:
-
# - 192.168.131.118:4883
-
# - 1192.168.131.118:4884
-
# - 192.168.131.118:4885
-
password: adfafsas
-
lettuce:
-
pool:
-
# 最大活跃链接数 默认8
-
max-active: 5
-
# 最大空闲连接数 默认8
-
max-idle: 10
-
# 最小空闲连接数 默认0
-
min-idle: 0
-
secondaryRedis:
-
cluster:
-
nodes: 192.168.131.118:4883,192.168.131.118:4884,192.168.131.118:4885
-
# nodes:
-
# - 192.168.131.118:4883
-
# - 192.168.131.118:4884
-
# - 192.168.131.118:4885
-
password: advfafasfsa
-
复制代码
redis配置类
-
@Configuration
-
public class RedisConfig {
-
@Autowired
-
private Environment environment;
-
/**
-
* 配置lettuce连接池
-
*
-
* @return
-
*/
-
@Bean
-
@Primary
-
@ConfigurationProperties(prefix = "spring.redis.cluster.lettuce.pool")
-
public GenericObjectPoolConfig redisPool() {
-
return new GenericObjectPoolConfig();
-
}
-
/**
-
* 配置第一个数据源的
-
*
-
* @return
-
*/
-
@Bean("redisClusterConfig")
-
@Primary
-
public RedisClusterConfiguration redisClusterConfig() {
-
Map<String, Object> source = new HashMap<>(8);
-
source.put("spring.redis.cluster.nodes", environment.getProperty("spring.redis.cluster.nodes"));
-
RedisClusterConfiguration redisClusterConfiguration;
-
redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
-
redisClusterConfiguration.setPassword(environment.getProperty("spring.redis.password"));
-
return redisClusterConfiguration;
-
}
-
/**
-
* 配置第一个数据源的连接工厂
-
* 这里注意:需要添加@Primary 指定bean的名称,目的是为了创建两个不同名称的LettuceConnectionFactory
-
*
-
* @param redisPool
-
* @param redisClusterConfig
-
* @return
-
*/
-
@Bean("lettuceConnectionFactory")
-
@Primary
-
public LettuceConnectionFactory lettuceConnectionFactory(GenericObjectPoolConfig redisPool, @Qualifier("redisClusterConfig") RedisClusterConfiguration redisClusterConfig) {
-
LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();
-
return new LettuceConnectionFactory(redisClusterConfig, clientConfiguration);
-
}
-
/**
-
* 配置第一个数据源的RedisTemplate
-
* 注意:这里指定使用名称=factory 的 RedisConnectionFactory
-
* 并且标识第一个数据源是默认数据源 @Primary
-
*
-
* @param redisConnectionFactory
-
* @return
-
*/
-
@Bean("redisTemplate")
-
@Primary
-
public RedisTemplate redisTemplate(@Qualifier("lettuceConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
-
return getRedisTemplate(redisConnectionFactory);
-
}
-
/**
-
* 配置第二个数据源
-
*
-
* @return
-
*/
-
@Bean("secondaryRedisClusterConfig")
-
public RedisClusterConfiguration secondaryRedisConfig() {
-
Map<String, Object> source = new HashMap<>(8);
-
source.put("spring.redis.cluster.nodes", environment.getProperty("spring.secondaryRedis.cluster.nodes"));
-
RedisClusterConfiguration redisClusterConfiguration;
-
redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
-
redisClusterConfiguration.setPassword(environment.getProperty("spring.redis.password"));
-
return redisClusterConfiguration;
-
}
-
@Bean("secondaryLettuceConnectionFactory")
-
public LettuceConnectionFactory secondaryLettuceConnectionFactory(GenericObjectPoolConfig redisPool, @Qualifier("secondaryRedisClusterConfig")RedisClusterConfiguration secondaryRedisClusterConfig) {
-
LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();
-
return new LettuceConnectionFactory(secondaryRedisClusterConfig, clientConfiguration);
-
}
-
/**
-
* 配置第一个数据源的RedisTemplate
-
* 注意:这里指定使用名称=factory2 的 RedisConnectionFactory
-
*
-
* @param redisConnectionFactory
-
* @return
-
*/
-
@Bean("secondaryRedisTemplate")
-
public RedisTemplate secondaryRedisTemplate(@Qualifier("secondaryLettuceConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
-
return getRedisTemplate(redisConnectionFactory);
-
}
-
private RedisTemplate getRedisTemplate(RedisConnectionFactory factory) {
-
RedisTemplate<String, Object> template = new RedisTemplate<>();
-
template.setConnectionFactory(factory);
-
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
-
ObjectMapper om = new ObjectMapper();
-
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
-
jackson2JsonRedisSerializer.setObjectMapper(om);
-
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
-
// key采用String的序列化方式
-
template.setKeySerializer(stringRedisSerializer);
-
// hash的key也采用String的序列化方式
-
template.setHashKeySerializer(stringRedisSerializer);
-
// value序列化方式采用jackson
-
template.setValueSerializer(jackson2JsonRedisSerializer);
-
// hash的value序列化方式采用jackson
-
template.setHashValueSerializer(jackson2JsonRedisSerializer);
-
template.afterPropertiesSet();
-
return template;
-
}
-
}
参考:
https://www.jianshu.com/p/071bae3834b0
https://www.tapme.top/blog/detail/2019-02-22-14-59/
https://www.nonelonely.com/article/1556289630491
https://juejin.im/post/5d65eb88e51d4561db5e3a79