当前位置: 代码网 > it编程>编程语言>Java > Java 缓存框架 Caffeine 应用场景解析

Java 缓存框架 Caffeine 应用场景解析

2025年09月24日 Java 我要评论
一、caffeine 简介1. 框架概述caffeine是由google工程师ben manes开发的一款java本地缓存框架,其初始版本发布于2014年。该框架的设计灵感来源于guava cache

一、caffeine 简介

1. 框架概述

caffeine是由google工程师ben manes开发的一款java本地缓存框架,其初始版本发布于2014年。该框架的设计灵感来源于guava cache,但在性能和功能方面进行了革命性的优化。caffeine基于"w-tinylfu"(window-tiny least frequently used)算法实现,这是一种改进的lfu缓存淘汰算法,结合了lfu的高命中率优势和lru的时效性特点。

1.1 caffeine的核心优势

1.1.1 超高性能

caffeine在性能方面实现了质的飞跃:

  • 基准测试显示,相比guava cache,caffeine的读性能提升约8-12倍,写性能提升约5-10倍
  • 支持每秒数百万次(典型值300-500万qps)的缓存操作
  • 采用无锁并发设计,大幅减少线程竞争(如使用并发哈希表和非阻塞队列)
  • 对jvm的内存模型进行了深度优化,减少缓存行伪共享问题
1.1.2 灵活的过期策略

caffeine提供三种核心过期策略:

  1. 写入后过期:通过expireafterwrite设置,例如:
    caffeine.newbuilder().expireafterwrite(10, timeunit.minutes).build();
  2. 访问后过期:通过expireafteraccess设置,适合热点数据场景
  3. 自定义过期:通过expireafter方法实现基于业务逻辑的复杂过期判断
1.1.3 异步支持

caffeine提供完整的异步缓存(asynccache)支持:

  • 异步加载机制:通过asyncloadingcache实现非阻塞式数据加载
  • 支持completablefuture:可与java8的异步编程模型完美结合
  • 典型应用场景:高并发环境下的微服务接口缓存
1.1.4 丰富的监听器

caffeine提供完善的监控支持:

  • 移除监听器(removallistener):可监听缓存项的驱逐、失效或手动移除
  • 统计功能:通过recordstats()开启命中率统计
  • 典型配置:
    cache.recordstats();
    cachestats stats = cache.stats();
    double hitrate = stats.hitrate();
1.1.5 内存安全

caffeine提供多种内存保护机制:

  1. 基于容量:通过maximumsize限制缓存项数量
  2. 基于时间:通过上述过期策略控制
  3. 基于引用:支持弱引用键/值(weakkeys/weakvalues)和软引用值(softvalues)
  4. 权重控制:通过weighermaximumweight实现基于对象大小的精确控制

典型内存安全配置示例:

caffeine.newbuilder()
    .maximumsize(10_000)
    .weigher((string key, string value) -> value.length())
    .maximumweight(50_000_000) // ~50mb
    .build();

二、caffeine 基础

在使用 caffeine 前,需先引入依赖,并了解其核心组件的作用。

2.1 依赖引入(maven/gradle)

caffeine 的最新版本可在 maven 中央仓库查询(https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine)

maven 配置示例(含注释说明)

<!-- caffeine核心依赖(必选) -->
<dependency>
    <groupid>com.github.ben-manes.caffeine</groupid>
    <artifactid>caffeine</artifactid>
    <version>3.2.7</version>  <!-- 截至2024年1月最新稳定版 -->
    <!-- 建议通过dependencymanagement统一管理版本 -->
</dependency>
<!-- 异步支持依赖(可选) -->
<!-- 当需要配合java11+的httpclient实现异步缓存加载时添加 -->
<dependency>
    <groupid>java.net.http</groupid>
    <artifactid>http-client</artifactid>
    <version>11.0.1</version>  <!-- 最低要求jdk11 -->
    <scope>runtime</scope>    <!-- 通常只需运行时依赖 -->
</dependency>

gradle 配置示例(kotlin dsl)

dependencies {
    // 核心实现(必选)
    implementation("com.github.ben-manes.caffeine:caffeine:3.2.7")
    // 异步支持(可选)
    runtimeonly("java.net.http:http-client:11.0.1") {
        because("for async cache loading with http requests")
    }
}

2.2 核心组件解析

caffeine 的核心组件采用分层设计,主要分为基础缓存接口和功能扩展接口两大类:

1.基础缓存接口层次结构

cache (基本功能)
├── loadingcache (同步加载)
└── asynccache (异步基础)
    └── asyncloadingcache (异步加载)

2.关键组件详细说明(含典型应用场景)

组件作用说明典型使用场景示例代码片段
cache<k,v>手动管理缓存,需显式处理缓存未命中简单缓存场景,数据源访问成本较低cache.get(key, k -> fetchfromdb(k))
loadingcache<k,v>自动加载缓存,内置cacheloader高频访问且加载逻辑固定的场景loadingcache.from(this::loadfromapi)
asynccache<k,v>返回completablefuture的异步接口配合非阻塞io或远程调用cache.get(key).thenaccept(value -> ...)
asyncloadingcache<k,v>异步自动加载缓存微服务间数据缓存asyncloadingcache.from(this::asyncload)
cacheloader<k,v>定义加载逻辑的函数式接口统一数据加载策略new cacheloader<>() { @override public v load(k key)... }
removallistener<k,v>移除事件监听器缓存一致性维护、监控统计listener((key,value,reason) -> logremoval())
expiry<k,v>细粒度过期控制动态ttl场景(如会话缓存)expireafter((key,value,currenttime) -> customttl)

3.高级特性支持

  • 权重计算:通过weigher接口实现基于缓存对象大小的淘汰策略
  • 刷新机制refreshafterwrite配合cacheloader.reload实现后台刷新
  • 统计监控recordstats()启用命中率等统计指标
  • 线程模型:默认使用forkjoinpool.commonpool(),可通过executor自定义

4.最佳实践提示:

  1. 对于长时间加载操作,优先选择asyncloadingcache避免阻塞
  2. 移除监听器不要执行耗时操作,否则会影响缓存性能
  3. 在spring环境中建议通过@bean配置全局缓存管理器
  4. 生产环境务必启用统计功能(recordstats)进行监控

三、caffeine 核心用法

caffeine 的使用流程遵循 "构建器模式配置 → 创建缓存实例 → 读写缓存" 的逻辑,下面分场景讲解具体用法。

3.1 基础缓存(cache):手动控制读写

cache 是最基础的缓存类型,需手动处理缓存未命中(未命中时返回 null),适合缓存逻辑简单的场景。

3.1.1 创建 cache 实例

通过 caffeine.newbuilder() 配置缓存规则,常见配置包括:

  • 容量控制
    • maximumsize(long):设置缓存最大容量(条目数),超过后按 w-tinylfu 算法淘汰。
    • maximumweight(long) + weigher(weigher):基于权重控制缓存大小,适合不同条目占用不同内存的场景。
  • 过期策略
    • expireafterwrite(duration):写入后过期(如 10 分钟未更新则过期),适合数据变更频繁的场景。
    • expireafteraccess(duration):访问后过期(如 5 分钟未访问则过期),适合热点数据缓存。
    • expireafter(expiry):自定义过期时间计算逻辑,可实现基于业务规则的过期。
  • 监听器
    • removallistener(removallistener):设置缓存移除监听器,可记录日志或触发后续操作。
  • 其他特性
    • weakkeys()/weakvalues():使用弱引用,允许被垃圾回收。
    • softvalues():使用软引用,在内存不足时被回收。
    • recordstats():启用统计信息收集。
import com.github.ben-manes.caffeine.cache.caffeine;
import com.github.ben-manes.caffeine.cache.cache;
import java.util.concurrent.timeunit;
public class caffeinebasicdemo {
    public static void main(string[] args) {
        // 1. 配置并创建cache实例(带详细注释)
        cache<string, string> usercache = caffeine.newbuilder()
            .maximumsize(1000) // 最大容量1000条
            .expireafterwrite(10, timeunit.minutes) // 写入后10分钟过期
            .expireafteraccess(5, timeunit.minutes) // 访问后5分钟过期(优先级低于expireafterwrite)
            .removallistener((key, value, cause) -> { // 缓存移除监听器
                system.out.printf("缓存移除:key=%s, value=%s, 原因=%s%n",
                    key, value, cause.tostring());
                // 原因可能是:explicit(手动删除)、replaced(值被替换)、
                // collected(垃圾回收)、expired(过期)、size(超过容量限制)
            })
            .recordstats() // 启用统计
            .build(); // 构建cache实例
        // 2. 写入缓存(多种方式)
        usercache.put("user:1001", "张三"); // 常规put
        usercache.asmap().putifabsent("user:1002", "李四"); // 线程安全写入
        // 3. 读取缓存(未命中返回null)
        string user1 = usercache.getifpresent("user:1001");
        system.out.println("读取user:1001:" + user1); // 输出:张三
        // 4. 读取并计算(未命中时执行函数逻辑,但不自动存入缓存)
        string user3 = usercache.get("user:1003", key -> {
            // 模拟从数据库查询数据(仅当缓存未命中时执行)
            system.out.println("缓存未命中,查询db:" + key);
            return "王五"; // 此结果不会自动存入缓存
        });
        system.out.println("读取user:1003:" + user3); // 输出:王五
        // 5. 缓存维护操作
        usercache.invalidate("user:1002"); // 单个删除
        usercache.invalidateall(list.of("user:1001", "user:1003")); // 批量删除
        usercache.cleanup(); // 手动触发清理过期条目
        usercache.invalidateall(); // 清空所有缓存
        // 6. 查看统计信息(需先启用recordstats)
        system.out.println("命中率:" + usercache.stats().hitrate());
    }
}

3.1.2 应用场景示例

  • 简单kv缓存
    • 缓存用户session信息
    • 缓存系统配置项
    • 临时数据存储(如验证码)
  • 配合spring cache
@configuration
@enablecaching
public class cacheconfig {
    @bean
    public cachemanager cachemanager() {
        caffeinecachemanager manager = new caffeinecachemanager();
        manager.setcaffeine(caffeine.newbuilder()
            .maximumsize(1000)
            .expireafterwrite(10, timeunit.minutes));
        return manager;
    }
}

多级缓存

// 作为本地缓存与redis组成二级缓存
public class multilevelcache {
    private final cache<string, object> localcache;
    private final redistemplate<string, object> redistemplate;
    public object get(string key) {
        object value = localcache.getifpresent(key);
        if (value == null) {
            value = redistemplate.opsforvalue().get(key);
            if (value != null) {
                localcache.put(key, value);
            }
        }
        return value;
    }
}

3.2 加载缓存(loadingcache):自动加载未命中数据

loadingcache 是 cache 的子类,通过实现 cacheloader 接口,实现 "缓存未命中时自动加载数据并存入缓存",适合缓存数据需从数据源(如 db、redis)加载的场景。

3.2.1 创建 loadingcache 实例

import com.github.ben-manes.caffeine.cache.caffeine;
import com.github.ben-manes.caffeine.cache.loadingcache;
import java.util.concurrent.executionexception;
import java.util.concurrent.timeunit;
import java.util.list;
public class caffeineloadingdemo {
    public static void main(string[] args) throws executionexception {
        // 1. 实现cacheloader:定义缓存未命中时的加载逻辑
        loadingcache<string, string> productcache = caffeine.newbuilder()
            .maximumsize(500)
            .expireafterwrite(30, timeunit.minutes)
            .refreshafterwrite(10, timeunit.minutes) // 10分钟后刷新(不阻塞读取)
            .recordstats()
            .build(new cacheloader<string, string>() {
                @override
                public string load(string key) throws exception {
                    // 模拟从数据库加载数据(缓存未命中时自动执行)
                    system.out.println("缓存未命中,从db加载:" + key);
                    if (key.startswith("prod:")) {
                        return "商品-" + key.substring(5); // 如key=prod:101 → 商品-101
                    }
                    throw new illegalargumentexception("invalid key format");
                }
                // 可选:实现批量加载(提升getall性能)
                @override
                public map<string, string> loadall(iterable<? extends string> keys) {
                    system.out.println("批量加载keys:" + keys);
                    // 实际应从db批量查询
                    map<string, string> result = new hashmap<>();
                    for (string key : keys) {
                        result.put(key, "商品-" + key.substring(5));
                    }
                    return result;
                }
            });
        // 2. 读取缓存(未命中时自动调用load()加载并存入缓存)
        string product1 = productcache.get("prod:101"); // 首次:加载并返回
        system.out.println("读取prod:101:" + product1); // 输出:商品-101
        // 3. 批量读取(getall())
        map<string, string> products = productcache.getall(list.of("prod:102", "prod:103"));
        system.out.println("批量读取结果:" + products);
        // 4. 主动刷新(异步)
        productcache.refresh("prod:101"); // 后台刷新,旧值仍可用
        // 5. 统计信息
        system.out.println("加载次数:" + productcache.stats().loadcount());
    }
}

3.2.2 关键特性:刷新(refresh)与过期(expire)的区别

特性刷新(refresh)过期(expire)
触发时机刷新时间到后过期时间到后
读取行为异步刷新,立即返回旧值同步重新加载,可能阻塞请求
适用场景数据允许短暂不一致(如商品详情)数据强一致要求(如订单状态)
实现方式需配置refreshafterwrite配置expireafterwrite/afteraccess

典型使用模式

// 商品详情缓存:10分钟强制过期,5分钟自动刷新
loadingcache<string, product> productcache = caffeine.newbuilder()
    .maximumsize(10000)
    .expireafterwrite(10, timeunit.minutes) // 强制过期时间
    .refreshafterwrite(5, timeunit.minutes) // 自动刷新时间
    .build(this::loadproductfromdb);

3.3 异步缓存(asynccache/asyncloadingcache):非阻塞读写

在高并发场景下,同步缓存的 load() 可能会阻塞线程,而 asynccache 通过返回 completablefuture 实现非阻塞操作,所有 io 操作均在异步线程池中执行。

3.3.1 创建 asyncloadingcache 实例

import com.github.ben-manes.caffeine.cache.asyncloadingcache;
import com.github.ben-manes.caffeine.cache.caffeine;
import java.util.concurrent.completablefuture;
import java.util.concurrent.executor;
import java.util.concurrent.executors;
import java.util.concurrent.timeunit;
public class caffeineasyncdemo {
    public static void main(string[] args) throws exception {
        // 1. 自定义线程池(生产环境建议使用有界队列和拒绝策略)
        executor executor = executors.newfixedthreadpool(5, r -> {
            thread thread = new thread(r);
            thread.setname("caffeine-async-" + thread.getid());
            return thread;
        });
        // 2. 创建asyncloadingcache实例
        asyncloadingcache<string, string> ordercache = caffeine.newbuilder()
            .maximumsize(1000)
            .expireafterwrite(15, timeunit.minutes)
            .executor(executor) // 指定异步线程池
            .buildasync(key -> {
                // 模拟耗时操作(如rpc调用,耗时200ms)
                timeunit.milliseconds.sleep(200);
                system.out.println(thread.currentthread().getname() + " 加载订单:" + key);
                return "订单-" + key.substring(6); // 如key=order:2024 → 订单-2024
            });
        // 3. 异步读取(推荐方式)
        completablefuture<string> future = ordercache.get("order:2024");
        future.thenapplyasync(order -> {
            system.out.println("处理订单数据:" + order);
            return order.touppercase();
        }, executor); // 使用相同线程池处理结果
        // 4. 批量读取(返回map<key, completablefuture>)
        map<string, completablefuture<string>> futures = 
            ordercache.getall(list.of("order:2025", "order:2026"));
        // 5. 同步获取(仅测试用,实际应避免)
        string order = ordercache.get("order:2027").get();
        system.out.println("同步获取结果:" + order);
    }
}

3.3.2 最佳实践

线程池配置

// 更完善的线程池配置
threadpoolexecutor executor = new threadpoolexecutor(
    5, // 核心线程数
    10, // 最大线程数
    60, timeunit.seconds, // 空闲线程存活时间
    new linkedblockingqueue<>(1000), // 有界队列
    new threadfactorybuilder().setnameformat("cache-loader-%d").build(),
    new threadpoolexecutor.callerrunspolicy() // 拒绝策略
);

异常处理

ordercache.get("badkey").exceptionally(ex -> {
    system.err.println("加载失败: " + ex.getmessage());
    return "defaultvalue";
});

结合spring使用

@cacheable(value = "orders", cachemanager = "asynccachemanager")
public completablefuture<order> getorderasync(string orderid) {
    return completablefuture.supplyasync(() -> orderservice.loadorder(orderid));
}

性能监控

cachestats stats = ordercache.synchronous().stats();
system.out.println("平均加载时间:" + stats.averageloadpenalty() + "ns");

四、caffeine 高级特性

4.1 缓存统计(cache statistics)

缓存统计功能是优化缓存性能的重要工具。通过开启缓存统计,可以实时监控以下关键指标:

  1. 命中率(hit rate):反映缓存有效性,计算公式为:命中次数/(命中次数+未命中次数)
  2. 加载耗时(load penalty):统计从数据源加载数据的平均耗时
  3. 移除次数(eviction count):因容量或过期策略导致的缓存移除次数
  4. 加载失败率(load failure rate):数据源加载失败的比例

典型应用场景:

  • 评估缓存配置是否合理
  • 识别热点数据
  • 监控缓存性能瓶颈
import com.github.ben-manes.caffeine.cache.cachestats;
public class caffeinestatsdemo {
    public static void main(string[] args) {
        loadingcache<string, string> statscache = caffeine.newbuilder()
            .maximumsize(100)
            .recordstats() // 必须显式开启统计功能
            .build(key -> {
                // 模拟耗时加载
                try {
                    thread.sleep(100);
                } catch (interruptedexception e) {
                    thread.currentthread().interrupt();
                }
                return "统计测试:" + key;
            });
        // 模拟读写操作
        statscache.get("key1");  // 第一次加载(未命中)
        statscache.get("key1");  // 命中已有缓存
        statscache.get("key2");  // 新键加载
        statscache.invalidate("key1"); // 手动失效
        // 获取统计结果
        cachestats stats = statscache.stats();
        system.out.println("缓存命中率:" + stats.hitrate());  // 50%(1次命中/2次查询)
        system.out.println("加载成功次数:" + stats.loadsuccesscount()); // 2次加载
        system.out.println("移除次数:" + stats.evictioncount()); // 0(未达到容量上限)
        system.out.println("平均加载耗时(ns):" + stats.averageloadpenalty()); // 约100ms
        system.out.println("加载失败率:" + stats.loadfailurerate()); // 0.0
    }
}

4.2 自定义过期策略(expiry)

标准的ttl(time-to-live)过期策略对所有缓存条目采用统一设置,而自定义过期策略允许基于业务特性实现精细化控制。

常见应用场景:

  • 不同优先级数据设置不同有效期(如热点数据短时效,冷数据长时效)
  • 读写操作影响过期时间(如读操作续期)
  • 动态调整过期时间(如根据数据价值计算)
import com.github.ben-manes.caffeine.cache.caffeine;
import com.github.ben-manes.caffeine.cache.expiry;
import com.github.ben-manes.caffeine.cache.loadingcache;
import java.util.concurrent.timeunit;
class customexpiry implements expiry<string, string> {
    @override
    public long expireaftercreate(string key, string value, long currenttime) {
        // 创建时过期策略
        if (key.startswith("flash:")) {    // 闪存数据:30秒过期
            return timeunit.seconds.tonanos(30);
        } else if (key.startswith("hot:")) { // 热门数据:5分钟
            return timeunit.minutes.tonanos(5);
        } else {                            // 普通数据:30分钟
            return timeunit.minutes.tonanos(30);
        }
    }
    @override
    public long expireafterupdate(string key, string value, 
            long currenttime, long currentduration) {
        // 更新策略:保持原有过期时间(默认)
        return currentduration;
        // 或者重置为创建时间:return expireaftercreate(key, value, currenttime);
    }
    @override
    public long expireafterread(string key, string value, 
            long currenttime, long currentduration) {
        // 读取时策略:热门数据读取后续期5分钟
        if (key.startswith("hot:")) {
            return timeunit.minutes.tonanos(5);
        }
        return currentduration;
    }
}
public class caffeinecustomexpirydemo {
    public static void main(string[] args) {
        loadingcache<string, string> customexpirycache = caffeine.newbuilder()
            .expireafter(new customexpiry())
            .build(key -> "自定义过期:" + key);
        customexpirycache.get("flash:news:2023");   // 30秒过期
        customexpirycache.get("hot:product:101");   // 5分钟且读取续期
        customexpirycache.get("normal:user:201");   // 30分钟过期
    }
}

4.3 弱引用与软引用:避免内存溢出

java引用类型与缓存回收策略:

引用类型gc行为适用场景caffeine配置
强引用永不回收默认方式-
软引用内存不足时回收缓存大对象.softvalues()
弱引用下次gc时回收临时性缓存.weakkeys()/.weakvalues()

注意事项:

  1. 使用weakkeys()时,key比较基于==而非equals()
  2. softvalues()可能导致gc压力增大
  3. 引用回收与显式失效策略共同作用
// 弱引用key+value的缓存(适合临时性数据)
cache<string, byte[]> weakcache = caffeine.newbuilder()
    .weakkeys()    // key无强引用时回收
    .weakvalues()  // value无强引用时回收
    .maximumsize(10_000)  // 仍保持容量限制
    .build();
// 软引用value的缓存(适合大对象)
cache<string, byte[]> softcache = caffeine.newbuilder()
    .softvalues()  // 内存不足时回收value
    .expireafterwrite(1, timeunit.hours)  // 配合显式过期
    .build();
// 典型使用场景
void processlargedata(string dataid) {
    byte[] data = softcache.get(dataid, id -> {
        // 从数据库加载大对象(如图片、文件等)
        return loadlargedatafromdb(id); 
    });
    // 使用数据...
}

五、caffeine 注意事项

在实际开发中,若使用不当,caffeine 可能出现缓存穿透、内存溢出、线程阻塞等问题,以下是核心注意事项:

5.1 区分 "刷新(refresh)" 与 "过期(expire)"

刷新(refreshafterwrite):

  • 工作机制:当缓存条目超过指定时间未被写入时,下次读取会触发异步刷新,但在此期间仍会返回旧值
  • 适用场景:对数据一致性要求不高,可接受短暂延迟的场景
    • 商品详情页的评论数统计
    • 新闻资讯的阅读量统计
    • 排行榜数据的更新

过期(expireafterwrite/expireafteraccess):

  • expireafterwrite:从写入开始计时
  • expireafteraccess:从最后一次访问开始计时
  • 工作机制:过期后缓存条目立即失效,读取时会同步阻塞直到重新加载完成
  • 适用场景:对数据一致性要求高的核心业务
    • 用户账户余额
    • 订单支付状态
    • 库存数量

⚠️ 典型误用场景:

将用户余额这类强一致性数据配置为refreshafterwrite(5s).可能导致:

  1. 用户a看到余额100元
  2. 用户b完成扣款50元
  3. 5秒内用户a仍看到100元(旧值)
  4. 直到下次读取才刷新为50元

5.2 避免缓存穿透:空值缓存与布隆过滤器

缓存穿透的典型特征:

  • 查询一个必然不存在的数据(如不存在的用户id)
  • 每次请求都穿透到数据库
  • 可能被恶意攻击者利用,造成数据库压力

解决方案1:空值缓存

loadingcache<string, string> cache = caffeine.newbuilder()
    .expireafterwrite(1, timeunit.minutes)  // 空值缓存1分钟
    .build(key -> {
        string value = queryfromdb(key);
        // 特殊空值标记,避免与真实空值混淆
        return value != null ? value : "null_value";  
    });

解决方案2:布隆过滤器(适合千万级key场景)

// 初始化布隆过滤器
bloomfilter<string> bloomfilter = bloomfilter.create(
    funnels.stringfunnel(charset.defaultcharset()), 
    1000000,  // 预期元素数量
    0.01      // 误判率
);
// 查询流程
if (!bloomfilter.mightcontain(key)) {
    return null;  // 肯定不存在
} else {
    return cache.get(key);  // 可能存在
}

5.3 缓存键(key)必须重写 hashcode() 和 equals()

常见错误案例:

class compositekey {
    private long id;
    private string category;
    // 缺少hashcode/equals实现
}
// 实际使用中
compositekey key1 = new compositekey(1l, "a");
compositekey key2 = new compositekey(1l, "a");
cache.put(key1, "value");
// 将返回null,因为key2被视为不同key
cache.getifpresent(key2); 

正确实现要点:

  1. 使用objects工具类自动生成
  2. 保证不可变(final字段)
  3. 实现serializable接口(分布式缓存需要)
class compositekey implements serializable {
    private final long id;
    private final string category;
    @override
    public boolean equals(object o) {
        if (this == o) return true;
        if (!(o instanceof compositekey)) return false;
        compositekey that = (compositekey) o;
        return objects.equals(id, that.id) && 
               objects.equals(category, that.category);
    }
    @override
    public int hashcode() {
        return objects.hash(id, category);
    }
}

5.4 异步缓存(asynccache)的线程池选择

默认线程池的问题:

  • forkjoinpool.commonpool()是jvm全局共享的
  • 可能被completablefuture等其他组件占用
  • 在容器环境中可能线程数不足

推荐配置:

executorservice executor = executors.newfixedthreadpool(
    runtime.getruntime().availableprocessors() * 2,
    new threadfactorybuilder()
        .setnameformat("caffeine-loader-%d")
        .setdaemon(true)
        .build()
);
asyncloadingcache<string, string> cache = caffeine.newbuilder()
    .executor(executor)  // 指定专属线程池
    .buildasync(key -> loadexpensivevalue(key));

5.5 避免内存溢出:合理配置容量与过期时间

典型配置示例:

caffeine.newbuilder()
    .maximumsize(10_000)  // 基于条目数限制
    .expireafterwrite(30, timeunit.minutes)  // 写入后30分钟过期
    .expireafteraccess(10, timeunit.minutes) // 10分钟无访问过期
    .weigher((string key, string value) -> value.length()) // 按value大小计算权重
    .maximumweight(50_000_000)  // 约50mb内存限制

监控建议:

  1. 通过cache.stats()获取命中率
  2. 使用jmx监控缓存大小
  3. 设置告警阈值(如内存使用>80%)

5.6 cacheloader 的异常处理

完整异常处理方案:

loadingcache<string, string> cache = caffeine.newbuilder()
    .build(new cacheloader<string, string>() {
        @override
        public string load(string key) {
            try {
                return querydb(key);
            } catch (sqlexception e) {
                // 记录详细日志
                log.error("db查询失败, key: {}", key, e);
                // 返回降级值
                return "default_value";
                // 或者抛出特定异常
                // throw new cacheloadexception(e);
            }
        }
    });
// 使用时的异常处理
try {
    return cache.get(key);
} catch (cacheloaderexception e) {
    // 处理加载失败
    return processfallback(key);
} catch (exception e) {
    // 兜底处理
    return "system_error";
}

六、常见问题

q1:caffeine 与 guava cache 的详细区别

性能比较

caffeine 采用了创新的 w-tinylfu 缓存淘汰算法,该算法结合了 tinylfu 和 lru 的优势:

  • 使用 count-min sketch 数据结构高效统计访问频率
  • 适应不同工作负载模式(突发性和长期性访问)
  • 在基准测试中,caffeine 的读写性能比 guava cache 高出 10-20 倍

功能特性对比

特性caffeineguava cache
异步加载支持 asyncloadingcache仅同步加载
过期策略支持基于大小、时间、引用等多种策略仅基本过期策略
自动刷新支持 refreshafterwrite不支持
权重计算支持自定义权重支持但性能较差
监听器支持移除监听器支持移除监听器
统计提供命中率等详细统计提供基本统计

兼容性与迁移

caffeine 在设计时特别考虑了与 guava cache 的兼容性:

  • 90%以上的 api 可以直接替换
  • 主要差异在于构建方式(caffeine.newbuilder() vs cachebuilder.newbuilder())
  • 迁移示例:
// guava cache
loadingcache<key, value> cache = cachebuilder.newbuilder()
    .maximumsize(1000)
    .build(new cacheloader<key, value>() {
        public value load(key key) {
            return createvalue(key);
        }
    });
// 迁移到 caffeine
loadingcache<key, value> cache = caffeine.newbuilder()
    .maximumsize(1000)
    .build(key -> createvalue(key));

q2:caffeine 的分布式缓存支持与多级缓存架构

本地缓存特性

caffeine 作为本地缓存的核心特点:

  • 仅作用于单个 jvm 进程内
  • 不同服务器节点间的缓存数据不共享
  • 适用于高频访问、低变化率的数据

二级缓存架构实现

典型的生产级缓存架构组合:

  • 第一层:caffeine 本地缓存(纳秒级响应)
    • 设置合理的过期时间(如30秒)
    • 适合极端热点数据
  • 第二层:redis 分布式缓存(毫秒级响应)
    • 设置较长的过期时间(如5分钟)
    • 使用redis集群保证高可用
  • 数据源:数据库/服务(秒级响应)
    • 最终数据一致性保障

实现示例

public class twolevelcacheservice {
    private final cache<string, object> localcache = caffeine.newbuilder()
        .maximumsize(10_000)
        .expireafterwrite(30, timeunit.seconds)
        .build();
    private final redistemplate<string, object> redistemplate;
    public object getdata(string key) {
        // 1. 尝试从本地缓存获取
        object value = localcache.getifpresent(key);
        if (value != null) {
            return value;
        }
        // 2. 尝试从redis获取
        value = redistemplate.opsforvalue().get(key);
        if (value != null) {
            localcache.put(key, value);
            return value;
        }
        // 3. 回源查询
        value = querydatabase(key);
        redistemplate.opsforvalue().set(key, value, 5, timeunit.minutes);
        localcache.put(key, value);
        return value;
    }
}

q3:缓存击穿解决方案的深入分析

互斥锁方案详解

实现要点

  1. 使用 key.intern() 获取字符串规范表示,确保相同key锁定同一对象
  2. 采用双重检查锁定模式减少锁竞争
  3. 设置合理的锁等待超时时间

增强版实现

public object getdatawithlock(string key) {
    object value = cache.getifpresent(key);
    if (value != null) {
        return value;
    }
    synchronized (key.intern()) {
        // 双重检查
        value = cache.getifpresent(key);
        if (value != null) {
            return value;
        }
        try {
            value = querydatasource(key);
            cache.put(key, value);
        } finally {
            // 释放资源
        }
    }
    return value;
}

热点key永不过期方案

实现模式

主动更新:后台线程定期(如每分钟)刷新热点数据

scheduledexecutorservice scheduler = executors.newscheduledthreadpool(1);
scheduler.scheduleatfixedrate(() -> {
    list<string> hotkeys = gethotkeylist();
    hotkeys.foreach(key -> {
        object value = querydatasource(key);
        cache.put(key, value);
    });
}, 0, 1, timeunit.minutes);

被动更新:获取数据时异步刷新

loadingcache<string, object> cache = caffeine.newbuilder()
    .maximumsize(10_000)
    .refreshafterwrite(1, timeunit.minutes)
    .build(key -> querydatasource(key));

其他防护策略

  1. 布隆过滤器:前置过滤不存在的key请求
  2. 缓存预热:系统启动时加载热点数据
  3. 随机过期时间:对相同类型key设置不同的过期时间偏移量
    int baseexpire = 3600; // 基础1小时
    int randomoffset = threadlocalrandom.current().nextint(600); // 0-10分钟随机
    cache.put(key, value, baseexpire + randomoffset, timeunit.seconds);
    
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com