本地进程缓存 Caffeine

🍓 简介:java系列技术分享(👉持续更新中…🔥)
🍓 初衷:一起学习、一起进步、坚持不懈
🍓 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正🙏
🍓 希望这篇文章对你有所帮助,欢迎点赞 👍 收藏 ⭐留言 📝

🍓 更多文章请点击
在这里插入图片描述在这里插入图片描述

12

前言

缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。我们把缓存分为两类:

  • 分布式缓存,例如Redis
    • 优点:存储容量更大、可靠性更好、可以在集群间共享
    • 缺点:访问缓存有网络开销
    • 场景:缓存数据量较大、可靠性要求较高、需要在集群间共享
  • 进程本地缓存,例如HashMap、GuavaCache:
    • 优点:读取本地内存,没有网络开销,速度更快
    • 缺点:存储容量有限、可靠性较低、无法共享
    • 场景:性能要求较高,缓存数据量较小

一、 什么是Caffeine?

Caffeine是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine。

  • Caffeine是基于java8实现的新一代缓存工具,缓存性能接近理论最优,可以看作是Guava Cache的增强版,功能上两者类似。
  • 不同的是Caffeine采用了一种结合LRU、LFU优点的算法W-TinyLFU在性能上有明显的优越性。
  • ConcurrentMap将存储所有存入的数据,直到你显式将其移除;
  • Caffeine将通过给定的配置,自动移除“不常用”的数据,以保持内存的合理占用。

因此,一种更好的理解方式是:Cache是一种带有存储和移除策略的Map。

GitHub地址:https://github.com/ben-manes/caffeine

在这里插入图片描述

二、Caffeine的基本使用

使用Caffeine,需要在工程中引入如下依赖

	<dependency>
		  <groupId>com.github.ben-manes.caffeine</groupId>
		  <artifactId>caffeine</artifactId>
		  <!--https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeinez找最新版-->
		  <version>2.9.0</version>
	</dependency>
public class CaffeineTest {
    public static void main(String[] args) {
        // 构建cache对象
        Cache<String, String> cache = Caffeine.newBuilder().build();

        // 存数据
        cache.put("key", "value");

        // 取数据
        String key= cache.getIfPresent("key");
        System.out.println("key= " + key);

        // 取数据,包含两个参数:
        // 参数一:缓存的key
        // 参数二:Lambda表达式,表达式参数就是缓存的key,方法体是查询数据库的逻辑
        // 优先根据key查询JVM缓存,如果未命中,则执行参数二的Lambda表达式
        String defaultKey = cache.get("defaultKey ", k -> {
            // 根据key去数据库查询数据
            return "value";
        });

        System.out.println("defaultKey = " + defaultKey );
    }
}

三、清除策略

Caffeine既然是缓存的一种,肯定需要有缓存的清除策略,不然的话内存总会有耗尽的时候。

  • 基于容量:设置缓存的数量上限

    // 创建缓存对象
    Cache<String, String> cache = Caffeine.newBuilder()
        .maximumSize(1) // 设置缓存大小上限为 1
        .build();
    
  • 基于时间:设置缓存的有效时间

    // 创建缓存对象
    Cache<String, String> cache = Caffeine.newBuilder()
        // 设置缓存有效期为 10 秒,从最后一次写入开始计时 
        .expireAfterWrite(Duration.ofSeconds(10)) 
        .build();
    
  • 基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用。

注意:在默认情况下,当一个缓存元素过期的时候,Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后,或者在空闲时间完成对失效数据的驱逐。

四、实现JVM进程缓存

首先,我们需要定义两个Caffeine的缓存对象,定义CaffeineConfig类:

@Configuration
public class CaffeineConfig {

    @Bean
    public Cache<Long, Item> itemCache(){
        return Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(10_000)
                .build();
    }

    @Bean
    public Cache<Long, ItemStock> stockCache(){
        return Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(10_000)
                .build();
    }
}

添加缓存逻辑:

@RestController
@RequestMapping("item")
public class ItemController {

    @Autowired
    private IItemService itemService;
    
    @Autowired
    private IItemStockService stockService;

    @Autowired
    private Cache<Long, Item> itemCache;
    
    @Autowired
    private Cache<Long, ItemStock> stockCache;
    
    // ...其它略
    
    @GetMapping("/{id}")
    public Item findById(@PathVariable("id") Long id) {
        return itemCache.get(id, key -> itemService.getById(key));
    }

    @GetMapping("/stock/{id}")
    public ItemStock findStockById(@PathVariable("id") Long id) {
        return stockCache.get(id, key -> stockService.getById(key));
    }
}

在这里插入图片描述在这里插入图片描述

### Caffeine本地缓存持久化实现 为了使Caffeine缓存具备持久化能力,在应用关闭时保存当前缓存状态,并在启动时加载之前的状态,通常有两种方式来达成这一目标: #### 方案一:基于文件系统的序列化/反序列化机制 通过自定义`CacheLoader`和`RemovalListener`接口配合使用,可以在每次移除条目或更新条目的时候将其写入磁盘;而在读取不存在的数据项时尝试从磁盘恢复。 ```java public class PersistentCache<K, V> { private final Cache<K, V> cache; public PersistentCache() throws IOException { File file = new File("cache.ser"); if (file.exists()) { try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { Map<K, V> map = (Map<K, V>) ois.readObject(); this.cache = Caffeine.newBuilder() .removalListener((key, value, cause) -> saveToFile(key, value)) .build(k -> loadFromFile(k), map::get); } } else { this.cache = Caffeine.newBuilder() .removalListener(this::saveToFile) .build(this::loadFromFile); } } private void saveToFile(K key, V value) { // 序列化逻辑... } @SuppressWarnings("unchecked") private V loadFromFile(K key) { // 反序列化逻辑... return null; // 返回默认值或其他处理 } } ``` 此方案利用了Java内置的对象流来进行简单的对象存储操作[^3]。 #### 方案二:集成第三方组件完成更复杂的持久层设计 对于更加复杂的应用场景,则可能需要引入额外的技术栈支持,比如嵌入式的轻量级关系型数据库(如SQLite),NoSQL解决方案(如LevelDB)。这些技术能够提供更好的性能以及事务特性,适用于大规模数据集下的高效检索需求。不过这超出了单纯讨论Caffeine本身的范畴,涉及到具体业务逻辑的设计考量[^1]。 上述两种方法都可以有效地解决Caffeine缓存进程重启后的数据丢失问题,开发者可以根据实际项目的规模和技术选型灵活选用合适的策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dream_sky分享

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值