JAVA中使用最广泛的本地缓存?Ehcache的自信从何而来 —— 感受来自Ehcache的强大实力

news2025/1/15 17:15:37

大家好,又见面了。

作为《深入理解缓存原理与实战设计》系列专栏,前面几篇文章中我们详细的介绍与探讨了Guava CacheCaffeine的实现、特性与使用方式。提到JAVA本地缓存框架,还有一个同样无法被忽视的强大存在 —— Ehcache!它最初是由Greg Luck于2003年开始开发,截止目前,Ehcache已经演进到了3.10.0版本,各方面的能力已经构建的非常完善。Ehcache官网上也毫不谦虚的描述自己是“Java's most widely-used cache”,即JAVA中使用最广泛的缓存,足见Ehcache的强大与自信。

此外,Ehcache还是被Hibernate选中并默认集成的缓存框架,它究竟有什么魅力可以让著名的Hibernate对其青眼有加?它与Caffeine又有啥区别呢?我们实际的业务项目里又该如何取舍呢?带着这些疑问,接下来就来认识下Ehcache,一睹Ehcache那些闪闪发光的优秀特性吧!

Ehcache的闪光特性

支持多级缓存

之前文章中我们介绍过的Guava Cache或者是Caffeine,都是纯内存缓存,使用上会受到内存大小的制约,而Ehcache则打破了这一约束。Ehcache2.x时代就已经支持了基于内存磁盘的二级缓存能力,而演进到Ehcache3.x版本时进一步扩展了此部分能力,增加了对于堆外缓存的支持。此外,结合Ehcache原生支持的集群能力,又可以打破单机的限制,完全解决容量这一制约因素。

综合而言,Ehcache支持的缓存形式就有了如下四种:

  • 堆内缓存(heap)

所谓的堆内(heap)缓存,就是我们常规意义上说的内存缓存,严格意义上来说,是指被JVM托管占用的部分内存。内存缓存最大的优势就是具有超快的读写速度,但是不足点就在于容量有限、且无法持久化

在创建缓存的时候可以指定使用堆内缓存,也可以一并指定堆内缓存允许的最大字节数

// 指定使用堆内缓存,并限制最大容量为100M
ResourcePoolsBuilder.newResourcePoolsBuilder().heap(100, MemoryUnit.MB);
复制代码

除了按照总字节大小限制,还可以按照记录数进行约束:

// 指定使用堆内缓存,并限制最大容量为100个Entity记录
ResourcePoolsBuilder.newResourcePoolsBuilder().heap(100, EntryUnit.ENTRIES);
复制代码
  • 堆外缓存(off-heap)

堆外(off-heap)缓存,同样是存储在内存中。其实就是在内存中开辟一块区域,将其当做磁盘进行使用。由于内存的读写速度特别快,所以将数据存储在这个区域,读写上可以获得比本地磁盘读取更优的表现。这里的“堆外”,主要是相对与JVM的堆内存而言的,因为这个区域不在JVM的堆内存中,所以叫堆外缓存。这块的关系如下图示意:

看到这里,不知道大家是否有这么个疑问:既然都是内存中存储,那为何多此一举非要将其划分为堆外缓存呢?直接将这部分的空间类驾到堆内缓存上,不是一样的效果吗?

我们知道JVM会基于GC机制自动的对内存中不再使用的对象进行垃圾回收,而GC的时候对系统性能的影响是非常大的。堆内缓存的数据越多,GC的压力就会越大,对系统性能的影响也会越明显。所以为了降低大量缓存对象的GC回收动作的影响,便出现了off-heap处理方式。在JVM堆外的内存中开辟一块空间,可以像使用本地磁盘一样去使用这块内存区域,这样就既享受了内存的高速读写能力,又避免频繁GC带来的烦恼。

可以在创建缓存的时候,通过offheap方法来指定使用堆外缓存并设定堆外缓存的容量大小,这样当heap缓存容量满之后,其余的数据便会存储到堆外缓存中。

ResourcePoolsBuilder.newResourcePoolsBuilder()
        .heap(100, MemoryUnit.KB) // 堆内缓存100K
        .offheap(10, MemoryUnit.MB); // 堆外缓存10M
复制代码

堆外缓存的时候,offheap的大小设定需要注意两个原则:

  1. offheap需要大于heap的容量大小(前提是heap大小设定的是字节数而非Entity数)
  2. offheap大小必须1M以上

如果设定的时候不满足上述条件,会报错:

Caused by: java.lang.IllegalArgumentException: The value of maxBytesLocalOffHeap is less than the minimum allowed value of 1M. Reconfigure maxBytesLocalOffHeap in ehcache.xml or programmatically.
	at org.ehcache.impl.internal.store.offheap.HeuristicConfiguration.<init>(HeuristicConfiguration.java:55)
	at org.ehcache.impl.internal.store.offheap.OffHeapStore.createBackingMap(OffHeapStore.java:102)
	at org.ehcache.impl.internal.store.offheap.OffHeapStore.access$500(OffHeapStore.java:69)
复制代码

总结下堆内缓存与堆外缓存的区别与各自优缺点

  1. 堆内缓存是由JVM管理的,在JVM中可以直接去以引用的形式去读取,所以读写的速度会特别高。而且JVM会负责其内容的回收与清理,使用起来比较“省心”。
  2. 堆外缓存是在内存中划定了一块独立的存储区域,然后可以将这部分内存当做“磁盘”进行使用。需要使用方自行维护数据的清理,读写前需要序列化反序列化操作,但可以省去GC的影响。
  • 磁盘缓存(disk)

当我们需要缓存的数据量特别大、内存容量无法满足需求的时候,可以使用disk磁盘存储来作为补充。相比于内存,磁盘的读写速度显然要慢一些、但是胜在其价格便宜,容量可以足够大。

我们可以在缓存创建的时候,指定使用磁盘缓存,作为堆内缓存或者堆外缓存的补充。

ResourcePoolsBuilder.newResourcePoolsBuilder()
        .heap(10, MemoryUnit.MB) 
        .offheap(1, MemoryUnit.MB)
        .disk(10, MemoryUnit.GB); // 指定使用10G磁盘缓存空间
复制代码

需要注意这里磁盘的容量设定一定要大于前面的heap以及offHeap的大小,否则会报错:

Exception in thread "main" java.lang.IllegalArgumentException: Tiering Inversion: 'Pool {100 MB offheap}' is not smaller than 'Pool {20 MB disk}'
	at org.ehcache.impl.config.ResourcePoolsImpl.validateResourcePools(ResourcePoolsImpl.java:137)
	at org.ehcache.config.builders.ResourcePoolsBuilder.<init>(ResourcePoolsBuilder.java:53)
复制代码
  • 集群缓存(Cluster)

作为单机缓存,数据都是存在各个进程内的,在分布式组网系统中,如果缓存数据发生变更,就会出现各个进程节点中缓存数据不一致的问题。为了解决这一问题,Ehcache支持通过集群的方式,将多个分布式节点组网成一个整体,保证相互节点之间的数据同步。

需要注意的是,除了堆内缓存属于JVM堆内部,可以直接通过引用的方式进行访问,其余几种类型都属于JVM外部的数据交互,所以对这部分数据的读写时,需要先进行序列化反序列化,因此要求缓存的数据对象一定要支持序列化与反序列化。

不同的缓存类型具有不同的运算处理速度,堆内缓存的速度最快,堆外缓存次之,集群缓存的速度最慢。为了兼具处理性能与缓存容量,可以采用多种缓存形式组合使用的方式,构建多级缓存来实现。组合上述几种不同缓存类型然后构建多级缓存的时候,也需要遵循几个约束:

  1. 多级缓存中必须有堆内缓存,必须按照堆内缓存 < 堆外缓存 < 磁盘缓存 < 集群缓存的顺序进行组合;
  2. 多级缓存中的容量设定必须遵循堆内缓存 < 堆外缓存 < 磁盘缓存 < 集群缓存的原则;
  3. 多级缓存中不允许磁盘缓存集群缓存同时出现;

按照上述原则,可以组合出所有合法的多级缓存类型:

堆内缓存 + 堆外缓存 堆内缓存 + 堆外缓存 + 磁盘缓存 堆内缓存 + 堆外缓存 + 集群缓存 堆内缓存 + 磁盘缓存 堆内缓存 + 集群缓存

支持缓存持久化

常规的基于内存的缓存都有一个通病就是无法持久化,每次重新启动的时候,缓存数据都会丢失,需要重新去构建。而Ehcache则支持使用磁盘来对缓存内容进行持久化保存。

如果需要开启持久化保存能力,我们首先需要在创建缓存的时候先指定下持久化结果存储的磁盘根目录,然后需要指定组合使用磁盘存储的容量,并选择开启持久化数据的能力。

public static void main(String[] args) {
    CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
            .withCache("myCache", CacheConfigurationBuilder.newCacheConfigurationBuilder(Integer.class,
                    String.class,
                    ResourcePoolsBuilder.newResourcePoolsBuilder()
                            .heap(1, MemoryUnit.MB)
                            .disk(10, MemoryUnit.GB, true)) // 指定需要持久化到磁盘
                    .build())
            .with(CacheManagerBuilder.persistence("d:\\myCache\\")) // 指定持久化磁盘路径
            .build(true);
    Cache<Integer, String> myCache = cacheManager.getCache("myCache", Integer.class, String.class);
    myCache.put(1, "value1");
    myCache.put(2, "value2");
    System.out.println(myCache.get(2));
    cacheManager.close();
}
复制代码

执行之后,指定的目录里面会留有对应的持久化文件记录:

这样在进程重新启动的时候,会自动从持久化文件中读取内容并加载到缓存中,可以直接使用。比如我们将代码修改下,缓存创建完成后不执行put操作,而是直接去读取数据。比如还是上面的这段代码,将put操作注释掉,重新启动执行,依旧可以获取到缓存值。

支持变身分布式缓存

在本专栏开立后的第一篇文章《聊一聊作为高并发系统基石之一的缓存,会用很简单,用好才是技术活》中,我们介绍了下在集群多节点场景下本地缓存经常会出现的一个缓存漂移问题。比如一个互动论坛系统里面,其中一个节点处理了修改请求并同步更新了自己的本地缓存,但是其余节点没有感知到这个变更操作,导致相互之间内存数据不一致,这个时候查询请求就会出现一会正常一会异常的情况。

对于分布式系统,或者是集群场景下,并非是本地缓存的主战场。为了保证集群内数据的一致性,很多场景往往就直接选择Redis集中式缓存。但是集中式缓存也弊端,比如有些数据并不怎么更新、但是每个节点对其依赖度却非常高,如果频繁地去Redis请求交互,又会导致大量的性能损耗在网络IO交互处理上。

针对这种情况,Ehcache给出了一个相对完美的答案:本地 + 集群化策略。即在本地缓存的基础上,将集群内各本地节点组成一个相互连接的网,然后基于某种机制,将一个节点上发生的变更同步给其余节点进行同步更新自身缓存数据,这样就可以实现各个节点的缓存数据一致。

Ehcache提供了多种不同的解决方案,可以将其由本地缓存变身为“分布式缓存”:

  • RMI组播方式

  • JMS消息方式

  • Cache Server模式

  • JGroup方式

  • Terracotta方式

在下一篇文章中,将专门针对上面的几种方式进行展开介绍。

更灵活和细粒度的过期时间设定

前面我们介绍过的本地缓存框架Caffeine与Guava Cache,它们支持设定过期时间,但是仅允许为设定缓存容器级别统一的过期时间,容器内的所有元素都遵循同一个过期时间。

Ehcache不仅支持缓存容器对象级别统一的过期时间设定,还会支持为容器中每一条缓存记录设定独立过期时间,允许不同记录有不同的过期时间。这在某些场景下还是非常友好的,可以指定部分热点数据一个相对较长的过期时间,避免热点数据因为过期导致的缓存击穿

同时支持JCache与SpringCache规范

Ehcache作为一个标准化构建的通用缓存框架,同时支持了JAVA目前业界最为主流的两大缓存标准,即官方的JSR107标准以及使用非常广泛的Spring Cache标准,这样使得业务中可以基于标准化的缓存接口去调用,避免了Ehcache深度耦合到业务逻辑中去。

作为当前绝对主流的Spring框架,Ehcache可以做到无缝集成,便于项目中使用。在下面的章节中会专门介绍如何与Spring进行集成,此处先不赘述。

Hibernate的默认缓存策略

Hibernate是一个著名的开源ORM框架实现,提供了对JDBC的轻量级封装实现,可以在代码中以面向对象的方式去操作数据库数据,此前著名的SSH框架中的H,指的便是Hibernate框架。Hibernate支持一二级缓存,其中一级缓存是session级别的缓存,默认开启。而Hibernate的二级缓存,默认使用的便是Ehcache来实现的。能够被大名鼎鼎的Hibernate选中作为默认的缓存实现,也可以证明Ehcache不俗的实力。

Ehcache、Caffeine、Redis如何选择

之前的文章中介绍过Caffeine的相关特性与用法,两者虽然同属JVM级别的本地缓存框架,但是两者在目标细分领域,还是各有侧重的。而作为具备分布式能力的本地缓存,Ehcache与天生的分布式集中式缓存之间似乎也存在一些功能上的重合度,那么EhcacheCaffeineRedis三者之间应该如何选择呢?先看下三者的定位:

  • Caffeine
  1. 更加轻量级,使用更加简单,可以理解为一个增强版的HashMap
  2. 足够纯粹,适用于仅需要本地缓存数据的常规场景,可以获取到绝佳的命中率与并发访问性能。
  • Redis
  1. 纯粹的集中缓存,为集群化、分布式多节点场景而生,可以保证缓存的一致性;
  2. 业务需要通过网络进行交互,相比与本地缓存而言性能上会有损耗
  • Ehcache
  1. 支持多级缓存扩展能力。通过内存+磁盘等多种存储机制,解决缓存容量问题,适合本地缓存中对容量有特别要求的场景;
  2. 支持缓存数据持久化操作。允许将内存中的缓存数据持久化到磁盘上,进程启动的时候从磁盘加载到内存中;
  3. 支持多节点集群化组网。可以将分布式场景下的各个节点组成集群,实现缓存数据一致,解决缓存漂移问题。

相比而言,Caffeine专注于提供纯粹且简单的本地基础缓存能力、Redis则聚焦统一缓存的数据一致性方面,而Ehcache的功能则是更为的中庸,介于两者之间,既具有本地缓存无可比拟的性能优势,又兼具分布式缓存的多节点数据一致性与容量扩展能力。项目里面进行选型的时候,可以结合上面的差异点,评估下自己的实际诉求,决定如何选择。

简单来说,把握如下原则即可:

  • 如果只是本地简单、少量缓存数据使用的,选择Caffeine

  • 如果本地缓存数据量较大、内存不足需要使用磁盘缓存的,选择EhCache

  • 如果是大型分布式多节点系统,业务对缓存使用较为重度,且各个节点需要依赖并频繁操作同一个缓存,选择Redis

小结回顾

好啦,关于Ehcache的一些问题关键特性,就介绍到这里了。不知道小伙伴们是否开始对Ehcache更加的感兴趣了呢?后面我们将一起来具体看下如何在项目中进行集成与使用Ehcache,充分去发掘与体验其强大之处。而关于Ehcache你是否有自己的一些想法与见解呢?欢迎评论区一起交流下,期待和各位小伙伴们一起切磋、共同成长。

📣 补充说明1

本文属于《深入理解缓存原理与实战设计》系列专栏的内容之一。该专栏围绕缓存这个宏大命题进行展开阐述,全方位、系统性地深度剖析各种缓存实现策略与原理、以及缓存的各种用法、各种问题应对策略,并一起探讨下缓存设计的哲学。

如果有兴趣,也欢迎关注此专栏。 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows同时安装两个版本JDK,并实现动态切换JAVA8或者JAVA11

一、需求 对于Java开发工程师来说&#xff0c;可能手头上同时负责不同的项目&#xff0c;但是由于历史的原因&#xff0c;Java版本可能没有做到统一升级&#xff0c;有的项目是使用JDK8版本&#xff0c;有的项目使用的是JDK11的版本&#xff0c;那这时候就需要我们电脑同时兼容…

html2canvas 行内元素边框样式生成问题解决(根据文字生成图片)

项目场景&#xff1a; 实现一个基于一段文字生成一张图片的需求&#xff0c;其中&#xff0c;有一段文字需要下划线&#xff0c;但是不是text-decoration:underline;的样式&#xff0c;因为下划线要距离字一段距离&#xff0c;接到这个方案时&#xff0c;第一时间想到的就是ht…

在x86的Docker中构建TVM的ARM环境

文章目录前言1. 加载arm-ubuntu镜像2. 安装acl库3. 编译arm运行时4. 编译在x86运行在arm4.1 在x86的环境中构建arm的编译环境4.2 测试x86-ubuntu与arm-ubuntu能否ping通4.3 调用RPC4.4 ACL的使用5. arm版的tvm编译和运行时环境5.1 构建arm版的tvm编译和运行时环境5.2 关于ubunt…

卷积版wav to image 训练实例

🍿*★,*:.☆欢迎您/$:*.★* 🍿 目录 背景 正文 总结 背景描述

Java语言知识大盘点(期末总复习)二

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

2022年珠海市第三届半导体行业集成电路测试工竞赛成功举办

11月19日&#xff0c;2022年珠海市第三届职业技能大赛暨香洲区第七届“香洲工匠”职业技能竞赛半导体行业集成电路测试工竞赛在珠海这片创新热土上成功举办&#xff0c;48支集成电路高素质技能人才队伍齐聚香江&#xff0c;同台竞技&#xff0c;碰撞出绚烂的“芯”火花。 香洲区…

Spring之Gateway网关

前言 什么是网关&#xff1f;简单理解就是我们所有服务的入口&#xff0c;当我们使用了微服务以后&#xff0c;每个服务都会有一个对应的接口&#xff0c;比如我们有用户服务&#xff0c;订单服务等等&#xff0c;如果没有网关的话&#xff0c;那么前端是这样调用的 很明显app和…

Design a TinyURL

title: Notes of System Design No.02 — Design a TinyURL date: 2022-05-05 13:23:57 tags: 系统设计 categories: 系统设计 description: " Design a TinyURL" 1.Functional Requirements 1.长链接->短链接(写) 2.短链接->长链接(读) 3.可以设置超时时间…

unittest框架

unittest框架1.通过unittest框架创建测试2.通过unittest框架添加断言3.自动化用例管理TestLoader类的用法4.unittest智能封装等待1.通过unittest框架创建测试 1.必须继承于unittest.TestCase类 2.可以定义setUp和tearDown方法进行初始化&#xff0c;每条测试用例开始或结束会执…

谷歌浏览器-chrome浏览器占用电脑CPU过高、容易崩溃的解决办法

一、问题背景 最近特别难受的一点——谷歌浏览器总是莫名其妙崩溃&#xff0c;而且明明是只开了两三个标签页的情况下。 不管是谷歌自己的任务管理器&#xff0c;还是win10自带的任务管理器&#xff1b;在崩溃情况下&#xff0c;谷歌浏览器的电脑cpu占用率高达80以上。 网上…

Java01-JDK1.8下载安装教程(win11版)

文章导航JDK 1.8 官网下载&#xff08;下载慢&#xff09;百度网盘下载&#xff08;下载快&#xff09;安装过程JDK环境配置教程验证JDK是否安装成功使用JDK1.8的原因当下互联网行情以及个人建议JDK 1.8 官网下载&#xff08;下载慢&#xff09; 点击跳转至JDK1.8官方网址 32…

暴雪和网易分手百万玩家何去何从

暴雪和网易分手百万玩家何去何从 这两天看到很多报道说网易与暴雪分手的消息&#xff0c;作为一个游戏玩家我甚是感到很意外。 看了不少相关的报道消息才有了今天的这篇文章 暴雪和其旗下《魔兽世界》等游戏陪伴了我们这一代人成长&#xff0c;或许终究不属于这个时代。看到暴…

智慧路灯解决方案-最新全套文件

智慧路灯解决方案-最新全套文件一、建设背景二、思路架构三、建设方案四、获取 - 智慧路灯全套最新解决方案合集一、建设背景 智慧城市是利用信息通信技术感知、分析、整合城市运行核心系统的各种关键信息&#xff0c;从而改善民生、环保、公共安全、城市服务、智能响应包括工…

【Java八股文总结】之反射

文章目录Java反射一、泛型1、何为泛型&#xff1f;2、泛型通配符Q&#xff1a;泛型擦除是什么&#xff1f;3、泛型上限和下限二、反射1、何为反射&#xff1f;2、反射有什么用&#xff1f;3、反射应用场景有哪些&#xff1f;Q&#xff1a;反射的优缺点&#xff1f;4、反射获取C…

三极管集电极电阻的作用

放大状态&#xff1a;电流信号转变为电压信号 饱和状态 ;发射极正偏&#xff0c;集电极反偏 当有无电阻的作用。当集电极有电阻时&#xff0c;可以得到随IC电流变化的电压信号&#xff0c;当工作在饱和状态。集电阻电阻越大。越容易进入饱和状态.当Ib有个小电流,Ic会出现大的…

Dubbo的SPI机制

目录 什么是 SPI Java SPI 示例 Java SPI 源码分析 想一下 Java SPI 哪里不好 Dubbo SPI Dubbo SPI 简单实例 Dubbo 源码分析 getExtensionClasses Adaptive 注解 - 自适应扩展 Adaptive 注解在类上 Adaptive 注解在方法上 WrapperClass - AOP injectExtension - …

webpack 官方文档解读一(详细使用教程) 起步

什么是webpack 就是个打包工具。通过一系列插件帮你优化项目&#xff0c;压缩&#xff0c;混淆等。总之什么脏活累活都能干。 入门案例 创建一个目录&#xff0c;并安装webpack和webpack-cli这两个包。webpack包是webpack本体&#xff0c;webpack-cli是他提供的工具包。 mk…

RTL8380M/RTL8382M管理型交换机系统软件操作指南二:转发表

前面介绍了端口配置,这次对转发表进行详细的描述&#xff0c;主要包括以下三方面内容&#xff1a;基础配置、转发表、删除1.1 基础配置 1.1.1 老化时间 老化时间是一个影响交换机学习进程的参数。从一个地址记录加入地址表以后开始计时&#xff0c;如果在老化时间内各端口未收…

ResNet网络详解

ResNet ResNet在2015年由微软实验室提出&#xff0c;斩获当年lmageNet竞赛中分类任务第一名&#xff0c;目标检测第一名。获得coco数据集中目标检测第一名&#xff0c;图像分割第一名。 ResNet亮点 1.超深的网络结构(突破1000层) 2.提出residual模块 3.使用Batch Normalizat…

java项目-第147期ssm社区生活超市管理系统_(spring+springmvc+mybatis+jsp)_java毕业设计_计算机毕业设计

java项目-第147期ssm社区生活超市管理系统_(springspringmvcmybatisjsp)_java毕业设计_计算机毕业设计 【源码请到资源专栏下载】 今天分享的项目是《ssm社区生活超市管理系统》 该项目分为3个角色&#xff0c;管理员、用户、供应商角色。 用户可以浏览前台商品&#xff0c;进行…