Spring Cache的基本使用与分析
概述
使用 Spring Cache 可以极大的简化我们对数据的缓存,并且它封装了多种缓存,本文基于 redis 来说明。

基本使用
1、所需依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2、配置文件
spring:
# redis连接信息
redis:
host: 192.168.56.10
port: 6379
cache:
# 指定使用的缓存类型
type: redis
# 过期时间
redis:
time-to-live: 3600000
# 是否开启前缀,默认为true
use-key-prefix: true
# 键的前缀,如果不配置,默认就是缓存名cacheNames
key-prefix: CACHE_
# 是否缓存空置,防止缓存穿透,默认为true
cache-null-values: true
3、Spring Cache 提供的注解如下,使用方法参见:官方文档,通过这些注解,我们可以方便的操作缓存数据。
@Cacheable:触发缓存写入的操作@CacheEvict:触发缓存删除的操作@CachePut:更新缓存,而不会影响方法的执行@Caching:重新组合要应用于一个方法的多个缓存操作,即对一个方法添加多个缓存操作@CacheConfig:在类级别共享一些与缓存有关的常见设置
例如,如果需要对返回结果进行缓存,直接在方法上标注 @Cacheable 注解
@Cacheable(cacheNames = "userList") //指定缓存的名字,便于区分不同缓存
public List<User> getUserList() {
...
}
4、redis 默认使用 jdk 序列化,需要我们配置序列化机制,自定义一个配置类,否则存入的数据显示乱码
@EnableCaching //开启缓存
@Configuration
public class MyCacheConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(){
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//指定键和值的序列化机制
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return config;
}
}
5、使用以上配置后,虽然乱码的问题解决了,但配置文件又不生效了,比如过期时间等,这是因为在初始化时会判断用户是否自定义了配置文件,如果自定义了,原来的就不会生效,源码如下:
private org.springframework.data.redis.cache.RedisCacheConfiguration
determineConfiguration(ClassLoader classLoader) {
//如果配置了,就返回自定义的配置
if (this.redisCacheConfiguration != null) {
return this.redisCacheConfiguration;
}
//没配置使用默认的配置
Redis redisProperties = this.cacheProperties.getRedis();
org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
.defaultCacheConfig();
config = config.serializeValuesWith(
SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
6、所以,我们也需要手动获取 ttl、prefix 等属性,直接仿照源码就行,将配置类修改为如下:
@EnableCaching //开启缓存
@Configuration
@EnableConfigurationProperties(CacheProperties.class) //缓存的所有配置属性都在这个类里
public class MyCacheConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
//获取默认配置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//指定键和值的序列化机制
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
//获取配置文件的配置
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
原理分析
在 Spring 中 CacheManager 负责创建管理 Cache,Cache 负责缓存的读写,因此使用 redis 作为缓存对应的就有 RedisCacheManager 和 RedisCache。
打开 RedisCache 源码,我们需要注意这两个方法:
1、读取数据,未加锁
@Override
protected Object lookup(Object key) {
byte[] value = cacheWriter.get(name, createAndConvertCacheKey(key));
if (value == null) {
return null;
}
return deserializeCacheValue(value);
}
2、读取数据,加锁,这是 RedisCache 中唯一一个同步方法
@Override
public synchronized <T> T get(Object key, Callable<T> valueLoader) {
ValueWrapper result = get(key);
if (result != null) {
return (T) result.get();
}
T value = valueFromLoader(key, valueLoader);
put(key, value);
return value;
}
通过打断点的方式可以知道 RedisCache 默认调用的是 lookup(),因此不能应对缓存穿透,如果有相关需求,可以这样配置:@Cacheable(sync = true),开启同步模式,此配置只在 @Cacheable 中才有。
总结
Spring Cache 对于读模式下缓存失效的解决方案:
- 缓存穿透:
cache-null-values: true,允许写入空值 - 缓存击穿:
@Cacheable(sync = true),加锁 - 缓存雪崩:
time-to-live:xxx,设置不同的过期时间
而对于写模式,Spring Cache 并没有相应处理,我们需要使用其它方式处理。
总的来说:
1、对于常规数据(读多写少,及时性、一致性要求不高的数据)完全可以使用 Spring Cache
2、对于特殊数据(比如要求高一致性)则需要特殊处理
Spring Cache的基本使用与分析的更多相关文章
- Spring cache源码分析
Spring cache是一个缓存API层,封装了对多种缓存的通用操作,可以借助注解方便地为程序添加缓存功能. 常见的注解有@Cacheable.@CachePut.@CacheEvict,有没有想过 ...
- 注释驱动的 Spring cache 缓存介绍
概述 Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使 ...
- [转]注释驱动的 Spring cache 缓存介绍
原文:http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/ 概述 Spring 3.1 引入了激动人心的基于注释(an ...
- Spring Cache 介绍
Spring Cache 缓存是实际工作中非常常用的一种提高性能的方法, 我们会在许多场景下来使用缓存. 本文通过一个简单的例子进行展开,通过对比我们原来的自定义缓存和 spring 的基于注释的 c ...
- 注释驱动的 Spring cache 缓存介绍--转载
概述 Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使 ...
- Spring Cache简单介绍和使用
Spring Cache 缓存是实际工作中非经常常使用的一种提高性能的方法, 我们会在很多场景下来使用缓存. 本文通过一个简单的样例进行展开,通过对照我们原来的自己定义缓存和 spring 的基于凝视 ...
- 【转载】Spring Cache介绍
原文地址:http://www.cnblogs.com/rollenholt/p/4202631.html 缓存是实际工作中非常常用的一种提高性能的方法, 我们会在许多场景下来使用缓存. 本文通过一个 ...
- Spring cache 缓存
概述 Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使 ...
- Java缓存相关memcached、redis、guava、Spring Cache的使用
随笔分类 - Java缓存相关 主要记录memcached.redis.guava.Spring Cache的使用 第十二章 redis-cluster搭建(redis-3.2.5) 摘要: redi ...
随机推荐
- 编码理解的漫漫长路(Unicode、GBK、ISO)
Ø 那么现在开始康康都有哪些编码方式 1. ASCII
- 【题解】POJ3041 Asteroids - 图论 - 二分图匹配
声明:本博客所有题解都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。 POJ3041 Asteroids 题目描述 假如你现在正处在一个 \(N*N\ ...
- SpringMVC Spring Mybatis整合篇
1.创建WEB项目 创建项目:(ssmbuild)步骤略........ 给项目添加lib文件夹,用于存放jar包: 在WEB-INF目录下创建lib文件夹: 创建完成:运行项目时需要把jar导入到l ...
- [Qt] 打开Diskmgmt
QProcess mOpenDiskMgmt; QString program = "cmd"; QStringList arguments; arguments << ...
- Ubuntu 18.04更换apt-get源
使用apt-get安装时,会很慢,更换了国内的源后,就可以解决这个问题了. 1. 备份sources.list文件 sudo cp /etc/apt/sources.list /etc/apt/sou ...
- 解决从dockerhub上下载debian:jessie失败
解决从dockerhub上下载debian:jessie失败 笔者使用docker build 构建镜像出现下面的错误 Step 1/12 : FROM debian:jessie Get https ...
- 【三剑客】awk命令
前言 awk是一种很棒的语言,它适合文本处理和报表生成. 模式扫描和处理.处理文本流. awk不仅仅是Linux系统中的一个命令,而是一种编程语言,可以用来处理数据和生成报告. 处理的数据: 可以是一 ...
- HTML模版组件
1.介绍 BootStrap (最好看的) css js [x] 学习 BootStrap 规则 jQueryUI (美观一般,偏后台.*) css js [x] 学习 jQueryUI 规则 Eas ...
- Clustered和Nonclustered Indexes 各自得特点和区别及长短处
1 簇索引 簇索引对表的物理数据页中的数据按列进行排序然后再重新存储到磁盘上即簇索 引与数据是混为一体的它的叶节点中存储的是实际的数据由于簇索引对表中的数据一 一进行了排序因此用簇索引查找数据很快但由 ...
- 刚听完CSDN总裁蒋涛先生的学术报告
主题: 二十年程序人生和我的人才观 第一次参加所谓的"学术报告", 但感觉更多的是蒋总在跟我们分享他个人的成长经验. 按蒋总的话说, 他已经从2000年开始不碰怎么技术了, 所以个 ...