高并发技术(二)缓存

概念 :

缓存:使数据更接近使用者,加快访问速度

方式:先从缓存中获取,没有则加入缓存

适合数据:

经常读取的数据,频繁访问,热点数据,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> T get(Object var1, Class<T> var2);//根据key,和value的类型直接获取value

    <T> T get(Object var1, Callable<T> 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<String> 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;官方说应该避免这样使用(解释是如果带条件的注解相互排除的场景);不过个人感觉还是不要考虑这个好,让用户来决定如何使用,否则一会介绍的场景不能满足。

@Caching

有时候我们可能组合多个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);  
}  

 

基于xml的缓存

<!-- the service we want to make cacheable -->
<bean id="bookService" class="x.y.service.DefaultBookService"/>

<!-- cache definitions -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
    <cache:caching cache="books">
        <cache:cacheable method="findBook" key="#isbn"/>
        <cache:cache-evict method="loadBooks" all-entries="true"/>
    </cache:caching>
</cache:advice>

<!-- apply the cacheable behavior to all BookService interfaces -->
<aop:config>
    <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* x.y.BookService.*(..))"/>
</aop:config>

<!-- cache manager definition omitted -->


常见几种缓存的用法
enhance

<bean id="cacheManager"
      class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>

<!-- EhCache library setup -->
<bean id="ehcache"
      class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>


 

缓存的实现


实现基于

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)

SpringBoot的支持顺序

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  单独写吧

 

性能测试

Http缓存

多级缓存

 

 

 


一般cache的基础功能
提供存放读取 容器大小有限设置清除策略
一般是线程安全  单例

```
  private static Cache<String, BaseResponseVo<CartVo>> CACHE_CART = CacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(1, TimeUnit.SECONDS).build();

```


```
    final static Cache<Integer, String> cache = CacheBuilder.newBuilder()  
            //设置cache的初始大小为10,要合理设置该值  
            .initialCapacity(10)  
            //设置并发数为5,即同一时间最多只能有5个线程往cache执行写入操作  
            .concurrencyLevel(5)  
            //设置cache中的数据在写入之后的存活时间为10秒  
            .expireAfterWrite(10, TimeUnit.SECONDS)  
            //构建cache实例  
            .build();  
```

GuavaCache的实现是基于ConcurrentHashMap的。

常见清除策略

guauacache的几种清除策略


time eviction

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<User>,现在你执行了一个 update(user)的方法,你一定不希望清除整个缓存而想替换掉update的元素
这个在现有的抽象上没有很好的方案,可以考虑通过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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值