EhCache

一、EhCache介绍

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

但是MySQL这种关系型数据库查询数据相对比较慢,因为有磁盘IO,或者是全盘扫描的风险,在针对一些热点数据时,会对MySQL造成比较大的压力,此时我们可以采用缓存的方式来解决。

而缓存又分为很多种,相对服务端角度来说,可以采用Redis和JVM这两种方式。

Redis不必多说,直接基于基于内存读写,并发读写的并发能力特别强,所以很多时间,在分布式或者微服务的项目中,为了保证数据一致性,我们会采用Redis来实现缓存。

但是在一些单体项目,我们可以采用JVM级别的缓存,比如直接采用框架自带的,例如Hibernate的缓存,MyBatis的缓存,或者是Guava提供的Cache,以及今儿要玩的EhCache。

还有一种情况可以采用JVM缓存,在分布式环境下,如果并发特别大,Redis也扛不住,这是我们可以将数据平均的分散在各个节点的JVM缓存中,并且设置一个较短的生存时间,这样就可以减缓Redis的压力,从而解决热点数据Redis扛不住的问题

同时EhCache也是Hibernate框架默认使用的缓存组件实现二级缓存。类似MyBatis,就直接用的HashMap。

二、EhCache基本使用

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

通过后缀就可以看出EhCache是开源的组件。

EhCache除了开源,还有可以几乎0成本和Spring整合的有点,毕竟现在Java项目大多都是基于Spring方式构建的,这也可以让我们在使用EhCache的时候更加方便。

这里还是单独的使用EhCache来感受一下,其实使用方式和HashMap的put和get的方式类似,不过EhCache提供了更加丰富的功能。

EhCache有2.x和3.x两个常用的大版本,两个版本API差异巨大,这里咱们以3.x为讲解的内容应用

官方入门文档:

https://www.ehcache.org/documentation/3.10/getting-started.html

导入依赖,走你~

<dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId><version>3.8.1</version></dependency>

直接编写快速入门代码

publicstaticvoidmain(String[] args){
        //1\. 构建核心组件,CacheManager
        CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
                // 设置缓存别名
                .withCache("cache1",
                        CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class,Object.class, ResourcePoolsBuilder.heap(1)))  // 同时指定缓存的key-value类型,以及缓存容纳的个数
                .build();
        //2\. 初始化走你
        cacheManager.init();

        //3\. 获取Cache方式一。从缓存管理器拿到设置好的Cache
        Cache<String, Object> cache1 = cacheManager.getCache("cache1", String.class, Object.class);

        //4\. 获取Cache方式二。 基于缓存管理器构建一个Cache
        Cache<String, Object> cache2 = cacheManager.createCache("cache2", CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Object.class, ResourcePoolsBuilder.heap(5)));

        //5\. 操作
        cache1.put("cache0","阿巴阿巴~~");
        Object value1 = cache1.get("cache0");
        System.out.println(value1);

    }

EhCache也提供了xml的配置方式,不过现在SpringBoot项目居多,大多是没有xml配置信息的,所以这里核心以Java编码的方式配置

三、EhCache详细配置

首先在获取Cache时,可以提升指定好几个信息,依次把核心的配置搞一下

3.1 数据内存位置

EhCache3.0中不但提供了head的堆内存储方式,还提供了堆外存储以及磁盘存储

heap堆内内存:

heap表示使用堆内内存,heap(10L)表示只能存放put10个对象,当put第11个那么前面10个对象将有一个会被移除。

off-heap堆外内存:

off-heap叫做堆外内存,将你的对象从堆中脱离出来序列化,然后存储在一大块内存中,这就像它存储到磁盘上一样,但它仍然在RAM中。对象在这种状态下不能直接使用,它们必须首先反序列化,也不受垃圾收集。序列化和反序列化将会影响部分性能,使用堆外内存能够降低GC导致的暂停。

disk写到磁盘的内存:

disk表示将对象写到磁盘中,这样有个好处是当服务重启时可以直接读取磁盘上面的内容将数据加载到服务中。

可以在设置Cache对象时指定缓存的方式

EhCache提供了三种组合来实现多级缓存的效果

  • heap + off-heap
  • heap + disk
  • heap + off-head + disk
fe7ae3108cf8425aa4edcfcaacc10a64.png

在存储数据时,最外层的缓存是数据最全面的,而heap就属于临时存储的效果

但是heap中的性能最快的,因为无论是堆外内存还是磁盘存储,都需要对数据进行序列化和反序列化,不过其速度其实都很快。

在存储缓存数据时,数据会落到所有设置到的存储位置,获取的时候自然是由快到慢的查询方式

publicstaticvoidmain(String[] args){
    // 设置disk存储的路径前缀
    String path = "D:\\";
    // 声明cacheManager,并指定heap,off-heap以及磁盘存储方式
    CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
            .with(CacheManagerBuilder.persistence(new File(path, "data")))
            .withCache("item",CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
                            ResourcePoolsBuilder.newResourcePoolsBuilder()
//                                        .heap(1)
                                    .heap(1,MemoryUnit.MB)
                                    .offheap(2, MemoryUnit.MB)
                                    .disk(3, MemoryUnit.MB, true))
            )
            .build(true);  // 这里的true,相当于执行了iniy方法// 获取缓存Cache
    Cache<Long, String> cache = cacheManager.getCache("item", Long.class, String.class);
    // 存储数据//        cache.put(1L, UUID.randomUUID().toString());// 获取数据
    System.out.println(cache.get(1L));
    // 手动close,cacheManager,不然不会持久到磁盘
    cacheManager.close();
}

还需要注意,前置缓存空间必须要小于后置缓存,比如heap要比off-heap小,off-heap要比disk小

在本地磁盘也可以看到持久化的数据,分为很多中

  • meta元数据主要存放对应cache的信息,主要是数据类型和构建时间
  • data就是持久化到本地的数据啦,乱码看不懂滴干活
  • index类似指向data数据的具体索引信息

3.2 设置缓存的生存时间

大家熟悉的Redis中是可以设置key的生存时间的,不然长时间只吃不吐必然会内存溢出,EhCache也是这个情况,所以EhCache提供了给缓存设置生存时间的方式,一共有两种方式

publicstaticvoidmain(String[] args){
    //1\. 构建CacheManager
    CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();

    //2\. 构建Cache并设置特性
    cacheManager.createCache("item",CacheConfigurationBuilder
            .newCacheConfigurationBuilder(String.class,Object.class,ResourcePoolsBuilder.heap(10))
            .withExpiry(ExpiryPolicyBuilder.noExpiration()).build()                 // 不设置生存时间,一直活着
            .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMillis(20000))).build())     // 从缓存存储开始计算,生存多久
            .withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofSeconds(10))).build();       // 从缓存最后一次使用开始计算,生存多久
}

四、SpringBoot整合EhCache配置

单独使用EhCache需要考虑的内容还是比较多的,所以可以直接用SpringBoot整合EhCache,使用起来就更加方便。

首先SpringBoot是支持EhCache的,所以整体配置成本不高

4.1 导入依赖

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

4.2 准备EhCache配置项

# 准备EhCache缓存信息ehcache:heap:1000offheap:10disk:500diskDir:D:/data/cacheNames:-user-item

Java类引入

@Component@ConfigurationProperties("ehcache")publicclassEhCacheProps{
    privateint heap;

    privateint offheap;

    privateint disk;

    private String diskDir;

    private Set<String> cacheNames;
}

4.3 配置EhCache

因为SpringBoot默认在Cache的位置采用Redis,使用EhCache需要手动设置配置信息

并且EhCache涉及到offheap和disk时,需要序列化,那么存储的value值需要可以被序列化,直接采用Object会出问题,所以需要单独设置value的基类实现序列化接口

  • value的基类

    publicclassBaseObjectimplementsSerializable{
    }

  • 设置EhCache的CacheManager

    @Configuration@EnableCachingpublicclassEhCacheConfig{

    @Autowiredprivate EhCacheProps ehCacheProps;
    
      @Beanpublic CacheManager ehcacheManager(){
          Set<String> cacheNames = ehCacheProps.getCacheNames();
          // 设置缓存存放策略
          ResourcePoolsBuilder resourcePoolsBuilder = ResourcePoolsBuilder.newResourcePoolsBuilder()
                  // 堆内缓存大小
                  .heap(ehCacheProps.getHeap())
                  // 堆外缓存大小
                  .offheap(ehCacheProps.getOffheap(), MemoryUnit.MB)
                  // 磁盘缓存大小
                  .disk(ehCacheProps.getDisk(), MemoryUnit.MB);
    
          // 设置生存时间
          ExpiryPolicy expiryPolicy = ExpiryPolicyBuilder.noExpiration();
    
          // 设置配置项
          CacheConfiguration config = CacheConfigurationBuilder
                  .newCacheConfigurationBuilder(String.class, BaseObject.class, resourcePoolsBuilder)
                  .withExpiry(expiryPolicy)
                  .build();
    
          // 设置持久化位置
          CacheManagerBuilder<PersistentCacheManager> cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder()
                  .with(CacheManagerBuilder.persistence(ehCacheProps.getDiskDir()));
    
          // 设置缓存名称信息for (String cacheName : cacheNames) {
              cacheManagerBuilder.withCache(cacheName,config);
          }
    
          return cacheManagerBuilder.build();
      }
    
    

    }

配置好之后,就会采用EhCache去缓存数据了,后面采用Java规范中的Cache注解即可。

五、Cache注解使用

Cache注解是JSR-107规范中的,Spring在3.1版本后就已经支持了Cache注解。咋前面的配置搞定之后,一般咱们就可以在Service层追加好注解,来标识数据查询的方式,以及更新的手段。

前面配置好了CacheManager,而且在CacheManager中也有管理好的Cache对象,在使用时,只需要指定好采用哪种Cache注解即可。

5.1 @Cacheable

这个是命中缓存的注解,也就是在查询的时候,根据当前注解查看缓存中有木有数据,木有就走业务,让后扔缓存里,后面就可以命中缓存了。

5.1.1 添加位置

可以添加在方法或者是直接添加到类中

添加到类:当前类的所有方法都会走缓存的方式。很明显,没办法细粒度化配置,所以一般不用

添加到方法:针对当前方法采用上述缓存逻辑。

5.1.2 缓存存储方式

在添加注解之后,必须添加上要采用哪个cacheNames,明显可以缓存到多个cache中,当然value也是一样的。

针对添加的注解的方法,方法返回值就是要缓存的value,key的话,默认不写,就是当前方法传递的参数。

当然key也声明,或者自己指定key的生成策略

5.1.3 key的声明方式

首先自然是不指定key的信息,采用默认的内容即可。

其次是指定缓存的key,咱们可以基于spel的方式设置key是什么内容。

@Override@Cacheable(cacheNames = "item",key = "#id")public String findById(String id){
    System.out.println("查询数据库。。。。。。。。。");
    return id;
}
@Override@Cacheable(cacheNames = "item",key = "#user.id")public String findByUser(User user){
    System.out.println("查询数据库。。。。。。。。。");
    return id;
}

SPEL也支持很多其他特殊可用值

0d02632c2c7843088bf5ee55ec90dc14.png

还有自定义策略的方式声明key

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355

推荐阅读更多精彩内容