JVM之EhCache缓存

EhCache缓存

一、EhCache介绍

在查询数据的时候,数据大多来自数据库,咱们会基于SQL语句的方式与数据库交互,数据库一般会基于本地磁盘IO的形式将数据读取到内存,返回给Java服务端,Java服务端再将数据响应给客户端,做数据展示。

但是MySQL这种关系型数据库在查询数据时,相对比较慢,因为有磁盘IO,有时没命中索引还需要全盘扫描。在针对一些热点数据时,如果完全采用MySQL,会存在俩问题。第一个MySQL相对很脆弱,肯能会崩,第二个MySQL查询效率慢。会采用缓存。

而缓存分为很多种,相对服务端的角度来说大致分为两种,一种JVM缓存(堆内缓存),另一种是堆外缓存(操作系统的内存中、Redis跨服务的缓存)

Redis不用说太多,Redis基于内存读写,效率很高,而且Redis服务的并发能力很强。毕竟Redis是另一个服务,需要通过网络IO的形式去查询数据。不过一般分布式微服务的缓存首选还是Redis。

但是单体项目,想把缓存的性能提升的比Redis还要快,选择JVM缓存了,一般框架自带的缓存机制,比如Hibernate缓存,MyBatis也有一级缓存和二级缓存。

为什么DAO层框架已经提供了缓存的概念,为什么要搞EhCache:

因为DAO层框架的缓存是在Mapper层触发的,EhCache可以将缓存提到Service层触发。效率肯定会有提升

并且EhCache提供了非常丰富的功能,不但可以将数据存储在JVM内部,还可以放到堆外,甚至还可以存储到本地磁盘。

二、EhCache基本使用

EhCache的官网:http://www.ehcache.org

EhCache明显开源的,EhCache可以几乎0成本和Spring整合,配合Java规范,直接采用Cache注解实现缓存

@Cacheable这个是Java的规范,Spring集成了这个规范默认整合Redis,不过也可以整合EhCache

EhCache官方有两大版本,分别是2.x和3.x的版本,这里选择3.x版本去玩,可以更好的以SpringBoot的形式去集成到一起使用

先单独使用EhCache查看效果

EhCache快速入门

导入依赖

<dependencies>
    <dependency>
        <groupId>org.ehcache</groupId>
        <artifactId>ehcache</artifactId>
        <version>3.8.1</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

入门操作

@Test
public void test(){
    //1. 初始化好CacheManager
    CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
            // 一个CacheManager可以管理多个Cache
            .withCache(
                    "singleDog",
                    CacheConfigurationBuilder.newCacheConfigurationBuilder(
                            String.class,
                            Object.class,
                            // heap相当于设置数据在堆内存中存储的 个数 或者 大小
                            ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10).build()).build()
            ).build(true);
//        cacheManager.init();

    //2. 基于CacheManager回去到Cache对象
    Cache<String, Object> cache = cacheManager.getCache("singleDog", String.class, Object.class);

    //3. 存  set/put/add/
    cache.put("ehcache","57个单身狗!!");

    //4. 取
    System.out.println(cache.get("ehcache"));
}

三、EhCache配置

EhCache提供很多丰富的配置,其中有两个是很重要的。

3.1 数据存储位置

EhCache3.x版本中不但提供了堆内缓存heap,还提供了堆外缓存off-heap,并且还提供了数据的持久化操作,可以将数据落到磁盘中disk。

heap堆内内存存储

heap表示使用堆内内存:

  • heap(10)代表当前Cache最多只能存储10个数据,当你put第11个数据时,第一个数据就会被移除。
  • heap(10,大小单位MB)代表当前Cache最多只能存储10MB数据。

off-heap堆外内存

off-heap是将存储的数据放到操作系统的一块内存区域存储,不是JVM内部,这块空间属于RAM。这种对象是不能直接拿到JVM中使用的,在存储时,需要对数据进行序列化操作,同时获取出来的时候也要做反序列化操作。

disk落到磁盘

disk表将数据落到本地磁盘,这样的话,当服务重启后,依然会从磁盘反序列化数据到内存中。

EhCache提供了三种组合方式:

  • heap + off-heap
  • heap + disk
  • heap + off-heap + disk

image.png

在组合情况下存储,存储数据时,数据先落到堆内内存,同时同步到对外内存以及本地磁盘。本地底盘因为空间充裕,所以本地磁盘数据是最全的。而且EhCache要求空间大小必须disk > off-heap > heap。

在组合情况下读取,因为性能原型,肯定是先找heap查询数据,没有数据去off-heap查询数据,off-heap没有数据再去disk中读取数据,同时读取数据之后,可以将数据一次同步到off-heap、heap

通过API实现组合存储方式:

@Test
public void test(){
    //0. 声明存储位置
    String path = "D:\\ehcache";
    //1. 初始化好CacheManager
    CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
            // 设置disk存储的位置
            .with(CacheManagerBuilder.persistence(path))
            .withCache(
                    "singleDog",
                    CacheConfigurationBuilder.newCacheConfigurationBuilder(
                            String.class,
                            String.class,
                            ResourcePoolsBuilder.newResourcePoolsBuilder()
                                    .heap(10)
                                    // 堆外内存
                                    .offheap(10, MemoryUnit.MB)
                                    // 磁盘存储,记得添加true,才能正常的持久化,并且序列化以及反序列化
                                    .disk(11,MemoryUnit.MB, true)
                                    .build()
                    ).build()
            ).build(true);

    //2. 基于CacheManager回去到Cache对象
    Cache<String, String> cache = cacheManager.getCache("singleDog", String.class, String.class);

    //3. 存
//        cache.put("singleDog","29个单身狗!!");

    //4. 取
    System.out.println(cache.get("singleDog"));

    //5. 保证数据正常持久化不丢失,记得cacheManager.close();
    cacheManager.close();
}

本地磁盘存储的方式,一共有三个文件

  • mata:元数据存储,记录这当前cache的key类型和value类型
  • data:存储具体数据的位置,将数据序列化成字节存储
  • index:类似索引,帮助查看数据的。

3.2 数据生存时间

因为数据如果一致存放在内存当中,可能会出现内存泄漏等问题,数据在内存,一致不用,还占着空间

EhCache提供了对数据设置生存时间的机制

提供了三种机制:

  • noExpiration:不设置生存时间
  • timeToLiveExpiration:从数据落到缓存计算生存时间
  • timeToIdleExpiration:从最后一个get计算生存时间
@Test
public void test() throws InterruptedException {
    //1. 初始化好CacheManager
    CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
            .withCache(
                    "singleDog",
                    CacheConfigurationBuilder.newCacheConfigurationBuilder(
                            String.class,
                            Object.class,
                            ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10).build())
                            // 三选一。
                            // 不设置生存时间
//                                .withExpiry(ExpiryPolicy.NO_EXPIRY)
                            // 设置生存时间,从存储开始计算
//                                .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMillis(1000)))
                            // 设置生存时间,每次获取数据后,重置生存时间
                            .withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofMillis(1000)))
                            .build()
            ).build(true);

    Cache<String, Object> cache = cacheManager.getCache("singleDog", String.class, Object.class);

    cache.put("ehcache","24个单身狗!!");
    System.out.println(cache.get("ehcache"));
    Thread.sleep(500);
    cache.get("ehcache");
    Thread.sleep(500);
    System.out.println(cache.get("ehcache"));
}

四、SpringBoot整合EhCache

SpringBoot默认情况下是整合了EhCache的,但是SPringBoot整合的EhCache的2.x版本。

这里依然整合EhCache的3.x版本。

4.1 构建SpringBoot工程

阿巴阿巴

4.2 导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.ehcache</groupId>
        <artifactId>ehcache</artifactId>
        <version>3.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
</dependencies>

4.3 准备EhCache的配置项

# 准备EhCache基础配置项
ehcache:
  heap: 1000           # 堆内内存缓存个数
  off-heap: 10         # 对外内存存储大小 MB
  disk: 20             # 磁盘存储数据大小  MB
  diskDir: D:/data/    # 磁盘存储路径
  cacheNames:          # 基于CacheManager构建多少个缓存
    - user
    - item
    - card

引入配置文件中的配置项

@Component
@ConfigurationProperties(prefix = "ehcache")
public class EhCacheProps {

    private int heap;

    private int offheap;

    private int disk;

    private String diskDir;

    private Set<String> cacheNames;

}

4.4 配置CachaManager

@Configuration
@EnableCaching
public class EhCacheConfig {

    @Autowired
    private EhCacheProps ehCacheProps;

    @Bean
    public CacheManager ehCacheManager(){
        //1. 缓存名称
        Set<String> cacheNames = ehCacheProps.getCacheNames();

        //2. 设置内存存储位置和数量大小
        ResourcePools resourcePools = ResourcePoolsBuilder.newResourcePoolsBuilder()
                .heap(ehCacheProps.getHeap())
                .offheap(ehCacheProps.getOffheap(), MemoryUnit.MB)
                .disk(ehCacheProps.getDisk(),MemoryUnit.MB)
                .build();

        //3. 设置生存时间
        ExpiryPolicy expiry = ExpiryPolicyBuilder.noExpiration();

        //4. 设置CacheConfiguration
  		// baseObject是一个POJO类实现了序列化接口
        CacheConfiguration cacheConfiguration = CacheConfigurationBuilder
                .newCacheConfigurationBuilder(String.class, BaseObject.class, resourcePools)
                .withExpiry(expiry)
                .build();

        //5. 设置磁盘存储的位置
        CacheManagerBuilder<PersistentCacheManager> cacheManagerBuilder =
                CacheManagerBuilder.newCacheManagerBuilder().with(CacheManagerBuilder.persistence(ehCacheProps.getDiskDir()));

        //6. 缓存名称设置好。
        for (String cacheName : cacheNames) {
            cacheManagerBuilder.withCache(cacheName,cacheConfiguration);
        }

        //7. 构建
        return cacheManagerBuilder.build();
    }
}

五、Cache注解使用

Cache注解是JSR规范中的,Spring支持这种注解。前面配置好关于CacheManager之后,就可以在Service层添加Cache注解,实现缓存使用,缓存更新,缓存清除。

5.1 @Cacheable

5.1.1 基本使用

这个是查询缓存的注解,可以加载方法上,也可以加在类上(不要添加在类上,这样很多细粒度配置就无法实现,比如@Transactional),可以在执行当前方法前,根据注解查看方法的返回内容是否已经被缓存,如果已经缓存,不需要执行业务代码,直接返回数据。如果没有命中缓存,正常执行业务代码,在执行完毕后,会将返回结果作为缓存,存储起来。

直接在Service层的方法上添加@Cacheable,注意,必须填写@Cacheable中的value或者cacheName属性

默认情况下,每次查询会基于Key(默认是方法的参数)去查看是否命中缓存

  • 如果命中缓存,直接返回
  • 如果未命中缓存,正常执行业务代码,基于方法返回结果做缓存
5.1.2 key的声明方式

key的声明方式有两种,一种是基于Spring的Expression Language去实现,另一种是基于编写类的方式动态的生成key

5.1.2.1 Spel表达式语言实现
@Override
@Cacheable(cacheNames = {"item"},key = "#id")    // 123
public String echo(String id,String... args) {
    System.out.println("查询数据库~");
    // itemMapper.findById(id);
    return id;
}

这种方式要基于Spel实现,但是Spel用的不过,单独为了这种操作熟悉Spel成本蛮高的,而且功能并不丰富,所以更推荐第二种方式,编写类的方式设置key的生成策略

5.1.2.2 KeyGenerator实现

这种方式需要在Spring容器中构建KeyGenerator实现类,基于注解配置进去即可

设置key的生成策略。

@Configuration
public class CacheKeyGenerator {

    @Bean(name = "itemKeyGenerator")
    public KeyGenerator itemKeyGenerator(){
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                return method.getName() + params[0];
            }
        };
    }
}

设置bean name到keyGenerator中

@Override
@Cacheable(cacheNames = {"item"},keyGenerator = "itemKeyGenerator")   
public String echo(String id,String... args) {
    System.out.println("查询数据库~");
    // itemMapper.findById(id);
    return id;
}
5.1.3 缓存条件

在执行方法前后,判断当前数据是否需要缓存,所以一般基础参数的判断。

  • 条件为true代表缓存(condition)
  • 条件为false代表缓存(unless)

都可以基于Spel编写条件表达式

5.1.3.1 condition

在执行方法前,决定是否需要缓存

可以在condition中编写Spel,只要条件为true,既代表当前数据可以缓存

@Override
@Cacheable(cacheNames = {"item"},condition = "#id.equals(\"123\")")
public String echo(String id) {
    System.out.println("查询数据库~");
    // itemMapper.findById(id);
    return id;
}
5.1.3.2 unless

执行方法之后,决定是否需要缓存

unless也可以编写Spel,条件为false时,代表数据可以缓存,如果为true,代表数据不需要缓存

@Override
@Cacheable(cacheNames = {"item"},unless = "#result.equals(\"123\")")
public String echo(String id) {
    System.out.println("查询数据库~");
    // itemMapper.findById(id);
    return id;
}

更多的其实还是在执行查询前,来判断数据是否需要缓存。如果真的需要做,也是避免诡异的操作。

比如Service在出现异常结果时,返回-1,那么这种-1,就不需要缓存。

5.1.3.3 condition&unless的优先级

condition和unless都是代表是都需要缓存数据。

如果同时设置condition和unless。

  • condition,unless
  • true,false:都代表缓存,那就缓存喽。
  • true,true:unless代表不缓存,那就不缓存
  • false,false:condition代表不缓存数据,那就不缓存
  • false,true:都不让缓存, 那就不缓存

condition和unless没有优先级之分,他的优先级在于,不缓存的优先级高于缓存。

5.2.4 sync

缓存击穿问题。

当多个线程并发访问一个Service方法时,发现当前方法没有缓存数据,此时会让一个线程去执行业务代码查询数据,扔到缓存中,后面线程再查询缓存

可以设置sync属性为true,代表当执行Service方法时,发现缓存没数据,那么就需要去竞争锁资源去执行业务代码,后续线程等待前置线程执行完,再去直接查询缓存即可

@Override
@Cacheable(cacheNames = {"item"},sync = true)
public String echo(String id) {
    System.out.println("查询数据库~");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return id;
}

5.2 @CachePut

@CachePut注解巨简单,是在写数据之后,更新缓存的数据

在增删改的操作上追加@CachePut注解,会根据key去重置指定的缓存。

细节点就在于对标上查询方法的key

@Override
@CachePut(cacheNames = "item",key = "#item.id")
public String write(Item item) {
    // 写id为123的数据
    System.out.println("123被改成456");
    return "456";
}

@Cacheable其他的属性,和@Cacheable一模一样~~~

5.3 @CacheEvict

@CacheEvict是用来清楚缓存的,可以根据注解里的cacheNames和key来清除指定缓存,也可以清除整个cacheNames中的全部缓存

清除指定缓存

@Override
@CacheEvict(value = "item")
public void clear(String id) {
    System.out.println("清除缓存成功!");
}

清除全部缓存

@Override
@CacheEvict(value = "item",allEntries = true)
public void clearAll() {
    System.out.println("清除item中的全部缓存~!");
}

如果执行清除缓存过程中,业务代码出现异常,会导致无法正常清除缓存,可以设置一个属性来保证在方法业务执行之前,就将缓存正常清除beforeInvocation设置为true

@Override
@CacheEvict(value = "item",allEntries = true,beforeInvocation = true)
public void clearAll() {
    int i = 1 / 0;
    System.out.println("方法执行前,清除item中的全部缓存~!");
}

5.4 @Caching

没啥说的,一个组合数据,可以基于Caching实现@Cacheable,@CachePut以及@CacheEvict三个注解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/498185.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu下使用vscode进行C/C++开发:进阶篇

在vscode上进行C/C++开发的进阶需求: 1) 编写及调试源码时,可进行断点调试、可跨文件及文件夹进行函数调用。 2) 可生成库及自动提取对应的头文件和库文件。 3) 可基于当前工程资源一键点击验证所提取的库文件的正确性。 4) 可结合find_package实现方便的调用。 对于第一…

LLM之RAG实战(三十五)| 使用LangChain的3种query扩展来优化RAG

RAG有时无法从矢量数据库中检索到正确的文档。比如我们问如下问题&#xff1a; 从1980年到1990年&#xff0c;国际象棋的规则是什么&#xff1f; RAG在矢量数据库中进行相似性搜索&#xff0c;来查询与国际象棋规则问题相关的相关文档。然而&#xff0c;在某些情况下&#xff0…

mysql修改用户权限

https://blog.csdn.net/anzhen0429/article/details/78296814

Elasticsearch 和 Kibana 8.13:简化 kNN 和改进查询并行化

作者&#xff1a;Gilad Gal, Tyler Perkins, Srikanth Manvi, Aris Papadopoulos, Trevor Blackford 在 8.13 版本中&#xff0c;Elastic 引入了向量搜索的重大增强&#xff0c;并将 Cohere 嵌入集成到其统一 inference API 中。这些更新简化了将大型语言模型&#xff08;LLM&a…

java数据结构与算法刷题-----LeetCode278. 第一个错误的版本

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 二分查找 二分查找 解题思路&#xff1a;时间复杂度O( l o g 2 …

Unity照片墙简易圆形交互效果总结

还要很多可以优化的点地方&#xff0c;有兴趣的可以做 比如对象的销毁和生成可以做成对象池&#xff0c;走到最左边后再移动到最右边循环利用 分析过程文件&#xff0c;采用Blender&#xff0c;资源已上传&#xff0c;可以播放动画看效果&#xff0c;下面截个图&#xff1a; …

将MATLAB的图无失真复制到illustrator

选择复制选项 设置图元文件 复制到illustrator&#xff0c;可以看到每个图片部件都可以操作并且放大无失真

芒果YOLOv8改进145:全新风格原创YOLOv8网络结构解析图

&#x1f4a1;本篇分享一下个人绘制的原创全新风格 YOLOv8网络结构图 感觉搭配还行&#xff0c;看着比较直观。 该专栏完整目录链接&#xff1a; 芒果YOLOv8深度改进教程 订阅了专栏的读者 可以获取一份 <可以自行修改 / 编辑> 的 YOLOv8结构图修改源文件 YOLOv8结构图…

康耐视visionpro-CogBlobTool工具详细说明

CogBlobTool功能说明: 通过设置灰度值提取感兴趣区域,并分析所提取区域的面积、长宽等参数。 CogBlobTool操作说明: ①.打开工具栏,双击或点击鼠标拖拽添加CogBlobTool工具 ②.添加输入图像:单击鼠标右键“链接到”或以连线拖拽的方式选择相应输入源 ③.极性:“白底黑点…

康耐视visionpro-CogFindCircleTool工具详细说明

CogFindCircleTool功能说明: 通过用多个卡尺找到多个点来拟合所要找的圆 CogFindCircleTool操作说明: ①.打开工具栏,双击或点击鼠标拖拽添加CogFindCircleTool工具 ②.添加输入图像,右键“链接到”或以连线拖拽的方式选择相应输入源 ③.预期的圆弧:设置预期圆弧的中心点…

基于ssm的bbs论坛系统

开发环境&#xff1a;idea 前端&#xff1a;JQueryBootstraplayui后端&#xff1a;SpringSpringMVCMybatis数据库&#xff1a;mysqlredis 基于ssm的bbs论坛系统&#xff0c;功能有论坛、导读、动态、排行榜以及后台管理系统等等 话不多说&#xff0c;看图&#xff01;&#x…

VTK 9.2.6 加 QT6 编译

上一篇的example编译VTK 9.2.6 源码和VTK Examples 编译 Visual Studio 2022 增加 VTK_GROUP_ENABLE_Qt 为yes 指定QT6-DIR的路径为 C:\Qt\6.6.3\mingw_64\lib\cmake\Qt6

Android room 在dao中不能使用挂起suspend 否则会报错

错误&#xff1a; Type of the parameter must be a class annotated with Entity or a collection/array of it. kotlin.coroutines.Continuation<? super kotlin.Unit> $completion); 首先大家检查一下几个点 一、kotlin-kapt 二、 是否引入了 room-ktx 我是2024年…

康耐视visionpro-CogCaliperTool工具详细说明

CogCaliperTool功能说明: 卡尺工具,用于测量距离 CogCaliperTool操作说明: ①.打开工具栏,双击或点击鼠标拖拽添加CogCaliperTool ②.添加输入图像,右键“链接到”或以连线拖拽的方式选择相应输入源 ③.拖动屏幕上的矩形框到需要测量的位置。卡尺的搜索框角度与边缘不平…

C/C++ ③ —— C++11新特性

1. 类型推导 1.1 auto auto可以让编译器在编译期就推导出变量的类型 auto的使⽤必须⻢上初始化&#xff0c;否则⽆法推导出类型auto在⼀⾏定义多个变量时&#xff0c;各个变量的推导不能产⽣⼆义性&#xff0c;否则编译失败auto不能⽤作函数参数在类中auto不能⽤作⾮静态成员…

MYSQL数字函数:不可不知的数据处理利器

&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自&#xff1a;MYSQL数字函数&#xff1a;不可不知的数据处理利器 ✨​​​​​…

快速上手Spring Cloud 十五:与人工智能的智慧交融

快速上手Spring Cloud 一&#xff1a;Spring Cloud 简介 快速上手Spring Cloud 二&#xff1a;核心组件解析 快速上手Spring Cloud 三&#xff1a;API网关深入探索与实战应用 快速上手Spring Cloud 四&#xff1a;微服务治理与安全 快速上手Spring Cloud 五&#xff1a;Spring …

使用pytorch构建一个无监督的深度卷积GAN网络模型

本文为此系列的第二篇DCGAN&#xff0c;上一篇为初级的GAN。普通GAN有训练不稳定、容易陷入局部最优等问题&#xff0c;DCGAN相对于普通GAN的优点是能够生成更加逼真、清晰的图像。 因为DCGAN是在GAN的基础上的改造&#xff0c;所以本篇只针对GAN的改造点进行讲解&#xff0c;其…

Pytorch的hook函数

hook函数是勾子函数&#xff0c;用于在不改变原始模型结构的情况下&#xff0c;注入一些新的代码用于调试和检验模型&#xff0c;常见的用法有保留非叶子结点的梯度数据&#xff08;Pytorch的非叶子节点的梯度数据在计算完毕之后就会被删除&#xff0c;访问的时候会显示为None&…

RegSeg 学习笔记(待完善)

论文阅读 解决的问题 引用别的论文的内容 可以用 controlf 寻找想要的内容 PPM 空间金字塔池化改进 SPP / SPPF / SimSPPF / ASPP / RFB / SPPCSPC / SPPFCSPC / SPPELAN &#xfffc; ASPP STDC&#xff1a;short-term dense concatenate module 和 DDRNet SE-ResNeXt …