详解
下图是 Ehcache 在应用程序中的位置:
Ecache 是一个广泛使用的 Java 缓存框架,能够有效提升应用性能,并减少与后端数据库的交互次数。它采用了一系列高级缓存策略,包括内存缓存、磁盘缓存、分布式缓存等,并提供了丰富的 API 和工具类,可以方便地完成缓存的读写和管理。快速:Ecache 采用了一系列高效的缓存策略,能够实现快速的数据访问和读写,从而提高应用程序的性能。可扩展:Ecache 支持分布式缓存,可以方便地扩展到多台服务
一、Ehcache 简
Ehcache 是一个广泛使用的 Java 缓存框架,能够有效提升应用性能,并减少与后端数据库的交互次数。它采用了一系列高级缓存策略,包括内存缓存、磁盘缓存、分布式缓存等,并提供了丰富的 API 和工具类,可以方便地完成缓存的读写和管理。
Ehcache 主要有以下特点:
- 快速:Ehcache 采用了一系列高效的缓存策略,能够实现快速的数据访问和读写,从而提高应用程序的性能。
- 可扩展:Ehcache 支持分布式缓存,可以方便地扩展到多台服务器上,从而提高系统的容错性和吞吐量。
- 可靠性高:Ehcache 内置了多种缓存策略,支持数据持久化和恢复,同时还提供了完善的故障检测和纠正机制,从而保证了缓存数据的可靠性和稳定性。
- 易于使用:Ehcache 提供了丰富的 API 和工具类,可以方便地完成缓存的读写和管理。同时,它还与多种框架和技术集成,如 Spring、Hibernate、MyBatis 等,使得用户可以更加便捷地使用 Ecache。
二、工作原理
1. 缓存写入
当应用程序向 Ehcache 中写入数据时,Ehcache 会首先检查该数据是否已经存在于缓存中。如果数据已经存在于缓存中,则更新该数据;否则,将该数据写入缓存。以下是 Ehcache 缓存写入的详细流程。
-
当应用程序请求写入一个数据项到 Ehcache 中时,这个数据项被传递给Ehcache API。
-
Ehcache首先根据该数据项的键值对定位其对应的Cache对象。
-
Ehcache根据配置中的缓存策略,比如是否应该在缓存中创建一个新的元素,以及新元素是否会淘汰老的元素。
-
如果需要创建一个新缓存元素,则Ehcache创建一个新的元素并将其添加到Cache对象中。
-
如果缓存中已经存在该元素,Ehcache会根据缓存策略对该元素进行更新或替换。
-
Ehcache将更新后的Cache对象返回给应用程序。
2. 缓存查找
当应用程序需要查询缓存中的数据时,Ehcache 首先会检查该数据是否存在于缓存中。如果数据存在于缓存中,则直接返回缓存数据;否则,从数据库或其他资源获取数据,并将其存入缓存中。以下是 Ehcache 缓存查找的详细流程。
-
当应用程序请求从 Ehcache 中读取一个数据项时,这个请求被传递给Ehcache API。
-
Ehcache首先根据该数据项的键值对定位其对应的Cache对象。
-
Ehcache检查该数据项是否已经存在于缓存中。
-
如果数据项存在于缓存中,Ehcache可以直接将其返回给应用程序。
-
如果数据项不存在于缓存中,Ehcache就需要从数据库或其他数据源(如文件、网络等)中获取数据。
-
获取到数据后,Ehcache会将其添加到缓存中并返回给应用程序。
3. 缓存过期和驱逐
Ehcache 提供了多种缓存失效策略,例如基于时间的缓存失效、基于访问的缓存失效、基于大小的缓存失效等。当缓存数据过期或缓存空间不足时,Ehcache 会选择一部分缓存元素进行驱逐以腾出更多的内存空间。以下是 Ehcache 缓存过期和驱逐的详细流程。
-
Ehcache会周期性地扫描缓存中的元素来标记那些已经过期的元素。
-
Ehcache根据缓存策略(如基于时间、基于访问、基于大小等)判断哪些缓存元素应该被驱逐。
-
驱逐过程通常是异步执行的,Ehcache会在后台线程上执行这个操作。
4. 缓存持久化
Ehcache 还提供了缓存持久化功能,它可以将缓存中的数据持久化到磁盘或者其他数据源。在应用程序重启或者缓存失效后,Ehcache 可以从持久化存储中恢复数据,从而保证数据的完整性和可靠性。以下是 Ehcache 缓存持久化的详细流程。
-
Ehcache使用磁盘存储或数据库等持久化技术来存储缓存数据。
-
当缓存中的数据更新时,Ehcache会自动将此数据持久化到持久化存储中。
-
在应用程序重启或者缓存失效后,Ehcache会从持久化存储中读取缓存数据并重新加载到内存中。
三、Ehcache 的基本使用
Spring Boot 中集成 Ehcache 缓存代码示例。
1. 添加 Ehcache 依赖
首先需要在项目的 pom.xml
文件中添加 Ehcache 缓存依赖,如下所示:
<dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> <version>3.9.3</version> </dependency>
2. 配置 Ehcache
在 application.yml
或 application.properties
配置文件中添加 Ehcache 的配置信息,例如:
spring: ehcache: config: classpath:ehcache.xml
在上述配置中,我们指定了 Ehcache 的配置文件为 classpath:ehcache.xml
,这样 Spring Boot 在启动时会自动加载该配置文件。
接下来,需要在 src/main/resources
目录下添加 ehcache.xml
配置文件,并编写相应的 Ehcache 配置信息。例如,下面是一个简单的 Ehcache 配置文件:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.ehcache.org/v3" xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd"> <cache alias="myCache"> <key-type>java.lang.String</key-type> <value-type>java.lang.String</value-type> <expiry> <ttu value="10m"/> </expiry> <heap>100</heap> </cache> </config>
在上述配置中,我们定义了一个名为 myCache
的缓存,配置了键值类型、过期时间、最大存储数量等参数。
3. 使用 Ehcache
在完成 Ehcache 的配置后,就可以在 Spring Boot 中使用 Ehcache 缓存了。Spring Boot 提供了 @Cacheable
、@CachePut
、@CacheEvict
等注解来简化缓存操作,同时也支持基于缓存管理器的编程方式。
3.1 基于注解的缓存
首先介绍一下基于注解的缓存。Spring Boot 中,可以通过 @Cacheable
、@CachePut
和 @CacheEvict
注解来实现对缓存的读写和删除操作。
3.1.1 @Cacheable
注解
@Cacheable
注解用于指定方法的返回值可被缓存,同时也可以指定缓存的 key 和 cacheNames,例如:
@Service public class UserService { @Cacheable(cacheNames = "myCache", key = "#userId") public User getUserById(String userId) { System.out.println("get user by id: " + userId); return new User(userId, "张三"); } }
在上述代码中,我们使用 @Cacheable
注解将 getUserById()
方法返回值进行缓存,并指定了缓存的 key 为 userId
,缓存名称为 myCache
。
3.1.2 @CachePut
注解
@CachePut
注解用于更新缓存中的数据,例如:
@Service public class UserService { @CachePut(cacheNames = "myCache", key = "#user.id") public User updateUser(User user) { System.out.println("update user: " + user); return user; } }
在上述代码中,我们使用 @CachePut
注解更新了缓存中键为 user.id
的缓存数据。
3.1.3 @CacheEvict
注解
@CacheEvict
注解用于清除缓存中的数据,例如:
@Service public class UserService { @CacheEvict(cacheNames = "myCache", key = "#userId") public void deleteUserById(String userId) { System.out.println("delete user by id: " + userId); } }
在上述代码中,我们使用 @CacheEvict
注解清除了缓存中键为 userId
的缓存数据。
3.2 基于缓存管理器的编程方式
除了注解方式外,还可以基于缓存管理器的编程方式来实现对缓存的读写和删除操作。Spring Boot 中,可以使用 CacheManager
和 Cache
接口来实现缓存的管理和操作。
3.2.1 注入缓存管理器
使用基于缓存管理器的编程方式,首先需要注入使用的缓存管理器,例如:
@Service public class UserService { @Autowired private CacheManager cacheManager; }
在上述代码中,我们注入了 CacheManager
类型的 cacheManager
实例。
3.2.2 获取缓存实例
获取缓存实例的方式也很简单,可以通过缓存管理器的 getCache()
方法获取:
@Service public class UserService { @Autowired private CacheManager cacheManager; public User getUserById(String userId) { Cache cache = cacheManager.getCache("myCache"); Element element = cache.get(userId); if (element != null) { System.out.println("get user by id from cache: " + userId); return (User) element.getObjectValue(); } System.out.println("get user by id from db: " + userId); User user = new User(userId, "张三"); cache.put(new Element(userId, user)); return user; } }
在上述代码中,我们使用缓存管理器的 getCache()
方法获取名为 myCache
的缓存实例。然后,我们使用缓存实例的 get()
方法获取指定 key 对应的缓存元素,如果获取到了元素,则直接返回缓存数据;否则,从数据库中获取数据,并将其存入缓存中。
3.2.3 缓存操作
除了读取缓存数据外,我们还可以使用缓存实例的 put()
方法向缓存中添加数据,使用 remove()
方法删除缓存数据,例如:
@Service public class UserService { @Autowired private CacheManager cacheManager; public User updateUser(User user) { Cache cache = cacheManager.getCache("myCache"); cache.put(new Element(user.getId(), user)); return user; } public void deleteUserById(String userId) { Cache cache = cacheManager.getCache("myCache"); cache.remove(userId); } }
在上述代码中,我们分别使用了缓存实例的 put()
和 remove()
方法对缓存数据进行了更新和清除操作。
四、 Ehcache 的高级用法
1. 缓存策略
Ehcache 提供了多种缓存策略,可以根据实际需求选择合适的策略。其中,最常用的包括:
- LRU(Least Recently Used):移除最近最少使用的缓存项。
- LFU(Least Frequently Used):移除最不经常使用的缓存项。
- FIFO(First In, First Out):先进先出,即移除最早加入的缓存项。
- TTL(Time To Live):根据缓存项的过期时间来判断是否要移除该项。
- 随机替换:随机选择一项进行移除。
在 Ehcache 中,默认使用的是 LRU 策略。如果需要更改策略,可以在 ehcache.xml
配置文件中进行设置。
2. 内存缓存和磁盘缓存
Ehcache 支持将缓存数据同时存储在内存和磁盘上,并根据实际情况调整数据的存储位置。例如,可以将经常访问的缓存数据存储在内存中,而不常使用的数据则存储在磁盘上,从而达到提高缓存效率和降低成本的目的。
在 ehcache.xml
配置文件中,可以通过 <heap>
、<offheap>
和 <disk>
标签来定义缓存存储的位置。例如,下面的配置将缓存数据存储在内存中,并限制最大存储数量为 100 个:
<cache alias="myCache"> <key-type>java.lang.String</key-type> <value-type>java.lang.String</value-type> <heap>100</heap> </cache>
如果需要将部分缓存数据存储在磁盘上,可以添加如下配置:
<cache alias="myCache"> <key-type>java.lang.String</key-type> <value-type>java.lang.String</value-type> <heap>10</heap> <!-- 最多存储 10 个缓存项到堆中 --> <offheap>1MB</offheap> <!-- 最多存储 1MB 缓存项到堆外内存中 --> <diskPersistent>true</diskPersistent> <!-- 开启磁盘持久化,即使程序重启后,缓存仍然有效 --> <diskSpoolBufferSizeMB>30</diskSpoolBufferSizeMB> <!-- 磁盘缓存区大小,缓存数据先以临时文件的形式写入磁盘缓存区,待达到一定阈值后再写入磁盘 --> <diskExpiryThreadIntervalSeconds>120</diskExpiryThreadIntervalSeconds> <!-- 磁盘数据过期检测间隔时间,单位为秒 --> <maxEntriesLocalDisk>1000</maxEntriesLocalDisk> <!-- 磁盘缓存最大存储数量 --> </cache>
3. 分布式缓存
Ehcache 提供了分布式缓存的支持,并可以通过 RMI、JMS、Spring 等方式进行实现。在分布式环境下,每个缓存节点都可以通过网络访问远程节点上的缓存数据,从而实现数据共享和分布式缓存的目的。
在 ehcache.xml
配置文件中,可以添加 <cacheManagerPeerProviderFactory>
和 <cacheManagerPeerListenerFactory>
标签来定义缓存节点的配置信息。例如,下面的配置定义了一个名为 myCache
的缓存,采用了 RMI 方式实现分布式缓存:
<cache-manager-peer-provider-factory class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory" properties="peerDiscovery=automatic, multicastGroupAddress=230.0.0.1, multicastGroupPort=4446, timeToLive=32"/> <cache-manager-peer-listener-factory class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory" properties="port=40001, socketTimeoutMillis=2000"/> <cache alias="myCache"> <key-type>java.lang.String</key-type> <value-type>java.lang.String</value-type> <diskPersistent>true</diskPersistent> <diskSpoolBufferSizeMB>30</diskSpoolBufferSizeMB> <maxEntriesLocalDisk>1000</maxEntriesLocalDisk> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true"/> </cache>
在上述配置中,我们首先定义了一个 RMI 的缓存管理器提供者工厂 RMICacheManagerPeerProviderFactory
和一个 RMI 的缓存管理器监听器工厂 RMICacheManagerPeerListenerFactory
,并分别指定了自动发现、组播地址、端口等相关参数。
接着,我们定义了名为 myCache
缓存添加了一个 cacheEventListenerFactory
属性,用于定义缓存事件的处理方式,即使用 RMICacheReplicatorFactory
实现缓存数据的复制和同步。
需要注意的是,分布式缓存的配置和使用与单机缓存有所不同,同时也需要考虑到网络通信等问题,因此更加复杂和耗费资源。在选择是否需要使用分布式缓存时,需要权衡其带来的性能、复杂性和成本等因素。