找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

6029

积分

0

好友

775

主题
发表于 3 天前 | 查看: 16| 回复: 0

背景

在高性能的服务架构设计中,缓存始终是一个不可或缺的环节。实际项目里,我们通常会把热点数据放进 Redis 或 Memcached 这类缓存中间件,只有当缓存未命中时才回源查询数据库。这样一来,既提升了访问速度,也减轻了数据库的读写压力。

但随着业务逐渐演进,单纯的远程缓存方案也开始暴露出瓶颈。某些场景下,仅仅依赖 Redis 这类远程缓存已经不够,还需要搭配 Guava Cache 或 Caffeine 等本地缓存来进一步缩短响应时间、提升服务性能。于是,本地缓存作为一级缓存、远程缓存作为二级缓存的两级缓存架构便应运而生。

先忽略并发等复杂因素,两级缓存的基本访问流程可以用下面这张图来表示:

Redis+Caffeine两级缓存查询流程图

为什么要使用本地缓存

  • 本地缓存直接基于进程内的内存,访问速度极快。对于变更频率低、实时性要求不高的数据,放进本地缓存能够显著提升读取效率。
  • 使用本地缓存可以有效减少与 Redis 等远程缓存之间的数据交互,降低网络 I/O 开销以及网络通信带来的耗时。

设计一个本地缓存需要具备哪些功能

  • 能够存储数据,并且支持读、写操作;
  • 保证原子操作(线程安全),比如基于 ConcurrentHashMap;
  • 支持设置缓存的最大容量限制;
  • 超过最大限制时,有对应的淘汰策略,例如 LRU、LFU;
  • 支持过期时间淘汰,如定时、懒式、定期等机制;
  • 支持持久化;
  • 提供统计监控能力。

本地缓存方案选型

1. 使用 ConcurrentHashMap 实现本地缓存

缓存的本质,其实就是内存中的一个 KV 数据结构,对应到 JDK 里就是线程安全的 ConcurrentHashMap。不过如果直接拿 ConcurrentHashMap 来实现缓存,还需要自行解决淘汰策略、容量上限、过期时间等问题。

优点是实现简单、无需引入第三方包,比较适合简单的业务场景。缺点是一旦需求变多,定制化开发的成本会明显上升,稳定性和可靠性也难以保障。因此,对于复杂场景,建议优先选择成熟的开源方案。

2. 基于 Guava Cache 实现本地缓存

Guava 是 Google 团队开源的 Java 核心增强库,包含了集合、并发原语、缓存、IO、反射等工具模块,性能和稳定性均有保障,应用范围相当广泛。Guava Cache 支持以下特性:

  • 支持最大容量限制
  • 支持两种过期删除策略(插入时间和访问时间)
  • 支持简单的统计功能
  • 基于 LRU 算法实现

使用方式如下:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>
@Slf4j
public class GuavaCacheTest {
    public static void main(String[] args) throws ExecutionException {
        Cache<String, String> cache = CacheBuilder.newBuilder()
                .initialCapacity(5)  // 初始容量
                .maximumSize(10)     // 最大缓存数,超出淘汰
                .expireAfterWrite(60, TimeUnit.SECONDS) // 过期时间
                .build();

        String orderId = String.valueOf(123456789);
        // 获取orderInfo,如果key不存在,callable中调用getInfo方法返回数据
        String orderInfo = cache.get(orderId, () -> getInfo(orderId));
        log.info("orderInfo = {}", orderInfo);

    }

    private static String getInfo(String orderId) {
        String info = "";
        // 先查询redis缓存
        log.info("get data from redis");

        // 当redis缓存不存在查db
        log.info("get data from mysql");
        info = String.format("{orderId=%s}", orderId);
        return info;
    }
}

3. Caffeine

Caffeine 是基于 Java 8 实现的新一代缓存工具,缓存性能几乎接近理论最优值。可以把它理解为 Guava Cache 的增强版,两者功能类似,但 Caffeine 采用了融合 LRU 与 LFU 优点的 W-TinyLFU 算法,因此性能上有明显优势。

使用方式如下:

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.9.3</version>
</dependency>
@Slf4j
public class CaffeineTest {
    public static void main(String[] args) {
        Cache<String, String> cache = Caffeine.newBuilder()
                .initialCapacity(5)
                // 超出时淘汰
                .maximumSize(10)
                //设置写缓存后n秒钟过期
                .expireAfterWrite(60, TimeUnit.SECONDS)
                //设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
                //.expireAfterAccess(17, TimeUnit.SECONDS)
                .build();

        String orderId = String.valueOf(123456789);
        String orderInfo = cache.get(orderId, key -> getInfo(key));
        System.out.println(orderInfo);
    }

    private static String getInfo(String orderId) {
        String info = "";
        // 先查询redis缓存
        log.info("get data from redis");

        // 当redis缓存不存在查db
        log.info("get data from mysql");
        info = String.format("{orderId=%s}", orderId);
        return info;
    }
}

4. Encache

Encache 是一个纯 Java 的进程内缓存框架,以快速、精干著称,同时也是 Hibernate 默认的 CacheProvider。与 Caffeine 和 Guava Cache 相比,Encache 的功能更丰富、扩展性更强:

  • 支持多种缓存淘汰算法,包括 LRU、LFU 和 FIFO
  • 缓存支持堆内存储、堆外存储、磁盘存储(支持持久化)三种方式
  • 支持多种集群方案,解决多实例间的数据共享问题

使用方式如下:

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.9.7</version>
</dependency>
@Slf4j
public class EhcacheTest {
    private static final String ORDER_CACHE = "orderCache";
    public static void main(String[] args) {
        CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
                // 创建cache实例
                .withCache(ORDER_CACHE, CacheConfigurationBuilder
                        // 声明一个容量为20的堆内缓存
                        .newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(20)))
                .build(true);
        // 获取cache实例
        Cache<String, String> cache = cacheManager.getCache(ORDER_CACHE, String.class, String.class);

        String orderId = String.valueOf(123456789);
        String orderInfo = cache.get(orderId);
        if (StrUtil.isBlank(orderInfo)) {
            orderInfo = getInfo(orderId);
            cache.put(orderId, orderInfo);
        }
        log.info("orderInfo = {}", orderInfo);
    }

    private static String getInfo(String orderId) {
        String info = "";
        // 先查询redis缓存
        log.info("get data from redis");

        // 当redis缓存不存在查db
        log.info("get data from mysql");
        info = String.format("{orderId=%s}", orderId);
        return info;
    }
}

本地缓存问题及解决

1. 缓存一致性

两级缓存与数据库之间的数据如何保持一致?一旦数据发生修改,除了更新数据库,本地缓存和远程缓存也应该同步更新,否则就很容易读到旧数据。

解决方案1:MQ

实际部署大多是集群环境,多个节点各自持有本地缓存。借助 MQ 的广播模式,当数据被修改时向 MQ 发送消息,各节点监听并消费消息后删除本地缓存,从而达成最终一致性。

MQ广播模式删除本地缓存流程图

解决方案2:Canal + MQ

如果不想在业务代码中手动发送 MQ 消息,还可以采用近几年比较流行的方式:订阅数据库变更日志。Canal 订阅 MySQL 的 Binlog 日志,当数据发生变化时自动向 MQ 发送消息,进而实现本地缓存与数据库的一致性。

Canal+MySQL Binlog+MQ缓存一致性方案流程图

2. 如何提高本地缓存命中率

关于这一节,核心思路与通用的缓存命中率优化方法一致:合理设置过期时间、区分冷热数据、避免无效缓存写入等。

3. 本地缓存的技术选型问题

  • 从易用性来看,Guava Cache、Caffeine 和 Encache 都提供了成熟的接入方案,使用起来都比较简单。
  • 从功能性来看,Guava Cache 和 Caffeine 功能相近,均只支持堆内缓存;Encache 的功能则更为丰富。
  • 从性能对比来看,Caffeine 表现最优,Guava Cache 次之,Encache 相对最差(下图是三者的性能对比结果)。

Caffeine与Guava/Ehcache/LHM性能对比图

对于本地缓存的方案选择,我更推荐 Caffeine,性能优势相当明显。

虽然 Encache 功能更丰富,甚至提供了持久化和集群能力,但这些功能完全可以借助其他工具实现。在实际业务工程里,建议以 Caffeine 作为本地缓存,再配合 Redis 或 Memcached 作为分布式缓存,构建多级缓存体系,在性能和可靠性之间取得平衡。




上一篇:SpringBoot 集成 FFmpeg 与 ZLMediaKit 实现本地视频 RTMP 推流
下一篇:五大AI编程范式怎么选?Vibe/Plan/Spec/Glue/Smell Coding 深度解析与落地
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-9-17 12:59 , Processed in 0.552191 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表