Caffeine

结合你的 Java 后端开发背景(Spring Boot)和对高性能、缓存架构的关注,以下是 Caffeine 缓存库零基础到实战优化指南,聚焦「理论 + 可执行代码 + Spring Boot 集成 + 多级缓存实践」,完全适配你的技术栈和需求:

一、Caffeine 核心认知

1. 是什么?

  • 定位:Java 领域高性能本地缓存库,基于 Google Guava Cache 改进,性能提升 10-100 倍(官方 benchmark 数据),支持丰富的缓存策略和过期机制。
  • 核心优势
    • 基于「W-TinyLFU」淘汰算法(兼顾频率和近期性),缓存命中率远超 LRU/LFU;
    • 支持异步加载、过期时间(固定 / 滑动)、最大容量限制、缓存刷新等特性;
    • 线程安全,并发性能优异(底层使用 ConcurrentHashMap 优化);
    • 轻量级(依赖仅 100KB 左右),无额外依赖。
  • 适用场景
    • 后端服务本地缓存(热点数据、配置信息、接口结果缓存);
    • 多级缓存架构中的「本地缓存层」(搭配 Redis 实现「本地缓存 + 分布式缓存」);
    • 高并发场景下减少数据库 / Redis 访问压力(如秒杀、高频查询接口)。

二、快速入门:基础使用(纯 Java)

1. 引入依赖(Maven)

xml

复制代码
<!-- Caffeine 核心依赖 -->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.8</version> <!-- 最新稳定版 -->
</dependency>

2. 核心 API 实践(可直接运行)

示例 1:基本缓存(手动 put/get/ 过期配置)

java

运行

复制代码
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Cache;
import java.util.concurrent.TimeUnit;

public class CaffeineBasicDemo {
    public static void main(String[] args) throws InterruptedException {
        // 1. 构建缓存实例(配置:最大容量100,过期时间5秒(写入后))
        Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(100) // 最大缓存条目数
                .expireAfterWrite(5, TimeUnit.SECONDS) // 写入后5秒过期
                .build();

        // 2. 写入缓存
        cache.put("user:1", "张三");
        cache.put("user:2", "李四");

        // 3. 读取缓存(存在则返回值,不存在返回null)
        System.out.println(cache.getIfPresent("user:1")); // 输出:张三

        // 4. 过期测试(等待6秒后读取)
        Thread.sleep(6000);
        System.out.println(cache.getIfPresent("user:1")); // 输出:null
    }
}
示例 2:自动加载缓存(缓存不存在时自动触发加载逻辑)

java

运行

复制代码
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.util.concurrent.TimeUnit;

public class CaffeineLoadingCacheDemo {
    public static void main(String[] args) {
        // 1. 构建 LoadingCache(配置:滑动过期3秒,自动加载)
        LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
                .maximumSize(50)
                .expireAfterAccess(3, TimeUnit.SECONDS) // 滑动过期(3秒未访问则过期)
                .build(key -> loadDataFromDB(key)); // 缓存不存在时执行加载逻辑

        // 2. 首次访问:触发 loadDataFromDB
        System.out.println(loadingCache.get("user:3")); // 输出:从数据库加载用户3

        // 3. 再次访问:直接从缓存获取(3秒内)
        System.out.println(loadingCache.get("user:3")); // 输出:从数据库加载用户3(缓存命中)

        // 4. 滑动过期测试(等待4秒后访问)
        try {
            Thread.sleep(4000);
            System.out.println(loadingCache.get("user:3")); // 输出:从数据库加载用户3(重新加载)
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 模拟从数据库加载数据(实际项目中替换为DAO/Service调用)
    private static String loadDataFromDB(String key) {
        System.out.print("从数据库加载");
        return key.split(":")[1] + "(数据库数据)";
    }
}

三、Spring Boot 集成 Caffeine(实战核心)

Spring Boot 2.x+ 已原生支持 Caffeine 作为缓存管理器,以下是 生产级配置 + 使用示例

1. 引入依赖(Spring Boot 专用)

xml

复制代码
<!-- Spring Boot 缓存抽象 + Caffeine -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.8</version>
</dependency>

2. 配置文件(application.yml)

yaml

复制代码
spring:
  cache:
    type: caffeine # 指定缓存类型为 Caffeine
    caffeine:
      spec: maximumSize=1000,expireAfterWrite=10s # 缓存规格(最大1000条,写入后10秒过期)
      # 可选:指定缓存管理器名称(多缓存配置时使用)
      cache-manager:
        name: caffeineCacheManager

3. 缓存配置类(自定义高级策略)

如果需要多缓存策略(如不同接口用不同过期时间),可通过配置类扩展:

java

运行

复制代码
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableCaching // 启用Spring缓存注解(@Cacheable、@CacheEvict等)
public class CaffeineCacheConfig {

    // 定义多个缓存策略(按需扩展)
    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        List<CaffeineCache> caches = new ArrayList<>();

        // 缓存1:用户信息缓存(最大500条,5分钟过期)
        caches.add(new CaffeineCache(
                "userCache", // 缓存名称(@Cacheable中指定)
                Caffeine.newBuilder()
                        .maximumSize(500)
                        .expireAfterWrite(5, TimeUnit.MINUTES)
                        .build()
        ));

        // 缓存2:商品列表缓存(最大1000条,1分钟过期)
        caches.add(new CaffeineCache(
                "productCache",
                Caffeine.newBuilder()
                        .maximumSize(1000)
                        .expireAfterWrite(1, TimeUnit.MINUTES)
                        .build()
        ));

        // 缓存3:热点数据缓存(无过期时间,最大200条,基于LRU淘汰)
        caches.add(new CaffeineCache(
                "hotDataCache",
                Caffeine.newBuilder()
                        .maximumSize(200)
                        .evictionListener((key, value, cause) -> 
                            System.out.printf("缓存淘汰:key=%s, cause=%s%n", key, cause)
                        ) // 淘汰监听器(可选)
                        .build()
        ));

        cacheManager.setCaches(caches);
        return cacheManager;
    }
}

4. 业务层使用缓存注解

java

运行

复制代码
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    // 1. 查询用户:缓存存在则返回缓存,不存在则执行方法并缓存结果
    @Cacheable(value = "userCache", key = "#userId") // value指定缓存名称,key指定缓存键
    public User getUserById(Long userId) {
        // 模拟数据库查询(实际项目中替换为Mapper调用)
        System.out.println("从数据库查询用户:" + userId);
        return new User(userId, "用户" + userId, 25);
    }

    // 2. 更新用户:更新数据库后同步更新缓存
    @CachePut(value = "userCache", key = "#user.id") // 缓存键与查询一致
    public User updateUser(User user) {
        System.out.println("更新数据库用户:" + user.getId());
        user.setName("更新后的" + user.getName());
        return user;
    }

    // 3. 删除用户:删除数据库后删除缓存
    @CacheEvict(value = "userCache", key = "#userId") // 清除指定key的缓存
    public void deleteUser(Long userId) {
        System.out.println("删除数据库用户:" + userId);
    }

    // 4. 清空缓存:清除整个userCache缓存
    @CacheEvict(value = "userCache", allEntries = true)
    public void clearUserCache() {
        System.out.println("清空用户缓存");
    }

    // 模拟User实体类
    public static class User {
        private Long id;
        private String name;
        private Integer age;

        // 构造器、getter/setter省略
    }
}

5. 测试缓存效果(Controller 层)

java

运行

复制代码
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;

@RestController
@RequestMapping("/users")
public class UserController {

    @Resource
    private UserService userService;

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        // 第一次访问:执行getUserById(数据库查询),后续访问:直接返回缓存
        return userService.getUserById(id);
    }

    @PutMapping
    public User updateUser(@RequestBody UserService.User user) {
        return userService.updateUser(user);
    }

    @DeleteMapping("/{id}")
    public String deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return "删除成功";
    }

    @DeleteMapping("/cache/clear")
    public String clearCache() {
        userService.clearUserCache();
        return "缓存清空成功";
    }
}

四、高级特性与性能优化

1. 缓存策略选择(关键优化点)

策略 方法 适用场景
固定过期(写入后) expireAfterWrite(time) 数据更新频率低(如配置、静态数据)
滑动过期(访问后) expireAfterAccess(time) 数据更新频率高,需根据访问频率保留热点数据
无过期 + 容量限制 maximumSize(size) 热点数据,依赖淘汰算法自动清理冷数据
缓存刷新(定时重载) refreshAfterWrite(time) 数据需定时更新(如实时统计数据,避免缓存穿透)

优化建议

  • 热点查询接口用 expireAfterAccess(30s~5min) + 合理 maximumSize
  • 配置类 / 静态数据用 expireAfterWrite(1h~24h)
  • 实时性要求高的数据用 refreshAfterWrite(10s~1min)(结合异步加载)。

2. 异步加载与线程池优化

Caffeine 支持异步加载(避免阻塞主线程),结合 Spring 线程池配置:

java

运行

复制代码
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Configuration
public class AsyncCaffeineConfig {

    // 配置线程池(异步加载缓存时使用)
    @Bean
    public ExecutorService cacheExecutor() {
        return Executors.newFixedThreadPool(5); // 5个核心线程
    }

    // 构建异步加载缓存
    @Bean
    public AsyncLoadingCache<String, String> asyncHotDataCache(ExecutorService cacheExecutor) {
        return Caffeine.newBuilder()
                .maximumSize(200)
                .expireAfterAccess(2, TimeUnit.MINUTES)
                .executor(cacheExecutor) // 指定线程池
                .buildAsync(key -> loadHotDataAsync(key)); // 异步加载逻辑
    }

    // 模拟异步加载数据(如调用远程接口、查询数据库)
    private String loadHotDataAsync(String key) {
        System.out.println("异步加载热点数据:" + key);
        return "热点数据-" + key;
    }
}

3. 缓存穿透 / 击穿 / 雪崩防护(生产必看)

(1)缓存穿透(查询不存在的数据,导致缓存失效,直击数据库)
  • 解决方案:缓存空值 + 布隆过滤器

java

运行

复制代码
// 方式1:缓存空值(@Cacheable中配置)
@Cacheable(value = "userCache", key = "#userId", unless = "#result == null")
public User getUserById(Long userId) {
    User user = userMapper.selectById(userId);
    // 若用户不存在,返回null(不会缓存),但可优化为缓存空对象
    return user == null ? new User(userId, "空用户", 0) : user;
}

// 方式2:布隆过滤器(拦截不存在的key,避免查询数据库)
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.nio.charset.StandardCharsets;

@Configuration
public class BloomFilterConfig {

    // 初始化布隆过滤器(假设用户ID范围1~1000000)
    @Bean
    public BloomFilter<Long> userBloomFilter() {
        int expectedInsertions = 1000000; // 预计插入数量
        double fpp = 0.01; // 误判率(越低越耗内存)
        return BloomFilter.create(
                Funnels.longFunnel(),
                expectedInsertions,
                fpp
        );
    }
}

// 业务层使用布隆过滤器
@Service
public class UserService {

    @Resource
    private BloomFilter<Long> userBloomFilter;

    @Cacheable(value = "userCache", key = "#userId")
    public User getUserById(Long userId) {
        // 先通过布隆过滤器判断:不存在则直接返回null,不查询数据库
        if (!userBloomFilter.mightContain(userId)) {
            System.out.println("布隆过滤器拦截不存在的用户ID:" + userId);
            return null;
        }
        // 存在则查询数据库
        return userMapper.selectById(userId);
    }
}
(2)缓存击穿(热点 key 过期瞬间,大量请求直击数据库)
  • 解决方案:互斥锁 + 热点 key 永不过期

java

运行

复制代码
// 方式1:互斥锁(使用Redis分布式锁,避免并发查询数据库)
@Service
public class ProductService {

    @Resource
    private StringRedisTemplate redisTemplate;
    @Resource
    private ProductMapper productMapper;

    @Cacheable(value = "productCache", key = "#productId")
    public Product getProductById(Long productId) {
        // 1. 尝试获取分布式锁
        String lockKey = "lock:product:" + productId;
        boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS);
        if (locked) {
            try {
                // 2. 加锁成功:查询数据库并缓存
                Product product = productMapper.selectById(productId);
                return product;
            } finally {
                // 3. 释放锁
                redisTemplate.delete(lockKey);
            }
        } else {
            // 4. 加锁失败:重试(或返回默认值)
            try {
                Thread.sleep(100); // 等待100ms后重试
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return getProductById(productId); // 递归重试
        }
    }
}

// 方式2:热点key永不过期(定期后台刷新)
@Scheduled(fixedRate = 5 * 60 * 1000) // 每5分钟刷新一次热点商品缓存
public void refreshHotProductCache() {
    List<Long> hotProductIds = Arrays.asList(1001L, 1002L, 1003L); // 热点商品ID列表
    for (Long productId : hotProductIds) {
        Product product = productMapper.selectById(productId);
        // 手动更新缓存
        redisTemplate.opsForValue().set("productCache:" + productId, product, 1, TimeUnit.HOURS);
    }
}
(3)缓存雪崩(大量 key 同时过期,导致数据库压力骤增)
  • 解决方案:过期时间随机化 + 多级缓存

java

运行

复制代码
// 方式1:过期时间随机化(避免key集中过期)
@Bean
public CacheManager cacheManager() {
    SimpleCacheManager cacheManager = new SimpleCacheManager();
    List<CaffeineCache> caches = new ArrayList<>();
    caches.add(new CaffeineCache(
            "productCache",
            Caffeine.newBuilder()
                    .maximumSize(1000)
                    .expireAfterWrite(1, TimeUnit.MINUTES)
                    // 过期时间随机化(1分钟 ± 30秒)
                    .expireAfterWrite((long) (60 + Math.random() * 60), TimeUnit.SECONDS)
                    .build()
    ));
    cacheManager.setCaches(caches);
    return cacheManager;
}

// 方式2:多级缓存(本地缓存 + Redis分布式缓存)
@Service
public class MultiLevelCacheService {

    @Resource
    private LoadingCache<String, Product> localCache; // Caffeine本地缓存
    @Resource
    private StringRedisTemplate redisTemplate;
    @Resource
    private ProductMapper productMapper;

    public Product getProductById(Long productId) {
        String key = "product:" + productId;
        Product product;

        // 1. 先查本地缓存(Caffeine)
        product = localCache.getIfPresent(key);
        if (product != null) {
            return product;
        }

        // 2. 本地缓存未命中:查Redis分布式缓存
        String redisValue = redisTemplate.opsForValue().get(key);
        if (redisValue != null) {
            product = JSON.parseObject(redisValue, Product.class);
            // 同步到本地缓存
            localCache.put(key, product);
            return product;
        }

        // 3. Redis未命中:查数据库,同步到Redis和本地缓存
        product = productMapper.selectById(productId);
        redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 30, TimeUnit.MINUTES);
        localCache.put(key, product);
        return product;
    }
}

五、Windows 环境下的调试与问题排查

1. 缓存可视化监控(Windows 本地开发)

使用 caffeine-monitor 依赖查看缓存状态(开发环境专用):

xml

复制代码
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine-monitor</artifactId>
    <version>3.1.8</version>
    <scope>test</scope>
</dependency>

调试代码:

java

运行

复制代码
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import java.util.concurrent.TimeUnit;

public class CaffeineMonitorDemo {
    public static void main(String[] args) {
        Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(100)
                .recordStats() // 开启统计
                .expireAfterWrite(5, TimeUnit.SECONDS)
                .build();

        // 模拟缓存操作
        cache.put("a", "1");
        cache.getIfPresent("a");
        cache.getIfPresent("b"); // 缓存未命中

        // 打印缓存统计信息
        CacheStats stats = cache.stats();
        System.out.println("缓存命中率:" + stats.hitRate()); // 输出:0.5
        System.out.println("未命中率:" + stats.missRate()); // 输出:0.5
        System.out.println("缓存条目数:" + cache.estimatedSize()); // 输出:1
    }
}

2. Windows 下常见问题与解决方案

问题现象 原因分析 解决方案
缓存注解不生效 未加 @EnableCaching 注解 在配置类上添加 @EnableCaching
缓存键冲突 key 生成规则重复(如参数类型不同) 显式指定 key(如 key = "#userId.toString()"
本地缓存占用内存过高 maximumSize 配置过大 减小 maximumSize,添加 evictionListener 监控淘汰情况
异步加载缓存阻塞主线程 线程池核心线程数过少 增大线程池核心线程数(如 newFixedThreadPool(10)

六、扩展:Caffeine + Redis 多级缓存架构(生产实战)

结合你之前关注的 Redis,推荐 「本地缓存(Caffeine)+ 分布式缓存(Redis)」 架构,兼顾性能和一致性:

java

运行

复制代码
@Service
public class MultiLevelCacheService {

    // 1. 本地缓存(Caffeine)
    private final LoadingCache<String, User> localCache = Caffeine.newBuilder()
            .maximumSize(500)
            .expireAfterAccess(1, TimeUnit.MINUTES)
            .build(key -> loadFromRedis(key));

    // 2. 分布式缓存(Redis)
    @Resource
    private StringRedisTemplate redisTemplate;

    // 3. 数据库
    @Resource
    private UserMapper userMapper;

    // 查询用户:本地缓存 → Redis → 数据库
    public User getUserById(Long userId) {
        String key = "user:" + userId;
        User user;

        // 步骤1:查询本地缓存
        user = localCache.get(key);
        if (user != null) {
            return user;
        }

        // 步骤2:查询Redis
        String redisValue = redisTemplate.opsForValue().get(key);
        if (redisValue != null) {
            user = JSON.parseObject(redisValue, User.class);
            // 同步到本地缓存
            localCache.put(key, user);
            return user;
        }

        // 步骤3:查询数据库
        user = userMapper.selectById(userId);
        if (user != null) {
            // 同步到Redis(30分钟过期)
            redisTemplate.opsForValue().set(key, JSON.toJSONString(user), 30, TimeUnit.MINUTES);
            // 同步到本地缓存
            localCache.put(key, user);
        }
        return user;
    }

    // 更新用户:数据库 → Redis → 本地缓存
    public void updateUser(User user) {
        String key = "user:" + user.getId();
        // 步骤1:更新数据库
        userMapper.updateById(user);
        // 步骤2:更新Redis
        redisTemplate.opsForValue().set(key, JSON.toJSONString(user), 30, TimeUnit.MINUTES);
        // 步骤3:更新本地缓存
        localCache.put(key, user);
    }

    // 从Redis加载数据(本地缓存加载逻辑)
    private User loadFromRedis(String key) {
        String redisValue = redisTemplate.opsForValue().get(key);
        return redisValue != null ? JSON.parseObject(redisValue, User.class) : null;
    }
}

架构优势

  • 本地缓存:响应时间 < 1ms,减轻 Redis 压力;
  • Redis 缓存:解决分布式系统缓存一致性问题;
  • 数据库:最终数据来源,避免缓存雪崩。

总结

Caffeine 是 Java 本地缓存的首选方案,结合 Spring Boot 注解可快速集成到项目中。核心要点:

  1. 基础使用:通过 Caffeine.newBuilder() 配置缓存策略,@Cacheable 等注解简化开发;
  2. 性能优化:根据场景选择过期策略,配置合理的 maximumSize 和线程池;
  3. 生产防护:针对穿透 / 击穿 / 雪崩问题,结合布隆过滤器、互斥锁、随机过期时间;
  4. 架构扩展:与 Redis 搭配实现多级缓存,兼顾性能和分布式一致性。
相关推荐
n***271934 分钟前
JAVA (Springboot) i18n国际化语言配置
java·spring boot·python
汤姆yu35 分钟前
基于springboot的校园家教信息系统
java·spring boot·后端·校园家教
q***062937 分钟前
Spring Boot--@PathVariable、@RequestParam、@RequestBody
java·spring boot·后端
urkay-39 分钟前
Android 切换应用语言
android·java·kotlin·iphone·androidx
小石头 100861 小时前
【Java】String类(超级详细!!!)
java·开发语言·算法
.柒宇.1 小时前
力扣hot100---42.接雨水(java版)
java·算法·leetcode
D***44141 小时前
Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘(上)
java·spring boot·后端
迈巴赫车主1 小时前
蓝桥杯20534爆破 java
java·数据结构·算法·职场和发展·蓝桥杯
汝生淮南吾在北1 小时前
SpringBoot+Vue在线笔记管理系统
java·vue.js·spring boot·笔记·毕业设计·毕设