侧边栏壁纸
博主头像
拾荒的小海螺博主等级

只有想不到的,没有做不到的

  • 累计撰写 224 篇文章
  • 累计创建 19 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

JAVA:Spring Boot 集成 Caffeine 实现本地缓存的技术博客

拾荒的小海螺
2025-01-16 / 0 评论 / 0 点赞 / 25 阅读 / 11901 字

1、简述

在现代应用中,缓存是提高系统性能的关键手段。Caffeine 是一个基于 Java 的高性能本地缓存库,具备优雅的设计和强大的功能,包括基于容量、时间的自动过期,以及异步刷新缓存数据等特性。

样例代码: https://gitee.com/lhdxhl/springboot-example.git

本文将详细介绍如何在 Spring Boot 中集成 Caffeine,并结合具体代码示例,展示其使用方法。

image-afrs.png

2、主要特点

Caffeine 是 Guava Cache 的替代品,性能优异,主要特点包括:

  • 高性能:基于基准测试,性能优于其他主流缓存库。
  • 灵活配置:支持容量限制、时间过期、异步加载等。
  • 成熟设计:使用 Window TinyLFU 算法,提高缓存命中率。

在使用 Caffeine 之前,需要添加其依赖。以下是 Caffeine 的 Maven 依赖:

<!-- Caffeine Cache -->
<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>2.5.5</version>
</dependency>

3、配置 Caffeine 缓存

3.1 开启缓存支持

在主类上添加 @EnableCaching 注解:

package com.lm.caffeine;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@EnableCaching
@SpringBootApplication
public class CaffeineApplication {
    public static void main(String[] args) {
        SpringApplication.run(CaffeineApplication.class, args);
    }
}

3.2 配置缓存属性

在 application.yml 中定义缓存相关的配置:

spring:
  cache:
    type: caffeine
    cache-names: usersCache, productsCache # 定义缓存名称
    caffeine:
      spec: maximumSize=1024,refreshAfterWrite=60s

3.3 自定义缓存配置

创建一个配置类,定制化 Caffeine 缓存行为:

package com.lm.caffeine.config;

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.caffeine.CaffeineCacheManager;
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
public class CacheConfig {

    /**
     * SimpleCacheManager:
     *    这种缓存管理器允许你在应用程序启动时通过配置多个CaffeineCache来创建多个缓存。
     *    这种方式可以让你为每个方法单独配置缓存过期时间。
     * @Author shdxhl
     * @Method  caffeineCacheManager
     * @Date 9:13 2023-06-13
     * @Param []
     * @return void
     **/
//    @Bean
//    public CacheManager caffeineCacheManager() {
//        SimpleCacheManager cacheManager = new SimpleCacheManager();
//        List<CaffeineCache> caches = new ArrayList<>();
//        caches.add(new CaffeineCache("usersCache",
//                Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.SECONDS).build()));
//        caches.add(new CaffeineCache("productsCache",
//                Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).build()));
//        cacheManager.setCaches(caches);
//        return cacheManager;
//    }

    /**
     * CaffeineCacheManager:
     *   这种缓存管理器使用了一个全局的Caffeine配置来创建所有的缓存。
     *   这种方式不能为每个方法单独配置缓存过期时间,但是可以在程序启动时配置全局的缓存配置,这样就可以轻松地设置所有方法的缓存过期时间。
     * @Author shdxhl
     * @Method  cacheManager
     * @Date 9:13 2023-06-13
     * @Param []
     * @return void
     **/
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
                // 初始大小
                .initialCapacity(10)
                // 最大容量
                .maximumSize(100)
                // 打开统计
                .recordStats()
                // 5分钟不访问自动丢弃
                .expireAfterAccess(5, TimeUnit.MINUTES);
        caffeineCacheManager.setCaffeine(caffeine);
        // 设定缓存器名称
        caffeineCacheManager.setCacheNames(getCacheNames());
        // 值不可为空
        caffeineCacheManager.setAllowNullValues(false);
        return caffeineCacheManager;
    }

    private static List<String> getCacheNames() {
        List<String> names = new ArrayList<>(1);
        names.add("usersCache");
        names.add("productsCache");
        return names;
    }
}

4、具体示例

4.1 在 Service 层添加缓存

使用 @Cacheable 注解实现缓存:

package com.lm.caffeine.service;

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 {

    @Cacheable(value = "usersCache", key = "#id",unless = "#result==null")
    public String getUserById(String id) {
        System.out.println("查询数据库...");
        return "User-" + id;
    }

    /**
     * 更新缓存
     */
    @CachePut(cacheNames = "usersCache", key = "#id",unless = "#result==null")
    public String updateUser(String id) {
        System.out.println("更新数据库...");
        return "User-" + id;
    }

    /**
     * 删除缓存
     */
    @CacheEvict(cacheNames = "usersCache", key = "#id")
    public String delUser(String id) {
        System.out.println("删除数据库...");
        return "User-" + id;
    }
}

4.2 测试缓存效果

创建一个简单的 Controller 测试缓存功能:

package com.lm.caffeine.controller;

import com.lm.caffeine.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

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

    @Autowired
    private UserService userService;

    @GetMapping("/getUserById")
    public String getUserById(@RequestParam String id) {
        String userId  = userService.getUserById(id);
        return "Order created with ID: " + userId;
    }
}

5、高级运用

5.1 基于最大大小和时间的缓存清理

为一个用户数据查询接口实现缓存,要求缓存的最大条目数为 100,同时设置 5 分钟的过期时间。

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

import java.util.concurrent.TimeUnit;

public class CacheExample {
    public static void main(String[] args) {
        // 创建缓存实例
        Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(100) // 最大条目数
                .expireAfterWrite(5, TimeUnit.MINUTES) // 写入后 5 分钟过期
                .build();

        // 添加缓存
        cache.put("user:1", "John Doe");

        // 获取缓存
        String value = cache.getIfPresent("user:1");
        System.out.println("从缓存获取的值: " + value);

        // 缓存过期后返回 null
        try {
            Thread.sleep(TimeUnit.MINUTES.toMillis(6));
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        value = cache.getIfPresent("user:1");
        System.out.println("过期后获取的值: " + value);
    }
}

5.2 基于权重的缓存清理

根据缓存的对象大小(如 JSON 的字节数)进行清理,以确保缓存不会占用过多内存。

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

public class WeightedCacheExample {
    public static void main(String[] args) {
        // 创建缓存实例
        Cache<String, String> cache = Caffeine.newBuilder()
                .maximumWeight(10_000) // 最大权重(例如字节数)
                .weigher((String key, String value) -> value.length()) // 权重计算
                .build();

        // 添加缓存
        cache.put("key1", "smallValue"); // 权重较小
        cache.put("key2", "thisIsALargeValueExceedingWeightLimit"); // 权重大

        // 读取缓存
        System.out.println("key1: " + cache.getIfPresent("key1"));
        System.out.println("key2: " + cache.getIfPresent("key2"));
    }
}

5.3 异步刷新缓存

缓存中的数据需要定期刷新,以确保数据的实时性,但不希望用户直接触发时等待加载。

import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class AsyncCacheExample {
    public static void main(String[] args) {
        // 创建异步缓存实例
        AsyncLoadingCache<String, String> asyncCache = Caffeine.newBuilder()
                .expireAfterWrite(5, TimeUnit.MINUTES) // 写入后 5 分钟过期
                .refreshAfterWrite(2, TimeUnit.MINUTES) // 写入后 2 分钟刷新
                .buildAsync(key -> loadDataFromDatabase(key)); // 异步加载

        // 异步获取缓存
        asyncCache.get("user:1").thenAccept(value -> System.out.println("缓存值: " + value));

        // 主线程等待
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private static String loadDataFromDatabase(String key) {
        System.out.println("加载数据库数据 for key: " + key);
        return "UserData:" + key;
    }
}

5.4 统计缓存使用情况

监控缓存命中率和加载时间,便于优化缓存策略。

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

import java.util.concurrent.TimeUnit;

public class CacheStatsExample {
    public static void main(String[] args) {
        // 创建缓存实例
        Cache<String, String> cache = Caffeine.newBuilder()
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .recordStats() // 启用统计
                .build();

        // 使用缓存
        cache.put("key1", "value1");
        cache.getIfPresent("key1");
        cache.getIfPresent("key2"); // 未命中

        // 打印统计信息
        System.out.println("缓存统计: " + cache.stats());
    }
}

6、总结

通过 Spring Boot 集成 Caffeine,我们可以轻松实现高效的本地缓存,并灵活定制缓存行为。

核心功能总结:

  • 快速集成:通过 spring-boot-starter-cache 快速启用缓存功能。
  • 灵活配置:支持基于时间、容量的缓存过期机制。
  • 高性能:优秀的算法保障高命中率。

在实际项目中,根据业务需求合理配置缓存策略,能显著提升应用性能。

0

评论区