前言
1、查了许多资料,整合成一个通用的RedisTemplate。
2、推荐Redis可视化工具 Another Redis DeskTop Manager,免费好用,人机交互性能好。
配置
当前版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
引入依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
引入依赖说明
- 引入commons-pools2的目的,是因为spring-boot-data-redis中使用的是lettuce,取代了旧的Jedis。spring-boot-data-redis既然取代了Jedis,那就侧面说明这个luttuce性能更好。不过为了保证稳定性、可靠性,需要引入commons-pools2(含有Jedis)。
- 使用fastjson,常用的JSON转换器。
- 引入spring-session-data-redis,是因为有个小Bug,在查看了源码之后,还未发现能替换这个依赖库的原生的redisTemplate,后面会有演示。
编辑application.yml
spring:
redis:
host: 127.0.0.1
port: 6379
timeout: 3000
password: 123456
lettuce:
pool:
max-wait: 16
max-active: 16
max-idle: 16
min-idle: 1
shutdown-timeout: 10000ms # 关闭超时时间
database: 0
配置Redis配置类
配置类的注解
@Configuration
@EnableCaching
public class RedisConfig
配置类从application.yml中引入配置元数据
public class RedisConfig {
// 倘若 spring.redis.host 不存在,则会默认为127.0.0.1.
@Value("${spring.redis.host:#{'127.0.0.1'}}")
private String hostName;
@Value("${spring.redis.port:#{6379}}")
private int port;
@Value("${spring.redis.password:#{123456}}")
private String password;
@Value("${spring.redis.timeout:#{3000}}")
private int timeout;
@Value("${spring.redis.lettuce.pool.max-idle:#{16}}")
private int maxIdle;
@Value("${spring.redis.lettuce.pool.min-idle:#{1}}")
private int minIdle;
@Value("${spring.redis.lettuce.pool.max-wait:#{16}}")
private long maxWaitMillis;
@Value("${spring.redis.lettuce.pool.max-active:#{16}}")
private int maxActive;
@Value("${spring.redis.database:#{0}}")
private int databaseId;
// 其它配置
.....
}
配置LettuceConnectionFactory的连接池
@Bean
public LettuceConnectionFactory lettuceConnectionFactory() {
RedisConfiguration redisConfiguration = new RedisStandaloneConfiguration(
hostName, port
);
// 设置选用的数据库号码
((RedisStandaloneConfiguration) redisConfiguration).setDatabase(databaseId);
// 设置 redis 数据库密码
((RedisStandaloneConfiguration) redisConfiguration).setPassword(password);
// 连接池配置
GenericObjectPoolConfig<Object> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMinIdle(minIdle);
poolConfig.setMaxTotal(maxActive);
poolConfig.setMaxWaitMillis(maxWaitMillis);
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder
= LettucePoolingClientConfiguration.builder()
.commandTimeout(Duration.ofMillis(timeout));
LettucePoolingClientConfiguration lettucePoolingClientConfiguration = builder.build();
builder.poolConfig(poolConfig);
// 根据配置和客户端配置创建连接
LettuceConnectionFactory factory = new LettuceConnectionFactory(redisConfiguration, lettucePoolingClientConfiguration);
return factory;
}
说明:
- redis其实单线程性能更好,也不需要配置线程数之类的。当然,最近的版本都有支持多线程,就适当的配上线程数。
- 此处可以设置不同的databaseId,来达到应对不同database的操作目的。
配置KeyGenerator
@Bean
public KeyGenerator wiselyKeyGenerator() {
return (target, method, params) -> {
StringBuilder builder = new StringBuilder();
builder.append(target.getClass().getName());
builder.append(method.getName());
for (Object obj : params) {
builder.append(obj.toString());
}
return builder.toString();
};
}
这是一个通用的keyGenerator,可以解决分布式的命名问题。
配置RedisTemplate<String, Object>
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(
LettuceConnectionFactory lettuceConnectionFactory
) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
// 使用 FastJsonRedisSerializer 来序列化和反序列化redis 的 value的值
FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<>(Object.class);
ParserConfig.getGlobalInstance().addAccept("com.muzz");
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setCharset(StandardCharsets.UTF_8);
serializer.setFastJsonConfig(fastJsonConfig);
// key 的 String 序列化采用 StringRedisSerializer
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// value 的值序列化采用 fastJsonRedisSerializer
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
System.out.println(redisTemplate.getDefaultSerializer());
return redisTemplate;
}
有几个地方要注意
- 使用的lettuceConnectionFactory是由上面的lettuceConnectionFactory配置的。
- StringRedisSerializer更加的健壮,他是继承了RedisSerializer这个接口,并且指定的是String类型。
- 此处使用的是阿里巴巴的FastJsonRedisSerializer。在传入对象时,可以将对象转换成JSON格式,并以字符串的形式传入redis缓存中。在设计对象的时候,可以考虑使用 transient 来反序列化。
此时配置完成,下面配置选择使用
@Bean
// @ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
LettuceConnectionFactory lettuceConnectionFactory
) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(lettuceConnectionFactory);
return stringRedisTemplate;
}
这是更严格的 redisTemplate,网络上大佬写的。
@Bean
public RedisTemplate<String, Serializable> limitRedisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Serializable> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
数据测试
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
在测试之前,先说明一下,使用上面的依赖,会有小Bug出现,貌似是使用JDK的序列器,等会演示。
@CrossOrigin
@PostMapping(value = "login", consumes = {"application/json"})
public String sysUserLogin(
HttpServletRequest request,
HttpServletResponse response,
@RequestBody UserLoginReq req
) {
// 此处调用了 spring-session-data-redis 里面自带的 redisTemplate 进行操作
System.out.println(request.getSession().getId());
System.out.println(redisTemplate.keys("*"));
// 此处是注入了上面已经配置好的 redisTemplate,需要注意区分
redisTemplate.opsForHash().put("这是一个测试key", "这是一个测试hashkey", "这个一个测试value");
redisTemplate.opsForHash().put("这是另一个测试key", "这是另一个测试hashkey", req);
// Result result = sysUserLoginService.login(req);
return Result.newSuccessResult(req).toString();
}
request.getSession().getId()的结果:
spring-session-data-redis在获取了session( 调用getSession )后,会自动写入到redis里面。
测试问题
spring-session-data-redis写入的数据会出现十六进制的乱码问题
开头都是\xac\xed\x00\x05sr\x00\x0e,这是一个不应该出现乱码的问题,这会在redis中占据一定的存储空间,这是不能忍的。
而对于刚刚自定义的RedisTemplate<String, Object>来说不会出现这个问题。
value是字符串类型。
value是Object类型,Object被转换成JSON格式的字符串。
同样的Hash数据结构存储,结果却不大相同。
可以暂时推断,spring-session-data-redis是使用了默认的存储序列,这是需要注意的大坑。
翻看源码
其实可以看到,spring-session-data-redis是默认使用JdkSerializationRedisSerializer的。
后续解决办法
解决办法
https://blog.youkuaiyun.com/weixin_46828364/article/details/110821604