文章目录
缓存简介
缓存,我的理解是:让数据更接近于使用者;工作机制是:先从缓存中读取数据,如果没有再从慢速设备上读取实际数据(数据也会存入缓存);缓存什么:那些经常读取且不经常修改的数据/那些昂贵(CPU/IO)的且对于相同的请求有相同的计算结果的数据。如CPU–L1/L2–内存–磁盘就是一个典型的例子,CPU需要数据时先从L1/L2中读取,如果没有到内存中找,如果还没有会到磁盘上找。还有如用过Maven的朋友都应该知道,我们找依赖的时候,先从本机仓库找,再从本地服务器仓库找,最后到远程仓库服务器找;还有如京东的物流为什么那么快?他们在各个地都有分仓库,如果该仓库有货物那么送货的速度是非常快的。
缓存命中率
即从缓存中读取数据的次数 与 总读取次数的比率,命中率越高越好:
命中率 = 从缓存中读取次数 / (总读取次数[从缓存中读取次数 + 从慢速设备上读取的次数])
Miss率 = 没有从缓存中读取的次数 / (总读取次数[从缓存中读取次数 + 从慢速设备上读取的次数])
这是一个非常重要的监控指标,如果做缓存一定要监控这个指标来看缓存是否工作良好;
缓存策略
Eviction policy
移除策略,即如果缓存满了,从缓存中移除数据的策略;常见的有LFU、LRU、FIFO:
FIFO(First In First Out):先进先出算法,即先放入缓存的先被移除;
LRU(Least Recently Used):最久未使用算法,使用时间距离现在最久的那个被移除;
LFU(Least Frequently Used):最近最少使用算法,一定时间段内使用次数(频率)最少的那个被移除;
TTL(Time To Live )
存活期,即从缓存中创建时间点开始直到它到期的一个时间段(不管在这个时间段内有没有访问都将过期)
TTI(Time To Idle)
空闲期,即一个数据多久没被访问将从缓存中移除的时间。
Cache
SpringCache是SpringFramework3.1引入的新特性,提供了基于注解的缓存配置方法。它本质上不是一个具体的缓存实现方案(例如EHCache),而是一个对缓存使用的抽象,通过在已有代码中打上几个预定义的注释,就可以实现我们希望达到的缓存效果。SpringCache支持跟第三方缓存例如EHCache集成;另外也提供了开箱即用的默认实现,可以直接拿来使用。
SpringCache支持使用SpEL(Spring Expression Language)来定义缓存的key和各种condition,因此具备相当的灵活性,并可以支持非常复杂的语义。
package org.springframework.cache;
public interface Cache {
//缓存的名字
String getName();
//得到底层使用的缓存,如Ehcache
Object getNativeCache();
//根据key得到一个ValueWrapper,然后调用其get方法获取值
ValueWrapper get(Object key);
//根据key,和value的类型直接获取value
<T> T get(Object key, Class<T> type);
//往缓存放数据
void put(Object key, Object value);
ValueWrapper putIfAbsent(Object key, Object value);
//从缓存中移除key对应的缓存
void evict(Object key);
//清空缓存
void clear();
//缓存值的Wrapper
interface ValueWrapper {
//得到真实的value
Object get();
}
}
ConcurrentMapCache:使用java.util.concurrent.ConcurrentHashMap实现的Cache;
GuavaCache:对Guava com.google.common.cache.Cache进行的Wrapper,需要Google Guava 12.0或更高版本,@since spring 4;
EhCacheCache:使用Ehcache实现
JCacheCache:对javax.cache.Cache进行的wrapper,@since spring 3.2;spring4将此类更新到JCache 0.11版本;
CaffeineCache:Caffeine(咖啡因) Caffeine 是基于 Java 8 的高性能,接近最佳的缓存库,提供了内存缓存受到了guava Api缓存的启发,是在guava的缓存和ConcurrentLinkedHashMap基础上改进的,Spring5已经将guava的cached放弃掉,使用Caffeine
RedisCache:使用Redis实现
@EnableCaching
作用:标注在Configuration类上,用于启用Cache注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
//动态代理模式中,Spring AOP对于具体类的代理是使用JavaProxy还是cglib
boolean proxyTargetClass() default false;
//Spring AOP使用动态代理还是原生ASPECTJ来实现
AdviceMode mode() default AdviceMode.PROXY;
//启动顺序
int order() default Ordered.LOWEST_PRECEDENCE;
}
@CachePut
作用:标注到写数据的方法上,如新增/修改方法,调用方法时会自动把符合条件的数据存入缓存
public @interface CachePut {
//缓存的名字,可以把数据写到多个缓存
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
//缓存key,如果不指定将使用默认的KeyGenerator生成
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
//满足缓存条件的数据才会放入缓存,CachePut的condition只在调用方法之后判断,因此可以得到result
String condition() default "";
//用于否决缓存更新的,不像condition,该表达只在方法执行之后判断,此时可以拿到返回值result进行判断了
String unless() default "";
}
@CachePut(value = "mycache", key = "#user.id", condition = "#user.id ne 12")
public User save(User user) {
return user;
}
上述配置的含义是:在调用该方法时,会把符合条件的(user.id!=12的)user.id作为key,返回值作为value放入缓存;系统中可能存在多个cache,将返回结果保存在名称为"mycache"的缓存中。
@Cacheput中的condition和unless都是在执行方法之后用于被判断是否符合将结果保存到缓存中,因此它们都可以使用返回值#result。
@CacheEvict
public @interface CacheEvict {
//缓存的名字,可以从多个缓存中移除数据
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
//缓存key,如果不指定将使用默认的KeyGenerator生成
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
//满足缓存条件的数据才会从缓存中移除,condition在调用方法之前和之后都会判断
String condition() default "";
//是否移除所有数据
boolean allEntries() default false;
//是调用方法之前移除/还是调用之后移除
boolean beforeInvocation() default false;
}
@CacheEvict(value = "mycache", key = "#user.id")
public void delete(User user) {
users.remove(user);
}
上述配置的含义是:从名为mycache的缓存中移除key值为user.id的数据。
@CacheEvict的condition由beforeInvocation的值来确定是在方法调用前还是在方法调用后执行。若beforeInvocation为true则condition在方法调用前执行;否则condition在方法调用后执行。
@Cacheable
作用:标注到读取数据的方法上,即可缓存的方法,如查找方法:先从缓存中读取,如果没有再调用方法获取数据,然后把数据添加到缓存中
public @interface Cacheable {
//缓存的名字,可以从多个缓存中读取数据
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
//缓存key,如果不指定将使用默认的KeyGenerator生成
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
//满足缓存条件的数据才会从缓存中读取,或者在不存在的时候添加到缓存中
String condition() default "";
//用于否决缓存更新的,该表达只在方法执行之后判断,此时可以拿到返回值result进行判断了
String unless() default "";
boolean sync() default false;
}
@Cacheable(value="mycache2", key = "#username.concat(#email)", condition = "#username eq 'wangd'")
public User findByUsernameAndEmail(String username, String email){
Random random = new Random();
logger.info("cache2 miss, invoke find by name and email, name:" + username +
", email:"+email);
User user = new User(System.currentTimeMillis()+random.nextInt(10000),
username,
email);
return user;
}
上述配置表示:在调用该方法时,当符合条件(username的值为’wangd’)时,会使用username+email作为key,查询缓存,如果缓存中存在该key则直接将其对应的value返回,否则将方法调用的返回值作为value放入缓存。
@Cacheable中的condition是在执行方法之前用于被判断是否符合从缓存中读取,因此它无法使用返回值#result;而其unless是在执行方法之后做判断,因此它可以使用返回值#result。
@Caching
作用:定义若干组的Cache注释,用于实现多个缓存逻辑。例如用户新增成功后,添加id–>user;username—>user;email—>user到缓存,代码如下:
public @interface Caching {
Cacheable[] cacheable() default {}; //声明多个@Cacheable
CachePut[] put() default {}; //声明多个@CachePut
CacheEvict[] evict() default {}; //声明多个@CacheEvict
}
@Caching(
put = {
@CachePut(value = "mycache", key = "#user.id"),
@CachePut(value = "mycache2", key = "#user.username.concat(#user.email)")
},
evict = {
@CacheEvict(value = "tempcache", key = "#user.id")
}
)
public User save(User user) {
.............
}
上述配置表示:在保存一个用户信息的时候,以user.id为key将该user缓存在mycache中,并以user.username+user.email为key将该user缓存在mycache2中,同时从tempcache中删除key值为user.id的缓存。
CacheManager
除了GuavaCacheManager之外,其他Cache都支持Spring事务的,即如果事务回滚了,Cache的数据也会移除掉。
Spring不进行Cache的缓存策略的维护,这些都是由底层Cache自己实现,Spring只是提供了一个Wrapper,提供一套对外一致的API。
package org.springframework.cache;
import java.util.Collection;
public interface CacheManager {
Cache getCache(String name); //根据Cache名字获取Cache
Collection<String> getCacheNames(); //得到所有Cache的名字
}
RedisCacheManager
在Spring中缓存主要有一个缓存接口(Cache)与缓存管理接口(CacheManager)。可以通过扩展这两个接口实现对应的缓存管理。redis就是这样。当然还有很多比如Guava等都对其进行了扩展。这里只看redis的缓存基本架构:
ConcurrentMapCacheManager
GuavaCacheManager
EhCacheCacheManager
JCacheCacheManager
CompositeCacheManager
提供了CompositeCacheManager用于组合CacheManager,即可以从多个CacheManager中轮询得到相应的Cache,如
<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
<property name="cacheManagers">
<list>
<ref bean="ehcacheManager"/>
<ref bean="jcacheManager"/>
</list>
</property>
<property name="fallbackToNoOpCache" value="true"/>
</bean>
当我们调用cacheManager.getCache(cacheName) 时,会先从第一个cacheManager中查找有没有cacheName的cache,如果没有接着查找第二个,如果最后找不到,因为fallbackToNoOpCache=true,那么将返回一个NoOpCacheManager,否则返回null。