杰瑞科技汇

Ehcache 缓存如何高效使用与优化?

  1. Ehcache 简介:为什么选择它?
  2. 快速上手:一个最简单的内存缓存示例。
  3. 核心概念CacheManager, Cache, Element
  4. 配置详解:通过 ehcache.xml 进行高级配置。
  5. Spring Boot 集成:在现代应用中最常见的使用方式。
  6. 高级特性:磁盘溢出、缓存集群、事件监听等。
  7. 最佳实践与注意事项

Ehcache 简介

Ehcache 是一个纯 Java 的进程内缓存框架,它具有以下特点:

Ehcache 缓存如何高效使用与优化?-图1
(图片来源网络,侵删)
  • 成熟稳定:发展多年,被大量大型项目验证过,可靠性高。
  • 功能丰富
    • 支持内存缓存(堆内存)。
    • 支持将缓存数据溢出到磁盘,避免 OOM(内存溢出)。
    • 支持分布式缓存,可通过 RMI, JMS, Terracotta 等方式实现集群缓存。
    • 支持缓存事件监听(如创建、更新、移除)。
    • 支持多级缓存(如堆外内存 + 磁盘)。
  • 高性能:经过高度优化,读写性能优异。
  • 易用性:API 设计简单直观,并且与 Spring、Hibernate 等主流框架集成良好。
  • 版本演进
    • Ehcache 2.x:非常经典和广泛使用的版本,功能稳定,配置方式是 ehcache.xml
    • Ehcache 3.x:基于 JSR-107 (JCache) 标准,重新设计,API 和配置方式(如 config.yaml)都有较大变化,新项目推荐使用 3.x,但维护旧项目时仍会遇到 2.x。

本教程将主要介绍 Ehcache 3.x 的用法,因为它代表了未来的方向,并会简要提及 2.x 的区别。


快速上手 (Ehcache 3.x)

1 添加依赖

在你的 pom.xml (Maven) 或 build.gradle (Gradle) 中添加 Ehcache 3 的核心依赖。

Maven:

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.10.8</version> <!-- 建议使用最新稳定版 -->
</dependency>

2 编写代码

Ehcache 3 的使用非常简单,核心是 CacheManagerCache

Ehcache 缓存如何高效使用与优化?-图2
(图片来源网络,侵删)
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.expiry.ExpiryPolicy;
public class EhcacheQuickStart {
    public static void main(String[] args) throws InterruptedException {
        // 1. 创建 CacheManager
        // 使用 CacheManagerBuilder.newCacheManagerBuilder().build() 来构建一个 CacheManager
        // .build() 是懒加载的,只有在第一次使用时才会初始化
        CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();
        // 2. 初始化 CacheManager (使其可用)
        cacheManager.init();
        // 3. 创建一个名为 "userCache" 的 Cache
        // CacheConfigurationBuilder 定义了缓存的基本配置
        // ResourcePoolsBuilder 定义了缓存可以使用的资源(这里只使用堆内存,大小为 10)
        // ExpiryPolicy 定义了过期策略
        Cache<Long, String> userCache = cacheManager.createCache("userCache",
            CacheConfigurationBuilder.newCacheConfigurationBuilder(
                Long.class, // Key 类型
                String.class, // Value 类型
                ResourcePoolsBuilder.heap(10) // 堆内存,最多存 10 个元素
            ).withExpiry(ExpiryPolicy.timeToLiveExpiration(java.time.Duration.ofSeconds(10))) // 设置 TTL 为 10 秒
        );
        // 4. 使用缓存
        System.out.println("向缓存中添加数据...");
        userCache.put(1L, "Alice");
        userCache.put(2L, "Bob");
        System.out.println("从缓存中获取数据...");
        String user1 = userCache.get(1L);
        System.out.println("ID 为 1 的用户是: " + user1); // 输出: Alice
        System.out.println("更新缓存中的数据...");
        userCache.put(1L, "Alice Smith");
        String user1Updated = userCache.get(1L);
        System.out.println("更新后,ID 为 1 的用户是: " + user1Updated); // 输出: Alice Smith
        // 5. 等待过期
        System.out.println("等待 12 秒,让缓存过期...");
        Thread.sleep(12000);
        String user1AfterExpiry = userCache.get(1L);
        System.out.println("12 秒后,ID 为 1 的用户是: " + user1AfterExpiry); // 输出: null,因为已经过期
        // 6. 关闭 CacheManager
        // 这会释放所有缓存资源
        cacheManager.close();
    }
}

运行这段代码,你就能直观地看到 Ehcache 的基本用法:创建缓存、存取数据、更新数据和数据过期。


核心概念

  • CacheManager (缓存管理器)

    • Ehcache 的核心入口,负责管理一个或多个 Cache 实例。
    • 它是单例的,一个应用中通常只需要一个 CacheManager
    • 负责缓存的创建、获取和生命周期管理(初始化、关闭)。
  • Cache (缓存)

    • 一个命名的、类型安全的(<K, V>)数据存储区域。
    • 你可以有多个 Cache,每个都有唯一的名称和独立的配置。
    • 它是真正存放 Element 的地方。
  • Element (缓存元素)

    Ehcache 缓存如何高效使用与优化?-图3
    (图片来源网络,侵删)
    • Ehcache 2.x 中的核心概念,代表缓存中的一条记录,包含 key、value、创建时间、访问时间等元数据。
    • 在 Ehcache 3.x 中,这个概念被弱化了,你直接操作的是 Cache<K, V> 接口,put(key, value)get(key) 的操作直接在键值对上进行,底层会自动处理 Element 的封装,这使得 API 更符合 Java 集合的常规用法。

配置详解 (ehcache.xml vs. API)

虽然 Ehcache 3.x 推荐使用 Java API 或 YAML/JSON 进行配置,但为了兼容性和灵活性,我们仍然可以使用 XML 配置文件。

1 创建 ehcache.xml

src/main/resources 目录下创建 ehcache.xml 文件。

<?xml version="1.0" encoding="UTF-8"?>
<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/v3/ehcache.xsd">
    <!-- 定义一个名为 "userCache" 的缓存 -->
    <cache alias="userCache">
        <!-- key 和 value 的序列化方式 -->
        <key-type>java.lang.Long</key-type>
        <value-type>java.lang.String</value-type>
        <!-- 定义资源池 -->
        <resources>
            <!-- 堆内内存,最多 200 个元素 -->
            <heap unit="entries">200</heap>
            <!-- 磁盘持久化,当堆内存满了,会溢出到磁盘上,最多 100MB -->
            <offheap unit="MB">100</offheap>
            <!-- 磁盘存储,路径为系统临时目录下的 "myData" 目录 -->
            <disk unit="GB">1</disk>
        </resources>
        <!-- 过期策略 -->
        <expiry>
            <!-- 创建后 10 分钟过期 -->
            <ttl unit="minutes">10</ttl>
        </expiry>
    </cache>
    <!-- 再定义一个简单的缓存 -->
    <cache alias="simpleCache">
        <key-type>java.lang.String</key-type>
        <value-type>java.lang.Integer</value-type>
        <resources>
            <heap unit="entries">500</heap>
        </resources>
    </cache>
</config>

2 在代码中加载 XML 配置

// 从 classpath 加载 ehcache.xml 文件
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
    .with(CacheManagerBuilder.configurationFromFile("ehcache.xml"))
    .build();
cacheManager.init();
// 现在可以直接获取在 XML 中定义的缓存了
Cache<Long, String> userCache = cacheManager.getCache("userCache", Long.class, String.class);
Cache<String, Integer> simpleCache = cacheManager.getCache("simpleCache", String.class, Integer.class);

API vs. XML 配置对比

  • API 配置:代码化,类型安全,易于动态调整,适合单元测试。
  • XML/YAML 配置:将配置与代码分离,运维人员可以修改配置而无需重新编译代码,适合生产环境。

在实际开发中,推荐使用 API,因为它更灵活且易于维护,Spring Boot 集成就是最好的例子。


Spring Boot 集成 (强烈推荐)

在 Spring Boot 应用中使用 Ehcache 非常简单,官方提供了 spring-boot-starter-cachespring-boot-starter-cache

1 添加依赖

<!-- Spring Boot Cache 抽象层 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Ehcache 3 实现 -->
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>

2 启用缓存

在你的主启动类上添加 @EnableCaching 注解。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching // 启用 Spring 缓存功能
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

3 配置 Ehcache

src/main/resources/application.ymlapplication.properties 中配置 Ehcache。

使用 application.yml (推荐):

spring:
  cache:
    type: ehcache
    ehcache:
      config: classpath:ehcache-spring.xml # 指定 Ehcache 的配置文件路径

或者使用 application.properties:

spring.cache.type=ehcache
spring.cache.ehcache.config=classpath:ehcache-spring.xml

4 创建 Ehcache 配置文件

src/main/resources 下创建 ehcache-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<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/v3/ehcache.xsd">
    <!-- 默认缓存配置,所有未显式声明的缓存都将使用此配置 -->
    <cache-template name="defaultCache">
        <expiry>
            <ttl unit="minutes">30</ttl>
        </expiry>
        <heap unit="entries">1000</heap>
    </cache-template>
    <!-- 为特定缓存使用默认模板 -->
    <cache alias="users" uses-template="defaultCache"/>
    <cache alias="products" uses-template="defaultCache">
        <heap unit="entries">5000</heap>
    </cache>
</config>

5 在 Service 中使用缓存注解

现在你可以在你的 Service 方法上使用 Spring 的缓存注解了。

import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
@CacheConfig(cacheNames = "users") // 类级别缓存配置,指定默认的缓存名
public class UserService {
    // @Cacheable: 在方法执行前,会根据 key 查找缓存。
    // 如果找到,则直接返回缓存结果,不执行方法体。
    // 如果没找到,则执行方法体,并将返回结果存入缓存。
    @Cacheable(key = "#id")
    public User getUserById(Long id) {
        System.out.println("正在从数据库查询用户 ID: " + id);
        // 模拟数据库查询
        return new User(id, "User-" + id);
    }
    // @CachePut: 每次都会执行方法体,并将返回结果更新/放入缓存。
    // 主要用于更新操作。
    @CachePut(key = "#user.id")
    public User updateUser(User user) {
        System.out.println("正在更新用户: " + user);
        // 模拟数据库更新
        return user;
    }
    // @CacheEvict: 执行方法体,并根据 key 从缓存中移除数据。
    // 主要用于删除操作。
    @CacheEvict(key = "#id")
    public void deleteUser(Long id) {
        System.out.println("正在删除用户 ID: " + id);
        // 模拟数据库删除
    }
    // @CacheEvict 的 allEntries 属性,表示清空整个缓存
    @CacheEvict(allEntries = true)
    public void clearAllUsers() {
        System.out.println("正在清空所有用户缓存");
    }
}

注解说明

  • @Cacheable: 查询缓存,有则返回,无则执行并存入。
  • @CachePut: 更新缓存,每次都执行并存入。
  • @CacheEvict: 删除缓存,执行方法后删除指定 key 或全部缓存。
  • @CacheConfig: 类级别注解,可以统一指定 cacheNameskeyGenerator 等,避免在每个方法上重复。

高级特性

  • 磁盘溢出:通过在 resources 配置中添加 diskoffheap,当堆内存不足时,Ehcache 会自动将不常用的数据移出堆内存,防止 OOM。offheap (堆外内存) 比 disk (磁盘) 速度快得多。
  • 事件监听:可以为缓存添加事件监听器,在元素被创建、更新、移除或过期时执行自定义逻辑。
    // API 方式
    cache.getRuntimeConfiguration().registerCacheEventListener(new MyCacheEventListener(), EventType.CREATED, EventType.UPDATED);
  • 缓存集群/分布式:Ehcache 提供了企业级解决方案 Ehcache Clustered (基于 Terracotta),可以将缓存数据在多个节点之间同步,实现高可用和负载均衡,这是一个商业产品,功能强大。
  • JSR-107 (JCache) 支持:Ehcache 3.x 是 JSR-107 的参考实现,这意味着你不仅可以用 Ehcache 的原生 API,还可以使用标准的 JCache API (javax.cache),这提高了代码的可移植性。

最佳实践与注意事项

  1. Key 的设计:Key 应该是简单、不可变且高效的,推荐使用基本类型、String 或简单的 POJO,避免使用复杂的对象作为 Key,并确保其 hashCode()equals() 方法实现正确。
  2. Value 的大小:缓存的是“计算结果”或“频繁访问的数据”,而不是“大对象”,过大的 Value 会消耗大量内存,并降低缓存效率。
  3. 合理设置过期时间:为不同类型的数据设置合适的 TTL,热点数据可以设置长一点,会话信息可以设置短一点。
  4. 监控:对缓存进行监控至关重要,监控命中率、内存使用情况、元素数量等指标,可以帮助你判断缓存是否有效,并及时调整配置。
  5. 序列化:Ehcache 3.x 默认使用 Java 序列化,对于性能要求高的场景,可以考虑使用更高效的序列化方式,如 Kryo、Protostuff 或 Jackson。
  6. CacheManager 的生命周期:确保在应用关闭时(如 Spring 的 @PreDestroy 方法中)调用 cacheManager.close(),以释放资源。
  7. 选择合适的版本
    • 新项目:直接使用 Ehcache 3.x,并优先考虑与 Spring Boot 集成。
    • 维护旧项目:如果项目使用的是 Ehcache 2.x,并且没有升级的迫切需求,可以继续使用它,因为它非常稳定,升级到 3.x 需要修改 API 和配置方式。

希望这份详细的指南能帮助你全面掌握 Java 中使用 Ehcache 的方法!

分享:
扫描分享到社交APP
上一篇
下一篇