前言
在实际项目开发过程中,相信很多人都有用到过 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服务
导入依赖
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-pool2
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,只能存入字符串,很多时候,我们需要自定义 RedisTemplate ,设置序列化器,这样我们可以很方便的操作实例对象。如下所示:
@Configuration public class RedisConfig { @Bean public RedisTemplate
redisTemplate(LettuceConnectionFactory connectionFactory) { RedisTemplate
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 = L; 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
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配置如下:
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-redis
io.lettuce
lettuce-core
org.springframework.boot
spring-boot-starter-test
test
redis.clients
jedis
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等这样的配置,需要手动配置,如下:
通过以上方式就可以连接上 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
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
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依赖排除即可。
12 org.springframework.boot 3spring-boot-starter-data-redis 45 106 9io.lettuce 7lettuce-core 8
然后再引入jedis依赖——
12 redis.clients 3jedis 4
这样,在进行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
Spring Boot 自动帮我们在容器中生成了一个RedisTemplate和一个StringRedisTemplate。但是,这个RedisTemplate的泛型是
。这样在写代码就很不方便,要写好多类型转换的代码。
因为有@ConditionalOnMissingBean(name = "redisTemplate")注解,所以如果Spring容器中有一个name 为redisTemplate 的 RedisTemplate 对象那么这个自动配置的RedisTemplate就不会实例化。
2.2.2 配置一个RredisTemplate
我们需要一个泛型为
形式的RedisTemplate,并且设置这个RedisTemplate在数据存在Redis时key及value的序列化方式(默认使用的JdkSerializationRedisSerializer 这样的会导致我们通过redis desktop manager显示的我们key跟value的时候显示不是正常字符)。
@Configuration public class RedisConfig { @Bean public RedisTemplate
redisTemplate(RedisConnectionFactory factory){ RedisTemplate
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
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
hmget(String key) { return redisTemplate.opsForHash().entries(key); } / * HashSet * * @param key 键 * @param map 对应多个键值 * @return true 成功 false 失败 */ public boolean hmset(String key, Map
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
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
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 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 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 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
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
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
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
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.nonelonely.com/article/91
https://juejin.im/post/5d65eb88e51d4561db5e3a79
https://www.cnblogs.com/zhujiqian/p/14552873.html
请我喝咖啡
如果觉得文章写得不错,能对你有帮助,可以扫描我的微信二维码请我喝咖啡哦~~哈哈~~

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/203356.html原文链接:https://javaforall.net
