一、安装Redis
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
docker环境下的redis
1、首先去docker的镜像仓库dockerhub寻找一下,然后docker pull 一个redis下来
[root@ecs-s6-medium-2-linux-20190906163518 /]# docker pull redis
Using default tag: latest
Trying to pull repository docker.io/library/redis ...
latest: Pulling from docker.io/library/redis
b8f262c62ec6: Pull complete
93789b5343a5: Pull complete
49cdbb315637: Pull complete
ea0579387266: Pull complete
6b8ecda334de: Pull complete
ac5a9c26d32a: Pull complete
Digest: sha256:cb379e1a076fcd3d3f09e10d7b47ca631fb98fb33149ab559fa02c1b11436345
Status: Downloaded newer image for docker.io/redis:latest
[root@ecs-s6-medium-2-linux-20190906163518 /]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/redis latest 01a52b3b5cd1 12 days ago 98.2 MB
docker.io/mysql 5.7 383867b75fd2 3 weeks ago 373 MB
[root@ecs-s6-medium-2-linux-20190906163518 /]# docker run -d -p 6379:6379 --name myredis docker.io/redis
1de77d13d36b7f0d08137f039450b57d295afc7359564b2eb9d6e64bb53db646
[root@ecs-s6-medium-2-linux-20190906163518 /]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1de77d13d36b docker.io/redis "docker-entrypoint..." 12 seconds ago Up 11 seconds 0.0.0.0:6379->6379/tcp myredis
1eed21092c63 mysql:5.7 "docker-entrypoint..." 3 weeks ago Up 3 weeks mysql57
2、利用RedisDesktopManager来验证我们的redis是否已经成功
进行一个简单的测试
可以看到Redis已经可以开始使用了,Redis具体的命令可以参考REDIS的官方文档
二、添加Redis的starter
添加pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
添加yml配置
spring:
redis:
host: 119.3.223.115
全部添加完成后可以看到RedisAutoConfiguration 已经可以使用了
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public 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;
}
}
从注解开始看看:
@Configuration:表明这是一个配置类
@ConditionalOnClass(RedisOperations.class) :含有这个RedisOperations类才开始redis自动配置,RedisOperations是spring-data-redis jar包中的,在引入pring-boot-starter-data-redis时就引入了。
@EnableConfigurationProperties(RedisProperties.class):开启属性的自动注入
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }):导入这两个配置文件,这两个配置文件就是根据你导入的jar包来判断是用Lettuce还是Jedis来作为redis驱动的。
三、使用Redis
通过opsForXXX的形式。只要是redis中支持的命令基本都可以点出来。
Redis的五种基本类型
string(字符串),list(列表),set(集合),hash(哈希),zset(有序集合)
stringRedisTemplate.opsForValue();
stringRedisTemplate.opsForHash();
stringRedisTemplate.opsForList();
stringRedisTemplate.opsForSet();
stringRedisTemplate.opsForZSet();
@Test
public void test01(){
//gei redis 中保存数据
stringRedisTemplate.opsForValue().append("message","hello");
}
通常情况下我们在保存对象数据的时候。
(1)通常我们把对象转换为json去存储
(2)或者采用RedisTemplate的序列化器JdkSerializationRedisSerializer
我们可以自己写一个Serializer来实现json的转化
@Configuration
public class MyRedisConfig {
@Bean
@ConditionalOnMissingBean(
name = {"queRedisTemplate"}
)
public RedisTemplate<Object, List<QuestionnaireDetail>> queRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, List<QuestionnaireDetail>> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<QuestionnaireDetail> ser = new Jackson2JsonRedisSerializer<QuestionnaireDetail>(QuestionnaireDetail.class);
template.setDefaultSerializer(ser);
return template;
}
@Bean
@ConditionalOnMissingBean(
name = {"jsonRedisTemplate"}
)
public RedisTemplate<Object, Object> jsonRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Object> ser = new Jackson2JsonRedisSerializer<Object>(Object.class);
template.setDefaultSerializer(ser);
return template;
}
}
(1)queRedisTemplate:为序列化一个单独的对象。
(2)jsonRedisTemplate:为一个泛型的序列化。
都通过@Bean来注入
@Autowired
StringRedisTemplate stringRedisTemplate; //操作字符串
@Autowired
RedisTemplate<Object,Object> redisTemplate;//操作KV对象
@Autowired
RedisTemplate<Object, List<QuestionnaireDetail>> queRedisTemplate;//自定义独立的序列化为json的方法
@Autowired
RedisTemplate<Object,Object> jsonRedisTemplate;//自定义泛型的序列化为json的方法
@Resource
private QuestionDetailService questionDetailService;
/**
* Redis的五种基本类型
* string(字符串),list(列表),set(集合),hash(哈希),zset(有序集合)
* stringRedisTemplate.opsForValue();
* stringRedisTemplate.opsForHash();
* stringRedisTemplate.opsForList();
* stringRedisTemplate.opsForSet();
* stringRedisTemplate.opsForZSet();
* */
@Test
public void test01(){
List<QuestionnaireDetail> questionnaireDetailList=questionDetailService.getQuestionnaireDetailList((long) 1);
//gei redis 中保存数据 ("red1",questionnaireDetailList);
stringRedisTemplate.delete("message");
stringRedisTemplate.opsForValue().append("message","hello");
//测试
queRedisTemplate.delete("okq");
queRedisTemplate.opsForValue().set("okq",questionnaireDetailList);
//自带的插入对象通常情况下是乱码
redisTemplate.opsForValue().set("red1",questionnaireDetailList);
//自定义泛型的序列化通过Jackson2JsonRedisSerializer来实现序列化为Json
jsonRedisTemplate.opsForValue().set("redJson",questionnaireDetailList);
}
编写一个测试类。来实现调用
查看一下效果,完成了数据存储的json化。
四、自定义RedisCacheManage
首先我们要知道springboot2.0和1.0相比有了很多变化。其中redis的实现从Jedis变成了Lettuce
Jedis 是直连模式,在多个线程间共享一个 Jedis 实例时是线程不安全的,如果想要在多线程环境下使用 Jedis,需要使用连接池,
每个线程都去拿自己的 Jedis 实例,当连接数量增多时,物理连接成本就较高了。
Lettuce的连接是基于Netty的,连接实例可以在多个线程间共享,
所以,一个多线程的应用可以使用同一个连接实例,而不用担心并发线程的数量。当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。通过异步的方式可以让我们更好的利用系统资源,而不用浪费线程等待网络或磁盘I/O。
只在基于Lettuce的配置中,加入了RedisLockRegistry对应bean的配置,由于在集群的模式下,基于Jedis的配置下,通过RedisLockRegistry
获取分布式锁的时候报错:
EvalSha is not supported in cluster environment
具体的解决方案就是切换至基于Lettuce的配置,请参考
https://stackoverflow.com/questions/47092475/spring-boot-redistemplate-execute-sc
当我们引入了redis的starter时。容器中帮我们匹配了RedisCacheConfiguration
我们重写一个缓存管理器cacheManagerNew
/**
* 缓存管理器
* */
@Bean
public CacheManager cacheManagerNew(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<String> redisSerializerKey = new StringRedisSerializer();
//解决查询缓存转换异常的问题
Jackson2JsonRedisSerializer<Object> redisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
redisSerializer.setObjectMapper(objectMapper);
//自定义一个SerializationPair使用Jackson2JsonRedisSerializer
RedisSerializationContext.SerializationPair<Object> serializationPair = RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer);
//RedisCacheConfiguration的一些配置
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.disableCachingNullValues() //禁止存入空值
.prefixKeysWith("( ͡° ᴥ ͡°)") //给新增一个固定的前缀
.computePrefixWith(cacheName -> "caching:" + cacheName)//给新增一个自定义的前缀
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializerKey))//设置缓存的Key序列化为string
.serializeValuesWith(serializationPair) //设置缓存的value序列化为json
.entryTtl(Duration.ofHours(1)); // 设置缓存有效期一小时
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}
里边有很多的配置,在此处例举一些
.entryTtl(Duration.ofDays(1)) //Duration.ZERO表示永不过期(此值一般建议必须设置)
.disableKeyPrefix() // 禁用key的前缀
.disableCachingNullValues() //禁止缓存null值
前缀我个人觉得是非常重要的,建议约定:注解缓存一个统一前缀、RedisTemplate直接操作的> 缓存一个统一前缀
.prefixKeysWith(“baidu:”) // 底层其实调用的还是computePrefixWith() 方法,只是它的前缀是固定的(默认前缀是cacheName,此方法是把它固定住,一般不建议使用固定的)
.computePrefixWith(CacheKeyPrefix.simple()); // 使用内置的实现
.computePrefixWith(cacheName -> “caching:” + cacheName) // 自己实现,建议这么使用(cacheName也保留下来了)
RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory())
.disableCreateOnMissingCache() // 关闭动态创建Cache
.initialCacheNames() // 初始化时候就放进去的cacheNames(若关闭了动态创建,这个就是必须的)
.cacheDefaults(configuration) // 默认配置(强烈建议配置上)。 比如动态创建出来的都会走此默认配置
.withInitialCacheConfigurations() // 个性化配置 可以提供一个Map,针对每个Cache都进行个性化的配置(否则是默认配置)
.transactionAware() // 支持事务
在需要缓存的地方
@Cacheable(cacheNames = "userQ",cacheManager = "cacheManagerNew")
运行后可以看到,效果已经实现。通常情况下:
.computePrefixWith(cacheName -> “caching:” + cacheName)//给新增一个自定义的前缀
.prefixKeysWith("( ͡° ᴥ ͡°)") //给新增一个固定的前缀
都存在的时候,后面的会替代前面的设置。
五、回顾
@EnableCaching // 使用了CacheManager
首先别忘了开启它 否则无效
@Cacheable 触发缓存填充
/**
* @Cacheable:将方法的运行结果进行保存,以后再要相同的数据,直接从缓存中获取,不用调用方法
* CacheManager管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每个缓存组件有自己唯一的名字
* 属性:(缓存数据时使用key-value的形式)
* cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存
* key:缓存数据使用的key;可以用她来指定。默认是使用方法参数的值 键值对:1-方法的返回值
* 参数id的值:#id相当于#root.args[0]
* keyGenerator:key的生成器;可以自己指定key的生成器组件id
* ——key和keyGenerator不可同时使用
* cacheManager:指定缓存管理器(从哪个缓存管理器中取);cacheResolver:指定缓存解析器
* condition:指定符合条件的情况下才缓存;condition="#id>0":id大于0的时候才缓存
* unless:否定缓存;当unless指定的true,方法的返回值就不会被缓存;可以获取到结果进行判断
* unless="#result == null":结果为空时不缓存
* sync:缓存是否使用异步模式
* /
@CacheEvict 触发缓存驱逐
/**
*@CacheEvict缓存清除
* key:指定要清楚的数据
* allEntries=true:指定清除这个缓存中的所有数据
* beforeInvocation = false;缓存的清除是否在方法之前执行;false:在方法执行之后清除
*/
@CachePut 更新缓存而不会干扰方法执行
/**
* @CachePut:既调用方法,又更新缓存数据;
* 修改了数据库的某个数据,同时更新缓存
* 运行过程:
* 1、先调用目标方法
* 2、将目标方法的结果缓存起来
* 更新后重新查询出的数据是更新前的数据:
* ——(key默认使用方法参数的值)查询是的缓存是@Cacheable缓存的值,key是1;@CachePut更新后缓存的值:key是传入的employee对象
* ——所以要统一key:key="#employee.id";key="#result.id";
*/
@Caching 重新组合要在方法上应用的多个缓存操作
用于处理复杂的缓存情况。比如用户既要根据id缓存一份,也要根据电话缓存一份,还要根据电子邮箱缓存一份,就可以使用它
@CacheConfig 在类级别共享一些常见的缓存相关设置
可以在类级别上标注一些共用的缓存属性