Spring源码分析-Cache

缓存简介

缓存,我的理解是:让数据更接近于使用者;工作机制是:先从缓存中读取数据,如果没有再从慢速设备上读取实际数据(数据也会存入缓存);缓存什么:那些经常读取且不经常修改的数据/那些昂贵(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。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值