SpringBoot 缓存管理

缓存有关,先说下常用注解:

缓存注解介绍

@EnableCaching注解:开启缓存

@EnableCaching注解是由Spring框架提供的,SpringBoot框架对该注解进行了继承,该注解需要配置在类的上方(一般配置在项目启动类上),用于开启基于注解的缓存支持

@Cacheable注解:读取缓存

@Cacheable注解也是由Spring框架提供的,可以作用于类或方法上(通常作用于数据查询方法上),用于对方法的执行结果进行数据缓存存储。

注解的执行顺序是:先进行缓存查询,如果为空则进行方法查询(查数据库),并将结果进行缓存;如果缓存中有数据,则不进行方法查询,而是直接使用缓存数据。

执行流程&时机

方法运行之前,先去查询Cache(缓存组件),按照 cacheNames缓存空间 指定的名字获取,(CacheManager先获取相应的缓存),第一次获取缓存如果获取不到,Cache组件会自动创建。

去Cache中查找缓存的内容,使用一个key进行查找,默认在只有一个参数的情况下,key值默认就是方法的参数;

如果有多个参数或者没有参数,则SpringBoot会按照某种策略生成key,默认是使用KeyGenerator生成的,其实现类为SimpleKeyGenerator, 生成key的默认策略如下:

常用的SPEL表达式:

@CachePut注解:用于新增和修改操作 

@CachePut被使用于修改操作较多,若缓存中已经存在目标值了,该注解作用的方法依然会执行,执行后将结果保存在缓存中(覆盖掉原来的目标值)。默认执行顺序是:目标方法执行完之后生效。

@CachePut注解也提供了多个属性,这些属性与@Cacheable注解的属性完全相同。

@CacheEvict注解 用于删除操作

@CacheEvict注解也是由Spring框架提供的,可以作用于类或方法上(通常作用于数据删除方法上),该注解的作用是删除缓存数据

@CacheEvict注解的默认执行顺序是:先进行方法调用,然后将缓存进行清除。

@Caching:用于组合操作 

有时候我们可能组合多个Cache注解使用;比如用户新增成功后,我们要添加id-->user;username--->user;email--->user的缓存;此时就需要@Caching组合多个注解标签了。

如用户新增成功后,添加id-->user;username--->user;email--->user到缓存; 

@Caching

        put = { 

                @CachePut(value = "user", key = "#user.id"), 

                @CachePut(value = "user", key = "#user.username"), 

                @CachePut(value = "user", key = "#user.email"

        

public User save(User user){ };

  

@Caching 注解源码:

public @interface Caching { 

    Cacheable[] cacheable() default {}; //声明多个@Cacheable 

    CachePut[] put() default {};        //声明多个@CachePut 

    CacheEvict[] evict() default {};    //声明多个@CacheEvict 

  

//自定义缓存注解

//比如上面的@Caching组合,会让方法上的注解显得整个代码比较乱,此时可以使用自定义注解把这些注解组合到一个注解中,如:

@Caching

        put = { 

                @CachePut(value = "user", key = "#user.id"), 

                @CachePut(value = "user", key = "#user.username"), 

                @CachePut(value = "user", key = "#user.email"

        

)

  

@Target({ElementType.METHOD, ElementType.TYPE}) 

@Retention(RetentionPolicy.RUNTIME) 

@Inherited 

public @interface UserSaveCache { 

//这样我们在方法上使用如下代码即可,整个代码显得比较干净。

@UserSaveCache 

public User save(User user){ };


缓存管理

Spring框架支持  透明地  向应用程序添加缓存 对缓存进行管理,

SpringBoot 继承了Spring框架的缓存管理功能,通过使用@EnableCaching注解开启基于注解的缓存支持,SpringBoot就可以启动缓存管理的自动化配置。

SpringBoot 整合其他缓存组件

在SpringBoot中,数据的缓存管理存储依赖于Spring框架中cache相关的

org.springframework.cache.Cache  缓存接口

org.springframework.cache.CacheManager缓存管理器接口。

Spring提供的核心Cache接口: 提供了缓存操作的读取/写入/移除方法;

package org.springframework.cache; 

public interface Cache { 

    String getName();            //缓存的名字 

    Object getNativeCache();      //得到底层使用的缓存,如Ehcache 

    ValueWrapper get(Object key);  //根据key得到一个ValueWrapper,然后调用其get方法获取值 

    <T> T get(Object key, Class<T> type);  //根据key,和value的类型直接获取value 

    void put(Object key, Object value);       //往缓存放数据 

    void evict(Object key);                //从缓存中移除key对应的缓存 

    void clear();                    //清空缓存 

}

默认提供了如下实现:

  • 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版本;

另外,因为我们在应用中并不是使用一个Cache,而是多个,因此Spring还提供了CacheManager抽象,用于缓存的管理:  等等

 折叠原码

package org.springframework.cache; 

import java.util.Collection; 

public interface CacheManager { 

    Cache getCache(String name);         //根据Cache名字获取Cache  

    Collection<String> getCacheNames();  //得到所有Cache的名字 

}

 默认提供的实现: 

ConcurrentMapCacheManager/  ConcurrentMapCacheFactoryBean:    管理ConcurrentMapCache;

GuavaCacheManager;

EhCacheCacheManager/    EhCacheManagerFactoryBean;

JCacheCacheManager/   JCacheManagerFactoryBean;等等

注意点:

  • 除了GuavaCacheManager之外,其他Cache都支持Spring事务的,即如果事务回滚了,Cache的数据也会移除掉。
  • Spring 不进行Cache的缓存策略的维护,这些都是由底层Cache自己实现,Spring只是提供了一个Wrapper,提供一套对外一致的API。
     

如果程序中没有定义类型为CacheManager的Bean组件或者是名为cacheResolver的CacheResolver缓存解析器,SpringBoot将尝试选择启用以下缓存组件(按照指定的顺序):

SpringBoot 默认支持的缓存组件:

(1)Generic

(2)JCache (JSR-107) (EhCache 3、Hazelcast、Infinispan等)

(3)EhCache 2.x

(4)Hazelcast

(5)Infinispan

(6)Couchbase

(7)Redis

(8)Caffeine

(9)Simple:SpringBoot默认的缓存管理组件

在项目中添加某个缓存管理组件(例如Redis)后,SpringBoot项目会选择并启用对应的缓存管理器。

如果在项目中同时添加了多个缓存组件,且没有指定缓存管理器或者缓存解析器(CacheManager或者cacheResolver),

那么SpringBoot会按照上述顺序在添加的多个缓存组件中优先启用排在前面的某个缓存组件进行缓存管理(例如,同时添加了Couchbase和Redis这两个缓存组件,那么优先启用Couchbase组件)。

SpringBoot默认缓存底层结构

在诸多的缓存自动配置类中,SpringBoot默认装配的是SimpleCacheConfiguration,它使用的CacheManager是ConcurrentMapCacheManager,使用ConcurrentMap作为底层的数据结构,

根据Cache的名字查询出Cache,每一个Cache中存在多个key-value键值对、缓存值。

其他缓存组件的自动配置类

基于注解的Redis组件实现缓存管理

参考:https://www.cnblogs.com/blayn/p/14890975.html

当我们添加Redis相关的依赖启动器后,SpringBoot会使用RedisCacheConfigratioin作为自动配置类进行缓存相关的自动装配类(之前为默认的SimpleCacheConfiguration),容器中使用的缓存管理器变为了RedisCacheManager(之前为默认为cacheManager),

这个缓存管理器创建的Cache为RedisCache,进而操控Redis进行数据的缓存。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

@Configuration

@AutoConfigureAfter({RedisAutoConfiguration.class})

@ConditionalOnBean({RedisTemplate.class})

@ConditionalOnMissingBean({CacheManager.class})

@Conditional({CacheCondition.class})

class RedisCacheConfiguration {

    private final CacheProperties cacheProperties;

    private final CacheManagerCustomizers customizerInvoker;

    RedisCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker) {

        this.cacheProperties = cacheProperties;

        this.customizerInvoker = customizerInvoker;

    }

    @Bean

    public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {  // 自动注入 RedisCacheManager 缓存管理器

        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);

        cacheManager.setUsePrefix(true);

        List<String> cacheNames = this.cacheProperties.getCacheNames();

        if (!cacheNames.isEmpty()) {

            cacheManager.setCacheNames(cacheNames);

        }

        return (RedisCacheManager)this.customizerInvoker.customize(cacheManager);

    }

}

在SpringBoot全局配置文件中配置Redis有效期,示例代码如下:

 折叠原码

1

2

# 对基于注解的Redis缓存数据统一设置有效期为1分钟,单位毫秒

spring.cache.redis.time-to-live=60000

上述代码中,在SpringBoot全局配置文件中添加了“spring.cache.redis.time-to-live”属性统一设置Redis数据的有效期(单位为毫秒),但这种方式不够灵活,因此一般不用。

基于API的Redis缓存实现

例如:下面示例是一个典型的 通过API来操作缓存的演示: 首先查询缓存-→缓存中不存在则查询底层数据库--→将查询结果放入缓存,并设置有效期。

优劣: 优点:可以针对每个缓存手动设置有效期。 缺点: 代码侵入严重。

 折叠原码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

/**

 * 根据评论id查询评论

 */

public Comment findCommentById(Integer id){

    // 先查Redis缓存

    Object o = redisTemplate.opsForValue().get("comment_" + id);

    if (o != null) {

        return (Comment) o;

    else {

        // 如果缓存中没有,则从数据库查询

        Optional<Comment> dbComment = commentRepository.findById(id);

        if (dbComment.isPresent()) {

            Comment redisComment = dbComment.get();

            // 将查询结果存储到缓存中,并设置有效期为1天

            redisTemplate.opsForValue().set("comment_"+id, redisComment,1, TimeUnit.DAYS);

            return redisComment;

        else {

            return null;

        }

    }

}

如何实现缓存基于注解,并且可以针对每个缓存手动设置有效期?


杂文:

缓存命中率

即从缓存中读取数据的次数 与 总读取次数的比率,命中率越高越好:

命中率 = 从缓存中读取次数 / (总读取次数[从缓存中读取次数 + 从慢速设备上读取的次数])

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)

空闲期,即一个数据多久没被访问将从缓存中移除的时间

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值