美文网首页
SpringBoot缓存之整合Ehcache

SpringBoot缓存之整合Ehcache

作者: 上善若泪 | 来源:发表于2022-11-14 21:36 被阅读0次

    1 Ehcache

    点此了解Ehcache原理

    1.1 pom.xml

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    
    <!-- ehcache坐标 -->
    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache</artifactId>
    </dependency>
    

    1.2 ehcache.xml

    1.2.1 示例

    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <diskStore path="java.io.tmpdir"/>
    
        <!--defaultCache:echcache 的默认缓存策略 -->
        <defaultCache
                maxElementsInMemory="10000"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                maxElementsOnDisk="10000000"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU">
            <persistence strategy="localTempSwap"/>
        </defaultCache>
        <!-- 自定义缓存策略 -->
        <cache name="users"
               maxElementsInMemory="10000"
               eternal="false"
               timeToIdleSeconds="120"
               timeToLiveSeconds="120"
               maxElementsOnDisk="10000000"
               diskExpiryThreadIntervalSeconds="120"
               memoryStoreEvictionPolicy="LRU">
            <persistence strategy="localTempSwap"/>
        </cache>
    
    </ehcache>
    

    1.2.2 参数说明

    文件中参数说明:

    • diskStore:为缓存路径,ehcache分为内存磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
      • user.home:用户主目录
      • user.dir :用户当前工作目录
      • java.io.tmpdir:默认临时文件路径
    • defaultCachecache都是用来指定缓存的,defaultCache只能有一个,如果在使用时,没有指定具体缓存名字就用这个defaultCache默认缓存,cache缓存可以有多个,但是里面的name必须是不同名字
    • cache标签
      • name:缓存名称
      • maxElementsInMemory:缓存最大数目
      • maxElementsOnDisk:硬盘最大缓存个数。
      • eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      • overflowToDisk:是否保存到磁盘,当系统宕机时保存磁盘
      • timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      • timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0,也就是对象存活时间无穷大。
      • diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      • diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      • diskExpirTyhreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      • memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。也可以设置为FIFO(先进先出)或是LFU(较少使用)
      • clearOnFlush:内存数量最大时是否清除。
      • memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。

    1.3 修改启动类

    添加注解@EnableCaching,说明开启注解缓存

    @SpringBootApplication
    @EnableCaching
    @MapperScan("cn.jzh.mapper")
    public class App {
        public static void main(String[] args) {
            SpringApplication.run(App.class, args);
        }
    }    
    

    1.4 缓存数据

    1.4.1 @Cacheable

    @Cacheable注解在方法上,表示该方法的返回结果是可以缓存的。也就是说,该方法的返回结果会放在缓存中,以便于以后使用相同的参数调用该方法时,会返回缓存中的值,而不会实际执行该方法。

    注意:这里强调了一点:参数相同
    这一点应该是很容易理解的,因为缓存不关心方法的执行逻辑,它能确定的是:对于同一个方法,如果参数相同,那么返回结果也是相同的。但是如果参数不同,缓存只能假设结果是不同的,所以对于同一个方法,程序运行过程中,使用了多少种参数组合调用过该方法,理论上就会生成多少个缓存的 key(当然,这些组合的参数指的是与生成 key 相关的)

    1.4.1.1 常见属性

    @Cacheable这个注解常用的几个属性:

    • cacheNames/value:用来指定缓存组件的名字,二者选其一即可,若不指定则用默认缓存
      示例::@Cacheable("menu")
      • 关联多个缓存名
        @Cacheable 支持同一个方法关联多个缓存。这种情况下,当执行方法之前,这些关联的每一个缓存都会被检查,而且只要至少其中一个缓存命中了,那么这个缓存中的值就会被返回
        示例:@Cacheable({"menu", "menuById"})
    • key :缓存数据时使用的key,可以用它来指定。默认是使用方法参数的值。(这个 key 你可以使用 spEL 表达式来编写)
    • keyGenerator:key 的生成器。 key 和 keyGenerator 二选一使用
    • cacheManager :可以用来指定缓存管理器。从哪个缓存管理器里面获取缓存。
    • condition:可以用来指定符合条件的情况下才缓存
      示例:@Cacheable(value="menu",condition="#id>1")
    • unless :否定缓存。当 unless 指定的条件为 true ,方法的返回值就不会被缓存。当然也可以获取到结果进行判断。(通过 #result 获取方法结果)
      示例:@Cacheable(value="menu",unless="#id>1")
      执行后判断,不缓存的条件。unless 接收一个结果为 true 或 false 的表达式,表达式支持 SpEL。当结果为 true 时,不缓存
      示例:@Cacheable(value = {"menuById"}, key = "#id", unless = "#result.type == 'folder'")
    • sync :是否使用异步模式
      是否同步,true/false。在一个多线程的环境中,某些操作可能被相同的参数并发地调用,这样同一个 value 值可能被多次计算(或多次访问 db),这样就达不到缓存的目的。针对这些可能高并发的操作,我们可以使用 sync 参数来告诉底层的缓存提供者将缓存的入口锁住,这样就只能有一个线程计算操作的结果值,而其它线程需要等待,这样就避免了 n-1 次数据库访问。
      sync = true可以有效的避免缓存击穿的问题。

    1.4.1.2 key & keyGenerator

    需要提前注意指定key,需要以#打头
    官方说 keykeyGenerator 参数是互斥的,同时指定两个会导致异常

    • keyGenerator:
      当我们在声明 @Cacheable 时如果不指定 key 参数,则该缓存名下的所有 key 会使用 KeyGenerator根据参数自动生成。spring有一个默认的 SimpleKeyGenerator ,在 spring boot自动化配置中,这个会被默认注入。
      默认的 key 生成器要求参数具有有效的 hashCode() 和 equals()方法实现。另外,keyGenerator 也支持自定义, 并通过 keyGenerator 来指定
      生成规则如下:
      • 如果该缓存方法没有参数,返回 SimpleKey.EMPTY
      • 如果该缓存方法有一个参数,返回该参数的实例 ;
      • 如果该缓存方法有多个参数,返回一个包含所有参数的 SimpleKey
    • key:
      相较于使用 KeyGenerator 生成,spring 官方更推荐显式指定 key 的方式,即指定 @Cacheablekey 参数。
      即便是显式指定,但是 key的值还是需要根据参数的不同来生成,那么如何实现动态拼接呢?SpEL(Spring Expression Language,Spring 表达式语言) 能做到这一点。

    显示指定key,实现动态拼接

    @Cacheable(value = "users",key = "#root.methodName+'[' + #id +']'")
        public Object queryUserList(Integer id){
            QueryWrapper<User> wrapper = new QueryWrapper<>();
            wrapper.eq("id",1);
            List<User> users = userMapper.selectList(wrapper);
            return JSON.toJSON(users);
        }
    

    自定义keyGenerator

    import org.springframework.cache.interceptor.KeyGenerator;
    
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    @Configuration
    public class MyCacheConfig{
        @Bean("myKeyGenerator ")
        public KeyGenerator keyGenerator(){
            return new KeyGenerator(){
                @Override
                public Object generate(Object target, Method method, Object... params) {
                    return method.getName()+"["+ Arrays.asList(params).toArray()+"]";
                }
            };
        }
    }
    

    自定义keyGenerator使用例子

    @Cacheable(value = "users",keyGenerator = "myKeyGenerator")
        public Object queryUserList(Integer id){
            QueryWrapper<User> wrapper = new QueryWrapper<>();
            wrapper.eq("id",1);
            List<User> users = userMapper.selectList(wrapper);
            return JSON.toJSON(users);
        }
    

    1.4.1.3 使用spEL编写key

    前面说过,缓存的 key 支持使用 spEL 表达式去编写,下面总结一下使用 spEL 去编写 key 可以用的一些元数据

    名字 位置 描述 示例
    methodName root object 当前被调用方法名 #root.methodName
    method root object 当前被调用方法 #root.method.name
    target root object 当前被调用目标对象 #root.target
    targetClass root object 当前被调用目标对象类 #root.targetClass
    args root object 当前被调用方法参数列表 #root.args[0]
    caches root object 当前方法调用使用的缓存列表 ,如@Cacheable({"menu", "menuById"}),则有两个cache #root.caches[0].name
    argument name evaluation context 方法参数名字,可以直接使用 #参数名,也可以使用#p0或者#a0形式,0代表参数索引 #a0
    result evaluation context 方法执行后的返回值(仅当方法执行后的判断有效) #result

    1.4.1.4 cacheManager & cacheResolver

    CacheManager,缓存管理器是用来管理(检索)一类缓存的。通常来讲,缓存管理器是与缓存组件类型相关联的。我们知道,spring 缓存抽象的目的是为使用不同缓存组件类型提供统一的访问接口,以向开发者屏蔽各种缓存组件的差异性。那么 CacheManager 就是承担了这种屏蔽的功能。spring 为其支持的每一种缓存的组件类型提供了一个默认的 manager,如:RedisCacheManager 管理 redis 相关的缓存的检索、EhCacheManager 管理 ehCache 相关的缓等。

    CacheResolver,缓存解析器是用来管理缓存管理器的,CacheResolver 保持一个 cacheManager 的引用,并通过它来检索缓存。CacheResolver 与 CacheManager 的关系有点类似于 KeyGenerator 跟 key。spring 默认提供了一个 SimpleCacheResolver,开发者可以自定义并通过 @Bean 来注入自定义的解析器,以实现更灵活的检索。

    大多数情况下,我们的系统只会配置一种缓存,所以我们并不需要显式指定 cacheManager 或者 cacheResolver。但是 spring 允许我们的系统同时配置多种缓存组件,这种情况下,我们需要指定。指定的方式是使用 @CacheablecacheManager 或者 cacheResolver 参数。

    注意:按照官方文档,cacheManagercacheResolver 是互斥参数,同时指定两个可能会导致异常

    1.4.2 @CachePut

    当需要在不影响方法执行的情况下更新缓存时,可以使用@CachePut,也就是说,被 @CachePut 注解的缓存方法总是会执行,而且会尝试将结果放入缓存(当然,是否真的会缓存还跟一些注解参数有关,比如:unless 参数)。@CachePut@Cacheable 有相同的参数属性(但是没有 sync 属性)。@CachePut 更加适合于缓存填充,而不是方法执行流的优化。

    由于与 @Cacheable 的属性基本相同,所以不再重复示例。这里重点说明一下它们的区别:

    • @Cacheable 的逻辑是:查找缓存->有就返回->没有就执行方法体->将结果缓存起来;
    • @CachePut 的逻辑是:执行方法体 -> 将结果缓存起来;

    所以 @Cacheable 适用于查询数据的方法,@CachePut 适用于更新数据的方法。

    1.5 清除缓存@CacheEvict

    1.5.1 常见属性

    除了填充缓存,spring cache也支持使用 @CacheEvict 来删除缓存。@CacheEvict就是一个触发器,在每次调用被它注解的方法时,就会触发删除它指定的缓存的动作。跟 @Cacheable@CachePut 一样,@CacheEvict 也要求指定一个或多个缓存,也指定自定义一的缓存解析器和 key 生成器,也支持指定条件(condition 参数)。

    @CacheEvict是用来清除缓存的,有以下属性:

    • value/cacheNames:缓存位置名称,不能为空
    • key:缓存的key,默认为空(如果allEntries=false,清除指定key的缓存)
      一般来说,我们的更新操作只需要刷新缓存中某一个值,所以定义缓存的key值的方式就很重要,最好是能够唯一,因为这样可以准确的清除掉特定的缓存,而不会影响到其它缓存值
    • condition:触发条件,只有满足条件的情况才会清除缓存,默认为空,支持SpEL
    • allEntries:true表示清除value中的全部缓存,默认为false
    • beforeInvocation:是否在执行对应方法之前删除缓存,默认 false(即执行方法之后再删除缓存)

    1.5.2 beforeInvocation

    beforeInvocation@CacheEvict 中特有的一个属性,意为是否在执行对应方法之前删除缓存,默认 false(即执行方法之后再删除缓存)。
    首先思考一个问题,在什么情况下我们需要主动去删除缓存呢?一般来讲都是在删除数据的时候,需要主动去删除缓存。那么就存在一个问题,程序执行时顺序的,那我们到底是应该先删除缓存,再调用方法去数据库中删除;还是先从数据库中删除,完了之后再去删除对应的缓存呢?

    在正常情况下,这两种方式差别并不大,毕竟程序执行都是毫秒级的,顺序执行没有什么时间跨度。但是,现实环境复杂,缓存访问和 db 访问都可能会出现异常,这种情况下就有区别了:

    • 如果先删除缓存成功,然后 db 删除失败,那么接下来的查询就会直达数据库,造成压力;
    • 如果先 db 删除成功,然后删除缓存失败,那么就会造成脏缓存;

    至于该如何取舍,spring cache 通过 beforeInvocation 给开发者提供选择

    相关文章

      网友评论

          本文标题:SpringBoot缓存之整合Ehcache

          本文链接:https://www.haomeiwen.com/subject/rzrrxdtx.html