Lettuce与Jedis
Redis和Mysql一样是数据库,Mysql有对应的JDBC连接,Redis也有对应的Java客户端开发包,集成了Redis的一些命令操作,封装了Redis的java客户端,类似与redis-cli
前面:SpringBoot - 整合Redis:使用Jedis客户端通过Jedis、JedisPool使SpringBoot连接Redis
SpringBoot1.x的版本时默认使用的Jedis客户端,在SpringBoot2.x后,默认使用Lettuce
Lettuce与Jedis有什么区别?
- Jedis在实现上是直接连接Redis Server,如果在多线程环境下是非线程安全的。每个线程都去拿自己的Jedis 实例,当连接数量增多时,资源消耗阶梯式增大,连接成本就较高(使用JedisPool会有一定的改善)
- Lettuce的连接是基于Netty的,异步、多线程、事件驱动,连接实例可以在多个线程间共享,当多线程使用同一连接实例时,是线程安全的
Lettuce虽然更复杂,但更强(能被Spring生态承认)
RedisTemplate封装Lettuce
就如同JDBC可以连接Mysql数据库一样,通过Lettuce也可以连接操作Redis,通过调用lettuce-core核心jar包可以进行操作
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
关于Lettuce操作Redis可以看这位大佬的文章:Redis高级客户端Lettuce详解
但可以实现是一回事,方便又是一回事,用Lettuce操作Redis实际上是够呛的
Spring框架提供了对Lettuce的封装RedisTemplate,它让Spring框架体系可以更方便的操作Redis
RedisTemplate位于spring-data-redis:Spring官网相关,是一个高度封装的对象
相关配置:
- 一个SpringBoot项目及Maven依赖
<dependencies>
<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>
</dependency>
<!--redis依赖commons-pool-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- application.yml配置:Redis与Lettuce的相关属性
spring:
redis:
host: IP地址
password: Redis密码
port: 6379
timeout: 10000
lettuce:
pool:
# 连接池最大连接数
max-active: 8
# 连接池中的最大空闲连接
max-idle: 8
# 连接池中的最小空闲连接
min-idle: 2
# 连接池最大阻塞等待时间
max-wait: 1000
- 配置类RedisConfig(可以不设置,这个的作用是设置序列化策略,后面解释)
默认情况下的模板只能支持StringRedisTemplate<String,String>,只能存字符串,如果改成RedisTemplate<String,Object>就不行了
没有配置的话存的数据是:1111:RedisTemplate => lettuce
,实际上Redis中键值对是这样的
package com.lettuce.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Author : zfk
* Data : 16:27
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
RedisTemplate<String,Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jacksonSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSerializer.setObjectMapper(objectMapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//使用注解@Bean返回RedisTemplate时,同时配置hashKey与HashValue的序列化方式
//key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//value序列化方式采用Jackson
template.setValueSerializer(jacksonSerializer);
//hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//hash的vlaue序列化采用Jackson
template.setHashValueSerializer(jacksonSerializer);
template.afterPropertiesSet();
return template;
}
}
一个简单的案例:通过key获得value
使用服务层即可:
UserService接口:
package com.lettuce.service;
import com.lettuce.po.User;
/**
* Author : zfk
* Data : 17:01
*/
public interface UserService {
public String getString(String key);
}
实现类:UserServiceImpl
package com.lettuce.service;
import com.lettuce.po.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* Author : zfk
* Data : 16:56
*/
@Service
@Slf4j
public class UserServiceImpl implements UserService{
@Autowired
private RedisTemplate<String,Object> redisTemplate;
/**
* Redis命令 ==》 Lettuce => RedisTemplate进一步的封装
* redis String类型
* 用户输入一个key,先判断Redis是否存在数据,存在就在Redis中查询,
* 不存在就在Mysql数据库查询(模拟)。将结果返回给Redis
*/
@Override
public String getString(String key){
log.info("RedisTemplate ==> 测试");
String value = null;
//hasKey 相当于 exist
if (redisTemplate.hasKey(key)){
log.info("=== Redis查询到数据 ===");
return (String) redisTemplate.opsForValue().get(key);
}else {
value = "RedisTemplate => lettuce";
log.info("Redis没有查询到,存入:"+value);
redisTemplate.opsForValue().set(key,value);
}
return value;
}
}
测试类:LettuceApplicationTests
package com.lettuce;
import com.lettuce.po.User;
import com.lettuce.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class LettuceApplicationTests {
@Autowired
private UserService userService;
@Test
void contextLoads() {
String string = userService.getString("1111");
System.out.println(string);
}
}
第一次没有在Redis查找到数据,将值存入Redis:
再测试一次:Redis查找的了数据
序列化策略RedisSerializer<T>
前面我们自定义了配置类,当value为Object对象时的处理方法
Spring提供了以下序列化策略:
JdkSerializationRedisSerializer:POJO对象的存取场景,使用JDK本身序列化机制,将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。是目前最常用的序列化策略。
StringRedisSerializer:Key或者value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是“new String(bytes, charset)”和“string.getBytes(charset)”的直接封装。是最轻量级和高效的策略。
Jackson2JsonRedisSerializer:jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。【需要jackson-mapper-asl工具支持】
OxmSerializer:提供了将javabean与xml之间的转换能力,目前可用的三方支持包括jaxb,apache-xmlbeans;redis存储的数据将是xml工具。不过使用此策略,编程将会有些难度,而且效率最低;不建议使用。【需要spring-oxm模块的支持】
我们来解读一下编写的RedisConfig:
在RedisTemplate类中可以看到:默认StringRedisSerializer序列化策略
内置了keySerializer、hashKeySerializer4个属性
操作类:operation
Spring针对Lettuce/Jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
在RedisTemplate类中可以看到这些属性:
这些Operation对象对应Redis的数据类型:
- ValueOperations:简单K-V操作
- ListOperations:针对list类型的数据操作
- SetOperations:set类型数据操作
- StreamOperations:Stream是Redis 5.0引入的一种新数据类型,它以更抽象的方式模拟日志数据结构,但日志的本质仍然完好无损
- ZSetOperations:zset类型数据操作
- GeoOperations:地理位置信息
- HyperLogLogOperations:HyperLogLog是用来做基数统计的算法
- ClusterOperations:cluster集群
我们使用RedisTemplate操作:
redisTemplate.opsForValue().get(key);
本质上是操作Operation对象:
所以如果不喜欢这种方式,可以直接用Operation操作:改造上面的getString方法
public class UserServiceImpl implements UserService{
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Resource(name = "redisTemplate")
private ValueOperations<String,Object> valueOps;
/**
* Redis命令 ==》 Lettuce => RedisTemplate进一步的封装
* redis String类型
* 用户输入一个key,先判断Redis是否存在数据,存在就在Redis中查询,
* 不存在就在Mysql数据库查询(模拟)。将结果返回给Redis
*/
@Override
public String getString(String key){
log.info("RedisTemplate ==> 测试");
String value = null;
//hasKey 相当于 exist
if (redisTemplate.hasKey(key)){
log.info("=== Redis查询到数据 ===");
return (String) valueOps.get(key);
}else {
value = "RedisTemplate => lettuce";
log.info("Redis没有查询到,存入:"+value);
valueOps.set(key,value);
}
return value;
}
}
一个报错JsonParseException
如果Redis的键值对为:
取值时会报错:Json解析错误
Caused by: com.fasterxml.jackson.core.JsonParseException:
Unrecognized token 'RedisTemplate': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: (byte[])"RedisTemplate => lettuce"; line: 1, column: 15]
因为我们的配置template.setValueSerializer(jacksonSerializer);
,value序列化方式采用Jackson
而Jackson将字符串转为json会带上双引号,我们取值时没有双引号就会报错
把数据修改一下:带上双引号就ok了
代码
码云