缓存:使数据更接近使用者,加快访问速度
方式:先从缓存中获取,没有则加入缓存
适合数据:
经常读取的数据,频繁访问,热点数据,io瓶颈数据,计算贵的数据、结果相同的
5分钟法则 局部性原理数据
缓存命中率
Miss率
回收策略:
基于空间 基于容量 基于时间 TTL(存活时间) TTI(空闲期) 基于引用
回收算法FIFO LRU lfu
实际应用LRU(guaua ecache )
场景:应用CPU3级缓存 Maven加载依赖 京东物流
Spring3.1 事物中的缓存抽象
在4.1中被加入Jsr107,支持定制
最后这种抽象被Cache和CacheManger接口具体化。
重要接口Cache和CacheManger
再看看cache提供的方法
//Spring中Cache接口
package org.springframework.cache;
import java.util.concurrent.Callable;
public interface Cache {
String getName();//缓存的名称
Object getNativeCache();//底层使用缓存名称
Cache.ValueWrapper get(Object var1);//根据key得到一个ValueWrapper,然后调用其get方法获取值
T get(Object var1, Class var2);//根据key,和value的类型直接获取value
T get(Object var1, Callable var2);
void put(Object var1, Object var2);//往缓存放数据
Cache.ValueWrapper putIfAbsent(Object var1, Object var2);
void evict(Object var1);//从缓存中移除key对应的缓存
void clear(); //清空缓存
public static class ValueRetrievalException extends RuntimeException {
private final Object key;
public ValueRetrievalException(Object key, Callable> loader, Throwable ex) {
super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);
this.key = key;
}
public Object getKey() {
return this.key;
}
}
public interface ValueWrapper {//缓存值的Wrapper
Object get();
}
}
因为应用是多Cache所以使用CacheManager进行管理
public interface CacheManager {
Cache getCache(String var1);//根据cache名称获取Cache
Collection getCacheNames();//获取所有cache名称
}
注解位置:context包的org.springframework.cache.annotation
注解名 | 含义 | 备注 | 说明 |
@Cacheable | 即可缓存的方法,如查找方法:先从缓存中读取,如果没有再调用方法获取数据,然后把数据添加到缓存中 | ||
@CacheEivct | 应用到移除数据的方法上,如删除方法,调用方法时会从缓存中移除相应的数据 | ||
@CachePut | 如新增/修改方法,调用方法时会自动把相应的数据放入缓存: | ||
@Caching | 可以设置put evict等 | ||
@CacheConfig | 可以设置cacheNames keyGenerator cacheManger cacheResolver等属性。 |
public @interface CachePut {
String[] value();
//缓存的名字,可以把数据写到多个缓存
String key() default "";
//缓存key,如果不指定将使用默认的KeyGenerator生成,后边介绍
String condition() default "";
//满足缓存条件的数据才会放入缓存,condition在调用方法之前和之后都会判断
String unless() default "";
//用于否决缓存更新的,不像condition,该表达只在方法执行之后判断,
//此时可以拿到返回值result进行判断了
}
public @interface CacheEvict {
String[] value(); //请参考@CachePut
String key() default ""; //请参考@CachePut
String condition() default ""; //请参考@CachePut
boolean allEntries() default false; //是否移除所有数据
boolean beforeInvocation() default false;//是调用方法之前移除/还是调用之后移除
}
public @interface Cacheable {
String[] value(); //请参考@CachePut
String key() default ""; //请参考@CachePut
String condition() default "";//请参考@CachePut
String unless() default ""; //请参考@CachePut
}
运行流程
1、首先执行@CacheEvict(如果beforeInvocation=true且condition 通过),如果allEntries=true,则清空所有
2、接着收集@Cacheable(如果condition 通过,且key对应的数据不在缓存),放入cachePutRequests(也就是说如果cachePutRequests为空,则数据在缓存中)
3、如果cachePutRequests为空且没有@CachePut操作,那么将查找@Cacheable的缓存,否则result=缓存数据(也就是说只要当没有cache put请求时才会查找缓存)
4、如果没有找到缓存,那么调用实际的API,把结果放入result
5、如果有@CachePut操作(如果condition 通过),那么放入cachePutRequests
6、执行cachePutRequests,将数据写入缓存(unless为空或者unless解析结果为false);
7、执行@CacheEvict(如果beforeInvocation=false 且 condition 通过),如果allEntries=true,则清空所有
如果有@CachePut操作,即使有@Cacheable也不会从缓存中读取;问题很明显,如果要混合多个注解使用,不能组合使用@CachePut和@Cacheable;官方说应该避免这样使用(解释是如果带条件的注解相互排除的场景);不过个人感觉还是不要考虑这个好,让用户来决定如何使用,否则一会介绍的场景不能满足。
有时候我们可能组合多个Cache注解使用;比如用户新增成功后,我们要添加id-->user;username--->user;email--->user的缓存;此时就需要@Caching组合多个注解标签了。
public @interface Caching {
Cacheable[] cacheable() default {}; //声明多个@Cacheable
CachePut[] put() default {}; //声明多个@CachePut
CacheEvict[] evict() default {}; //声明多个@CacheEvict
}
可以设置spel
然后是几个抽象类
concurrent包
interceptor包
configer包
suport包
基于注解的配置使用继承于CachingConfigurerSupport
public class CachingConfigurerSupport implements CachingConfigurer {
public CachingConfigurerSupport() {
}
@Nullable
public CacheManager cacheManager() {
return null;
}//默认实现
@Nullable
public CacheResolver cacheResolver() {
return null;
}
@Nullable
public KeyGenerator keyGenerator() {
return null;
}
@Nullable
public CacheErrorHandler errorHandler() {
return null;
}
}
1、使用@EnableCaching启用Cache注解支持;
2、实现CachingConfigurer,然后注入需要的cacheManager和keyGenerator;从spring4开始默认的keyGenerator是SimpleKeyGenerator; 如果注解上没有指定key的化通过keyGenerator指定
默认的实现方式
@Override
public Object generate(Object target, Method method, Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
}
if (params.length == 1 && params[0] != null) {
return params[0];
}
return new SimpleKey(params);
}
常见几种缓存的用法
enhance
实现基于
ConcurrentHashMap实现的ConcurrentMapCache
Ehcache 2.x EhCacheCache
Gemfire cache
Caffeine
Guava caches 实现方式Guava com.google.common.cache.Cache进行的Wrapper
JSR-107 compliant caches (e.g. Ehcache 3.x) 对javax.cache.Cache进行的wrapper
ConcurrentMapCacheManager/ConcurrentMapCacheFactoryBean:管理ConcurrentMapCache;
GuavaCacheManager;
EhCacheCacheManager/EhCacheManagerFactoryBean;
JCacheCacheManager/JCacheManagerFactoryBean;
另外还提供了CompositeCacheManager用于组合CacheManager,即可以从多个CacheManager中轮询得到相应的Cache
除了GuavaCacheManager之外,其他Cache都支持Spring事务的,即如果事务回滚了,Cache的数据也会移除掉。
Spring不进行Cache的缓存策略的维护,这些都是由底层Cache自己实现,Spring只是提供了一个Wrapper,提供一套对外一致的API。
缓存 没有对多线程特殊处理
如果在SpringBoot没有定义一个CacheManger类型的Bean,或者一个cacheResolver(cacheconfig)
Genric
Jcache(ehcache3)
ehcache2
hazelcast
infinispan
couchbase
redis
Caffeine
guaua
simple
可以强制指定属性
spring.cache.type指定缓存供应商
CacheMangerCustomizer 自定义配置
如果有一个定义好的cache bean 一个cachemanger包装着它,那么采用通用缓存。
如果在classpath下找到ehcache.xml则缓存使用ehcache2.x
hazelcast也需要指定配置文件
需要显示指定infinispan配置文件
CoucheBase配置
Redis可用并且配置好,将会被自动配置
可以使用spring.cache.cache-names改为启动时创建其他缓存
Caffeine
java8对guaua的重写 SpringBoot2.0取代guaua,如果出现caffeine自动化配置
Guaua
Simple
如果上面选项都没有被实现,将会配置一个使用ConcurrentHashMap作为缓存存储的简单实例被配置,这是缓存没有添加第三方lib的默认配置。
关于缓存的强制禁用spring.cache.type=none。针对某些特定的环境。
优点快 不存在序列化 缺点 空间有限 引发gc 存热点数据
本地(进程内的)缓存
enhance
guaua cache 不需要太多的配置文件
和堆相反
优点 持久化
缺点一致性困难 设置更新策略 有 encache redis集群
分布式缓存
redis
memcahe
关于代码实现后期跟新
多级缓存
缓存使用模式Aside AsSoR 单独写吧
性能测试
一般cache的基础功能
提供存放读取 容器大小有限设置清除策略
一般是线程安全 单例
```
private static Cache
```
```
final static Cache
//设置cache的初始大小为10,要合理设置该值
.initialCapacity(10)
//设置并发数为5,即同一时间最多只能有5个线程往cache执行写入操作
.concurrencyLevel(5)
//设置cache中的数据在写入之后的存活时间为10秒
.expireAfterWrite(10, TimeUnit.SECONDS)
//构建cache实例
.build();
```
GuavaCache的实现是基于ConcurrentHashMap的。
guauacache的几种清除策略
expireAfterAccess(long, TimeUnit)
缓存项在创建后,在给定时间内没有被读/写访问,则清除。
expireAfterWrite(long, TimeUnit)
缓存项在创建后,在给定时间内没有被写访问(创建或覆盖),则清除。
sizebased evication
通过CacheBuilder.maximumSize(long)方法可以设置Cache的最大容量数,当缓存数量达到或接近该最大值时,Cache将清除掉那些最近最少使用的缓存。
你可以使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。
(1)个别清除:Cache.invalidate(key)
(2)批量清除:Cache.invalidateAll(keys)
(3)清除所有缓存项:Cache.invalidateAll()
在构建Cache实例过程中,通过设置使用弱引用的键、或弱引用的值、或软引用的值,从而使JVM在GC时顺带实现缓存的清除,不过一般不轻易使用这个特性。
(1)CacheBuilder.weakKeys():使用弱引用存储键
(2)CacheBuilder.weakValues():使用弱引用存储值
(3)CacheBuilder.softValues():使用软引用存储值
cache延迟删除
guaguacache实现中没有启动任何线程 都是通过调用其他线程来完成。
面向本地轻量级缓存
关于缓存和buffer的了解
Obviously this approach works only for methods that are guaranteed to return the same output (result) for a given input (or arguments) no matter how many times it is being executed.
Spring提供缓存的缺陷
也就是说Spring Cache注解还不是很完美,我认为可以这样设计:
@Cacheable(
cacheName = "缓存名称",
key="缓存key/SpEL",
value="缓存值/SpEL/不填默认返回值",
beforeCondition="方法执行之前的条件/SpEL",
afterCondition="方法执行后的条件/SpEL",
afterCache="缓存之后执行的逻辑/SpEL")
value也是一个SpEL,这样可以定制要缓存的数据;afterCache定制自己的缓存成功后的其他逻辑。
当然Spring Cache注解对于大多数场景够用了,如果场景复杂还是考虑使用AOP吧;
如果自己实现请考虑使用Spring Cache API进行缓存抽象。
评论
例如有一个缓存存放 list
这个在现有的抽象上没有很好的方案,可以考虑通过condition在之前的Helper方法中解决;当然不是很优雅。
个人认为,往往查询都会伴随着排序或者分页的时候,还是很难解决这个缺陷的
Cache中所设计的类
AbstractCacheManager
AbstractTransactionSupportingCacheManager 装饰
TransactionAwareCacheDecorator 拥有一个是否同步支持 异步需要进行注册
使用concurentHashMap存在cache名和cache
loadCaches 加载 通过配置文件entrySet
RedisCache的创建
使用了静态builder类
初始化 加载缓存列表 组装map decoreteCache
RedisCacheManager 拥有cacheWriter/cacheConfig/initCacheConfig/
allowInFlightCacheCreation
参考:
原文档的英文链接为
https://docs.spring.io/spring/docs/4.3.3.RELEASE/spring-framework-reference/htmlsingle/#cache