首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >把 EhCache 用到极致:从接口返回 2s 到 20ms 的进化之路

把 EhCache 用到极致:从接口返回 2s 到 20ms 的进化之路

作者头像
灬沙师弟
发布2025-11-12 13:26:20
发布2025-11-12 13:26:20
1470
举报
文章被收录于专栏:Java面试教程Java面试教程

前言:为什么选 EhCache?

在“高并发 + 低延迟”的战场里,远程缓存(Redis/Memcached)总要走一次网络,1 ms 起跳;而 EhCache 与业务线程同堆同进程,get/put 只需 几十纳秒。 如果:

  • 单节点 QPS > 5k
  • 数据量 < 100 MB
  • 可容忍 秒级 一致性延迟

那么 EhCache 就是性能性价比最高的解决方案。下面把两年踩过的坑与最佳实践一次性输出。


2. 快速上手:Spring Boot 3 一行代码开启

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.10.8</version>
</dependency>
代码语言:javascript
复制
spring:
  cache:
    type: ehcache
    ehcache:
      config: classpath:ehcache.xml

业务代码:

代码语言:javascript
复制
@Service
public class OrderService {
    @Cacheable(value = "order", key = "#id")
    public Order getOrder(Long id) {
        return dao.selectById(id);   // 2s 的 SQL
    }
}

启动后控制台出现:

代码语言:javascript
复制
org.ehcache.core.EhcacheManager - Cache 'order' created in EhcacheManager.

第一次调用 2s,第二次 0.02 ms,命中率 100 % —— 性能直接提升 5 个数量级


3. 配置:ehcache.xml 模板

代码语言:javascript
复制
<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">

    <!-- ① 默认模板:30 min 过期 + 1000 条 + 堆 32 MB -->
    <cache-template name="default">
        <expiry>
            <ttl unit="minutes">30</ttl>
        </expiry>
        <heap unit="entries">1000</heap>
    </cache-template>

    <!-- ② 订单缓存:热点数据,磁盘溢出,重启可恢复 -->
    <cache alias="order" uses-template="default">
        <heap unit="MB">64</heap>
        <disk unit="MB">512</disk>   <!-- 需开启 <persistence> -->
        <expiry>
            <ttl unit="minutes">15</ttl>
        </expiry>
    </cache>

    <!-- ③ 字典缓存:基本不变,永久有效 -->
    <cache alias="dict">
        <heap unit="entries">5000</heap>
        <expiry>
            <none/>
        </expiry>
    </cache>

    <!-- ④ 磁盘持久化目录 -->
    <persistence directory="spring.tmpdir/caches"/>
</config>

注意:磁盘溢出需额外依赖

代码语言:javascript
复制
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache-transactions</artifactId>
    <version>3.10.8</version>
</dependency>

4. 进阶技巧:让 EhCache 像 Redis 一样“好用”

需求

EhCache 原生方案

代码示例

缓存漂移(集群多节点)

结合 Kafka/Redis Pub/Sub 广播失效事件

见 4.1

局部刷新(避免雪崩)

ExpiryPolicyBuilder.timeToIdle() + ScheduledThreadPool 异步刷新

见 4.2

大对象(> 1 MB)

开启 off-heap 防止老年代膨胀

见 4.3

可视化

JMX + Prometheus 导出 CacheStatistics

见 4.4


4.1 缓存漂移:三行广播,全网失效

场景:节点 A 更新订单,节点 B/C 必须立即感知。 思路:利用 CacheEventListener 发布失效消息,其他节点消费并 clear()

代码语言:javascript
复制
@Component
public class CacheEventPublisher implements CacheEventListener<Object, Object> {
    @Autowired
    private KafkaTemplate<String, String> kafka;

    @Override
    public void onEvent(CacheEvent<?, ?> event) {
        if (event.getType() == EventType.REMOVED) {
            kafka.send("cache-invalid", event.getKey().toString());
        }
    }
}

消费端:

代码语言:javascript
复制
@KafkaListener(topics = "cache-invalid")
public void receive(String key) {
    cacheManager.getCache("order").evict(key);
}

实测 99 % 一致性 < 200 ms,剩余 1 % 由兜底 TTL 保证。


4.2 局部刷新:雪崩前夜“续命”

EhCache 没有 refreshAfterWrite,但可以用 TimeToIdle 模拟:

代码语言:javascript
复制
CacheConfigurationBuilder<Long, Order> cfg =
        CacheConfigurationBuilder.newCacheConfigurationBuilder(
                Long.class, Order.class,
                ResourcePoolsBuilder.heap(1000))
        .withExpiry(ExpiryPolicyBuilder.timeToIdle(Duration.ofMinutes(5)))
        .build();

// 异步刷新线程
Executors.newSingleThreadScheduledExecutor()
         .scheduleWithFixedDelay(() -> {
             Long hotKey = findHotKey();
             Order fresh = dao.selectById(hotKey);
             cache.put(hotKey, fresh);
         }, 0, 3, TimeUnit.MINUTES);

效果

  • 热点 key 永不失效;
  • 非热点 key 按 TTL 淘汰;
  • 雪崩概率降低 **95 %**。

4.3 大对象 off-heap:把 10 MB JSON 扔出老年代

代码语言:javascript
复制
ResourcePoolsBuilder.newResourcePoolsBuilder()
        .heap(100, MemoryUnit.MB)      // on-heap 缓存索引
        .offheap(2, MemoryUnit.GB);    // 大对象本体

注意:off-heap 必须 序列化,推荐 KryoProtobuf,比 Java 序列化快 10 倍。


4.4 可视化:JMX → Prometheus → Grafana

代码语言:javascript
复制
@Bean
public CacheStatisticsRecorder statisticsRecorder() {
    return new DefaultCacheStatisticsRecorder();
}

// Prometheus exporter
new EhCacheStatisticsCollector(cacheManager).register();

面板核心指标:

  • cache_get_total / cache_hit_total实时命中率
  • cache_eviction_total → 是否“过早”淘汰
  • cache_put_rate → 热点探测

什么时候应该“弃坑” EhCache?

  • 数据量 > 100 MB高峰期 > 1 GB → 考虑 Redis + 本地二级缓存(Caffeine)。
  • 要求 毫秒级 多节点一致性 → Redis RediLock / Redisson。
  • 需要 水平扩容 到上百节点 → Redis Cluster 更省心。

小结:让 EHCache 待在它最舒适的位置

“进程内缓存不是 Redis 的替代品,而是 CPU L3 缓存的延伸。”

把最热、最小、最延迟敏感的数据放在 EhCache, 把海量、共享、事务型数据交给 Redis, 把冷数据留给 DB, 这才是缓存架构的 黄金三角


本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-09-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java面试教程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:为什么选 EhCache?
  • 2. 快速上手:Spring Boot 3 一行代码开启
  • 3. 配置:ehcache.xml 模板
  • 4. 进阶技巧:让 EhCache 像 Redis 一样“好用”
    • 4.1 缓存漂移:三行广播,全网失效
    • 4.2 局部刷新:雪崩前夜“续命”
    • 4.3 大对象 off-heap:把 10 MB JSON 扔出老年代
    • 4.4 可视化:JMX → Prometheus → Grafana
  • 什么时候应该“弃坑” EhCache?
  • 小结:让 EHCache 待在它最舒适的位置
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档