https://blog.csdn.net/zzhongcy/article/details/102584028
前言
在实际项目开发过程中,相信很多人都有用到过 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 {undefined
@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) {undefined
RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(jedisConnectionFactory());
return redisTemplate;
}
@Bean
public JedisConnectionFactory jedisConnectionFactory() {undefined
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
类来设置连接的端口,地址等属性
那么问题来了,
Springboot2.x是如何默认使用了lettuce,这得去研究下里面的部分代码。我们可以可进入到Springboot2.x自动装配模块的redis部分,其中有一个RedisAutoConfiguration类,其主要作用是对Springboot自动配置连接redis类:
@Configuration( proxyBeanMethods = false )
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
......省略
}
这里只需要关注里面的一行注解:
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
这就意味着使用spring-boot-starter-data-redis依赖时,可自动导入lettuce和jedis两种驱动,按理来说,不会同时存在两种驱动,这样没有太大意义,因此,这里的先后顺序就很重要了,为什么这么说呢?
分别进入到LettuceConnectionConfiguration.class与JedisConnectionConfiguration.class当中,各自展示本文需要涉及到的核心代码:
- //LettuceConnectionConfiguration
- @ConditionalOnClass({RedisClient.class})
- class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
- ......省略
- @Bean
- @ConditionalOnMissingBean({RedisConnectionFactory.class})
- LettuceConnectionFactory redisConnectionFactory(ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers, ClientResources clientResources) throws UnknownHostException {
- LettuceClientConfiguration clientConfig = this.getLettuceClientConfiguration(builderCustomizers, clientResources, this.getProperties().getLettuce().getPool());
- return this.createLettuceConnectionFactory(clientConfig);
- }
- }
- //JedisConnectionConfiguration
- @ConditionalOnClass({GenericObjectPool.class, JedisConnection.class, Jedis.class})
- class JedisConnectionConfiguration extends RedisConnectionConfiguration {
- ......省略
- @Bean
- @ConditionalOnMissingBean({RedisConnectionFactory.class})
- JedisConnectionFactory redisConnectionFactory(ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) throws UnknownHostException {
- return this.createJedisConnectionFactory(builderCustomizers);
- }
- }
可见,LettuceConnectionConfiguration.class与JedisConnectionConfiguration.class当中都有一个相同的注解 @ConditionalOnMissingBean({RedisConnectionFactory.class}),这是说,假如RedisConnectionFactory这个bean已经被注册到容器里,那么与它相似的其他Bean就不会再被加载注册,
简单点说,对LettuceConnectionConfiguration与JedisConnectionConfiguration各自加上 @ConditionalOnMissingBean({RedisConnectionFactory.class})注解,两者当中只能加载注册其中一个到容器里,另外一个就不会再进行加载注册。
那么,问题就来了,谁会先被注册呢?
这就回到了上面提到的一句,
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
这一句里的先后顺序很关键,LettuceConnectionConfiguration在前面,就意味着,LettuceConnectionConfiguration将会被注册。
可见,Springboot默认是使用lettuce来连接redis的。
当我们引入spring-boot-starter-data-redis依赖包时,其实就相当于引入lettuce包,这时就会使用lettuce驱动,若不想使用该默认的lettuce驱动,直接将lettuce依赖排除即可。
1 <dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-data-redis</artifactId>
4 <exclusions>
5 <exclusion>
6 <groupId>io.lettuce</groupId>
7 <artifactId>lettuce-core</artifactId>
8 </exclusion>
9 </exclusions>
10 </dependency>
然后再引入jedis依赖——
1 <dependency>
2 <groupId>redis.clients</groupId>
3 <artifactId>jedis</artifactId>
4 </dependency>
这样,在进行RedisAutoConfiguration的导入注解时,因为没有找到lettuce依赖,故而这注解@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})的第二个位置上的JedisConnectionConfiguration就有效了,就可以被注册到容器了,当做springboot操作redis的驱动。
lettuce与jedis两者有什么区别呢?
- lettuce:底层是用netty实现,线程安全,默认只有一个实例。
- jedis:可直连redis服务端,配合连接池使用,可增加物理连接。
根据异常提示找到出现错误的方法,在下列代码里的LettuceConverters.toBoolean(this.getConnection().zadd(key, score, value))——
1 public Boolean zAdd(byte[] key, double score, byte[] value) {
2 Assert.notNull(key, "Key must not be null!");
3 Assert.notNull(value, "Value must not be null!");
4
5 try {
6 if (this.isPipelined()) {
7 this.pipeline(this.connection.newLettuceResult(this.getAsyncConnection().zadd(key, score, value), LettuceConverters.longToBoolean()));
8 return null;
9 } else if (this.isQueueing()) {
10 this.transaction(this.connection.newLettuceResult(this.getAsyncConnection().zadd(key, score, value), LettuceConverters.longToBoolean()));
11 return null;
12 } else {
13 return LettuceConverters.toBoolean(this.getConnection().zadd(key, score, value));
14 }
15 } catch (Exception var6) {
16 throw this.convertLettuceAccessException(var6);
17 }
18 }
LettuceConverters.toBoolean()是将long转为Boolean,正常情况下,this.getConnection().zadd(key, score, value)如果新增成功话,那么返回1,这样LettuceConverters.toBoolean(1)得到的是true,反之,如果新增失败,则返回0,即LettuceConverters.toBoolean(0),还有第三种情况,就是这个this.getConnection().zadd(key, score, value)方法出现异常,什么情况下会出现异常呢?
应该是,connection连接失败的时候。
这就意味着,以lettuce驱动连接redis的过程当中,会出现连接断开的情况,导致无法新增成功,超过一定时间还没有正常,就会出现连接超时的情况。
最后发现,阿里云官网上关于redis的客户端连接,是不推荐使用Lettuce客户端——
但是Lettuce后续版本可能会解决这个问题,让我们拭目以待,关注版本更新吧。
其他例子
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.13118
- 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.16131.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;
- }
- }
参考:
Spring Boot 2.0 整合 Redis(Lettuce) - 简书
springboot整合redis - 烦嚣的人
https://www.nonelonely.com/article/1556289630491
https://juejin.im/post/5d65eb88e51d4561db5e3a79
Springboot2.x集成lettuce连接redis集群报超时异常Command timed out after 6 second(s) - 朱季谦 - 博客园