一. 应用
1. pom.xml
Spring Boot提供了redis相关启动器,用于导入redis访问依赖,具体如下:
<!-- redis启动器
我这边由Spring Boot启动器依赖的spring-boot-starter-data-redis的版本是1.5.20.RELEASE,但这个jar仅仅是为了将redis与Spring Boot整合,
真正发挥redis作用的是spring-data-redis,我这边的版本是1.8.20.RELEASE
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 在访问redis时,必须依赖jackson 原因在于java最擅长处理对象,而Redis中存储的是json字符串,二者之间需要转换,
Spring底层针对java对象和json字符串的转换用到的就是jackson-databind,所以必须依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
2. application.properties
# redis连接配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
#redis数据库共有16个, 编号从0至15 (默认选择0号数据库)
spring.redis.database=0
#获取连接的超时等待时长 (0代表不等待)
spring.redis.timeout=0
#redis连接池配置
# 一个JedisPool可以分配多少个jedis实例。 设置为0代表无限制
spring.redis.pool.max-active=8
#连接池分配连接的最大等待时间 (单位:毫秒) 若值为-1,则代表无线等待,直到被分配到一个连接为止
spring.redis.pool.max-wait=-1
#最大空闲连接数
spring.redis.pool.max-idle=18
#最小空闲连接数
spring.redis.pool.min-idle=10
3. dao
@Repository
public class RedisDaoImpl implements RedisDao {
@Autowired
private RedisTemplate<String ,Object> redisTemplate;
@Override
public void set(String key, String value) {
this.redisTemplate.opsForValue().set(key, value);
}
@Override
public void set(String key, Object value) {
this.redisTemplate.opsForValue().set(key, value);
}
@Override
public Object get(String key) {
return this.redisTemplate.opsForValue().get(key);
}
@Override
public long ttl(String key) {
return this.redisTemplate.getExpire(key);
}
@Override
public void expire(String key, long times, TimeUnit timeUnit) {
this.redisTemplate.expire(key, times, timeUnit);
}
}
注意: 如果此时直接拿dao来使用,会报如下错误:
No Beans of 'RedisTemplate<String,Object>' type found.
Java程序员一般都喜欢直接操纵对象,因此value值希望是Object类型。但很可惜,Spring Boot自身提供的RedisTemplate中并没有RedisTemplate<String, Object>类型。
在Spring Boot环境中,会默认创建两个RedisTemplate类型的对象,分别是:
1. RedisTemplate<Object, Object>
2. StringRedisTemplate<String, String>
它们由RedisAutoConfiguration中的静态内部类RedisConfiguration中的两个方法执行而来。重点代码如下:
/**
* Standard Redis configuration.
*/
@Configuration
protected static class RedisConfiguration {
@Bean
//ConditionalOnMissingBean 当没有名称为redisTemplate的对象被创建时,
//才去创建redisTemplate对象
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
//StringRedisTemplate继承自RedisTemplate<String, String>
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
此外,如果直接使用SpringBoot提供的默认RedisTemplate实现,会产生错误的结果:key数据是前缀为二进制编码后缀为toString输出的字符串,value数据则无法录入Redis当中。这是因为SpringBoot默认提供的RedisTemplate没有实现KeySerializer和ValueSerializer接口,用的是自己实现的一套序列化逻辑。解决方案: 通过Configuration来自定义初始化一套自己需要的RedisTemplate对象, 并指定对应的key和value序列化器即可。
4. Configuration
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class MyRedisConfiguration {
/**
* RedisConnectionFactory就是我们在配置文件中定义属性的对象
*/
@Bean
public RedisTemplate<String, Object> initialRedisTemplate(RedisConnectionFactory factory){
//创建RedisTemplate对象
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//设置连接工厂
redisTemplate.setConnectionFactory(factory);
//设置key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置value的序列化器
//GenericJackson2JsonRedisSerializer()的好处在于不需要像Jackson2JsonRedisSerializer<>那样必须定义泛型,这样就可以适配更多类型的数据了。
//在序列化的对象时,GenericJackson2JsonRedisSerializer会将对象的完整类型名称作为新的属性存放,并在反序列化的时候根据该完整类型名称将json字符串转换成指定对象。
//比如a.b.User类型的java对象,有属性name和age
//序列化 -> {"@class": "a.b.User", "name": "xx", "age": 25}
//反序列化 -> 读取json中的class属性,决定转换为什么类型的对象
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return null;
}
}
5. 集群版
集群版Redis在引入依赖,RedisTemplate的操作,@Configuration自定义配置上与单机版Redis完全一致,唯一的不同之处在于配置文件application.properties
#集群版Redis中不考虑database的问题
spring.redis.cluster.nodes=192.168.0.100:7000,192.168.0.100:7001,192.168.0.100:7002
#获取连接的超时等待时长 (0代表不等待)
spring.redis.timeout=0
#redis连接池配置
# 一个JedisPool可以分配多少个jedis实例。 设置为0代表无限制
spring.redis.pool.max-active=8
#连接池分配连接的最大等待时间 (单位:毫秒) 若值为-1,则代表无线等待,直到被分配到一个连接为止
spring.redis.pool.max-wait=-1
#最大空闲连接数
spring.redis.pool.max-idle=18
#最小空闲连接数
spring.redis.pool.min-idle=10
二. 问题
1. Redis中保存中文时,显示"乱码"。
答: 如果向Redis中录入中文,那么直接在redis-cli控制台将无法查看到可识别的中文内容,而是一串遵循Unicode字符集序列化的数据。这是因为Redis的底层存储的是字节数组。如果录入中文时使用的是UTF-8编码规则,那么Redis中存储的就是Unicode字符集和UTF-8编码规则编码的数据。如果录入中文使用的是GBK编码规则,那么Redis中存储的就是GBK编码规则编码的字节数组。Jedis在读取Redis中保存的中文数据时,会有自动编解码的能力,保证字符串的正常显示,前提是编解码的字符集要统一。
2. 集群版本是否支持事务?
答: 不支持。
3. 集群版本是否支持分区?
答:不支持分区,因为集群版本中只能使用(也只有)0号数据库,并且在配置文件中也没有针对database属性进行配置。因此在 redis-cli中不支持使用select命令选择分区。
4. Redis是否支持高并发?
答: Redis是单进程的,它的底层通过单线程做入口,用多路复用器和多线程实现高并发处理(和AIO、Netty原理非常类似,单线程通过观察者模式监听外来的请求,将对应的读写操作写入多路复用器注册表中,接着由多路复用器去找线程池分配的线程来完成后续的业务逻辑处理)
5. Redis是否用到了协程?
答: 没有用到。协程与线程的关系就像线程与进程的关系。但Redis只是单线程借助多路复用器和多线程实现的。
6. Redis能否保证并发数据安全?
答: 能。Redis是单线程,集中式调度的。由单线程统一接收请求,注册到多路复用器并分发请求,因此能够保证前后的逻辑顺序一致。
7. Redis是否需要考虑单点问题?
答: 不需要。集群中每一台Redis服务器都是平衡的,访问集群而不是在访问某一台Redis服务器。