当前位置:   article > 正文

本地缓存之王Caffeine 保姆级教程(值得珍藏)_caffeine教程

caffeine教程

点击下载《本地缓存之王Caffeine 保姆级教程(值得珍藏)》

1. 简介

在编程领域,缓存是不可或缺的一部分,从处理器到应用层,其应用无处不在。从根本上讲,缓存是利用空间换取时间的一种策略,通过优化数据存储方式,提高后续数据访问速度。

对于Java开发者来说,有很多常用的缓存解决方案,例如EhCache和Memcached等。这些解决方案的核心目标是提高系统吞吐量,减轻数据库等持久层的压力。

根据其部署和应用范围,缓存可以分为本地缓存和分布式缓存两种类型。Caffeine是一种非常优秀的本地缓存解决方案,而Redis则广泛用于分布式缓存场景。

Caffeine是一个基于Java 1.8的高性能本地缓存库,源自Guava的改进。自Spring 5开始,Caffeine已成为默认的缓存实现,取代了原先的Google Guava。官方资料显示,Caffeine的缓存命中率已接近理论最优值。实际上,Caffeine与ConcurrentMap在功能上有许多相似之处,都支持并发操作,且数据的存取时间复杂度为O(1)。然而,二者在数据管理策略上存在显著差异:

  • ConcurrentMap会保留存入的所有数据,除非用户显式地移除;
  • 而Caffeine则根据预设的配置自动剔除“不常用”的数据,确保内存的合理使用。

因此,更恰当的理解是:Cache是一种具备存储和移除策略的Map。

2. 核心特性

  • 高性能:Caffeine采用了多种优化技术,包括基于链表和哈希表的数据结构、优化的内存访问模式以及针对并发访问的优化算法,以减少缓存的内存占用和提高缓存访问速度。这使得它在读写操作上有着卓越的表现。
  • 内存管理:Caffeine实现了自适应的内存管理,能够根据缓存的使用情况动态调整内存分配。它还支持不同的缓存过期策略,有效控制内存使用。
  • 过期策略:Caffeine支持多种缓存过期策略,如基于时间、基于大小、基于引用等,同时也允许用户自定义过期策略。
  • 简洁而强大的API:Caffeine提供了简洁而强大的API,使得缓存的创建和使用变得相对简单。
  • 缓存加载器:Caffeine提供了CacheLoader接口,使得异步加载和刷新缓存项变得更容易。
  • 监听器和事件:可以使用监听器跟踪缓存的变化,对缓存进行事件监听和处理。

3. Caffeine使用

3.1 手动创建

import com.github.benmanes.caffeine.cache.Cache;  
import com.github.benmanes.caffeine.cache.Caffeine;  
  
import java.util.concurrent.TimeUnit;  
  
public class CaffeineManualExample {  
    // 创建一个指定最大容量的缓存,缓存项在写入后10分钟过期  
    private static final Cache<String, String> CACHE = Caffeine.newBuilder()  
            .maximumSize(100)  
            .expireAfterWrite(10, TimeUnit.MINUTES)  
            .build();  
  
    public static void main(String[] args) {  
        // 向缓存中添加数据  
        CACHE.put("key1", "value1");  
        CACHE.put("key2", "value2");  
  
        // 从缓存中获取数据  
        String value1 = CACHE.getIfPresent("key1");  
        System.out.println("Value for key1: " + value1);  
  
        // 等待一段时间后,缓存项过期,再次尝试获取将返回null  
        try {  
            Thread.sleep(TimeUnit.MINUTES.toMillis(15));  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        String value2 = CACHE.getIfPresent("key2");  
        System.out.println("Value for key2 (after expiration): " + value2); // 输出:null  
    }  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

3.2 自动创建

在Spring Boot应用中,你可以使用@Cacheable@CacheEvict注解来自动创建缓存。首先,确保你的Spring Boot项目已经添加了Spring Boot Cache Starter的依赖。

常用注解

  • @Cacheable :表示该方法支持缓存。当调用被注解的方法时,如果对应的键已经存在缓存,则不再执行方法体,而从缓存中直接返回。当方法返回null时,将不进行缓存操作。
  • @CachePut :表示执行该方法后,其值将作为最新结果更新到缓存中,每次都会执行该方法。
  • @CacheEvict :表示执行该方法后,将触发缓存清除操作。
  • @Caching :用于组合前三个注解。

常用注解属性

  • cacheNames/value :缓存组件的名字,即cacheManager中缓存的名称。
  • key :缓存数据时使用的key。默认使用方法参数值,也可以使用SpEL表达式进行编写。
  • keyGenerator :和key二选一使用。
  • cacheManager :指定使用的缓存管理器。
  • condition :在方法执行开始前检查,在符合condition的情况下,进行缓存。
  • unless :在方法执行完成后检查,在符合unless的情况下,不进行缓存。
  • sync :是否使用同步模式。若使用同步模式,在多个线程同时对一个key进行load时,其他线程将被阻塞。

下面是一个简单的例子:

  1. 添加依赖:在pom.xml中添加Spring Boot Cache Starter的依赖
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-cache</artifactId>  
</dependency>
  • 1
  • 2
  • 3
  • 4
  1. 配置缓存:在application.propertiesapplication.yml中配置Caffeine作为缓存提供者
spring.cache.type=caffeine  
spring.cache.caffeine.spec=maximumSize=500,expireAfterWrite=60m # 指定缓存最大容量为500,缓存项在写入后60分钟过期。
  • 1
  • 2
  1. 使用注解:创建一个服务类,并使用@Cacheable@CacheEvict注解来标记方法
import org.springframework.cache.annotation.Cacheable;  
import org.springframework.cache.annotation.CacheEvict;  
import org.springframework.stereotype.Service;  
  
@Service  
public class ExampleService {  
    @Cacheable(value = "exampleCache") // 将方法结果缓存到名为"exampleCache"的缓存中。  
    public String getData() {  
        // 模拟耗时操作,如数据库查询。这里只是返回一个字符串。  
        return "data";  
    }
    
    @CacheEvict(value = "exampleCache", key = "#id") // 从名为"exampleCache"的缓存中移除指定键(#id)的缓存项。  
    public void evictData(String id) { 
    // 该方法目前没有实际功能,只是为了演示如何使用@CacheEvict。 
    }  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.3 Caffeine的异步获取

Caffeine的异步获取功能允许你在缓存数据加载时执行异步操作,从而减少应用程序的等待时间。你可以使用CacheLoader接口来定义异步加载缓存项的逻辑。当缓存中没有对应的数据时,Caffeine会自动触发CacheLoader的实现类来异步加载数据。一旦数据加载完成,它将被存入缓存并返回给调用者。使用异步获取功能可以解决应用程序在等待数据加载时阻塞的问题,提高整体性能。

使用示例

  1. 定义一个实现CacheLoader接口的类,实现load方法:
import com.github.benmanes.caffeine.cache.CacheLoader;  
import com.github.benmanes.caffeine.cache.LoadingCache;  
  
import java.util.concurrent.CompletableFuture;  
import java.util.concurrent.TimeUnit;  
  
public class AsyncDataLoader implements CacheLoader<String, String> {  
    @Override  
    public CompletableFuture<String> load(String key) throws Exception {  
        // 模拟耗时操作,如数据库查询。这里只是返回一个字符串。  
        String data = "Async data for key: " + key;  
        return CompletableFuture.completedFuture(data); // 返回异步加载的结果。  
    }  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  1. 创建一个基于AsyncDataLoaderLoadingCache实例:
import com.github.benmanes.caffeine.cache.Caffeine;  
import com.github.benmanes.caffeine.cache.LoadingCache;  
  
import java.util.concurrent.TimeUnit;  
  
public class CaffeineAsyncExample {  
    public static void main(String[] args) {  
        // 创建异步加载缓存的实例,指定缓存的容量和过期时间等配置。  
        LoadingCache<String, String> cache = Caffeine.newBuilder()  
                .maximumSize(100) // 缓存最大容量为100个键值对。  
                .expireAfterWrite(10, TimeUnit.MINUTES) // 缓存项在写入后10分钟过期。  
                .build(new AsyncDataLoader()); // 使用自定义的AsyncDataLoader作为加载器。  
  
        // 从缓存中获取数据,如果缓存中没有数据,则会触发异步加载。  
        String data = cache.get("key1"); // 返回的数据可能是null,因为此时数据可能正在异步加载中。  
        System.out.println("Data for key1: " + data); // 输出可能为null,因为数据可能还没有加载完成。  
    }  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在上面的示例中,我们定义了一个AsyncDataLoader类来实现CacheLoader接口。该类中的load方法用于异步加载数据。然后,我们使用Caffeine的LoadingCache构建器创建了一个基于AsyncDataLoader的缓存实例。当从缓存中获取数据时,如果缓存中没有对应的数据,Caffeine会自动调用AsyncDataLoaderload方法进行异步加载。请注意,获取到的数据可能为null,因为此时数据可能正在异步加载中。你可以根据实际需求对异步加载的数据进行处理。

3.4 驱逐策略

Caffeine 的驱逐策略决定了当缓存满了之后,哪些缓存项会被移除。了解驱逐策略对于正确使用缓存和优化缓存性能非常重要。

Caffeine 支持多种驱逐策略,以下是其中一些:

  1. 最近最少使用(LRU):这是最常见的驱逐策略,它将最长时间未被使用的缓存项驱逐出缓存。当缓存满了并且需要添加新的缓存项时,它会移除最长时间未被使用的缓存项。
  2. 先进先出(FIFO):这种策略按照缓存项的插入顺序进行驱逐。当缓存满了并且需要添加新的缓存项时,它会移除最早插入的缓存项。
  3. 基于大小的驱逐:这种策略根据缓存项的大小进行驱逐。当缓存满了并且需要添加新的缓存项时,它会移除最小的缓存项。这对于需要存储大量数据的场景非常有用,例如图片缓存。
  4. 基于时间的驱逐:这种策略根据缓存项的过期时间进行驱逐。当缓存满了并且需要添加新的缓存项时,它会移除最早过期的缓存项。
  5. 基于引用计数的驱逐:这种策略使用引用计数来跟踪缓存项的引用次数。当缓存满了并且需要添加新的缓存项时,它会移除引用计数最低的缓存项。这可以帮助避免热点数据占据整个缓存空间。
  6. 基于权重的驱逐:这种策略根据缓存项的权重进行驱逐。当缓存满了并且需要添加新的缓存项时,它会移除权重最小的缓存项。这可以帮助在缓存中保留更有价值的数据。

在 Caffeine 中,你可以通过 Cache 构造函数的第二个参数来指定驱逐策略。例如,如果你想使用 LRU 驱逐策略,你可以这样做:

Cache<KeyType, ValueType> cache = Caffeine.newBuilder()  
    .maximumSize(100) // 设置最大容量为100  
    .evictionPolicy(Eviction.LRU) // 设置驱逐策略为LRU  
    .build();
  • 1
  • 2
  • 3
  • 4

3.5 刷新机制

在 Caffeine 中,缓存数据在达到一定条件时会进行刷新,以保持缓存的时效性。下面将对 Caffeine 的刷新机制进行详细讲解。

  1. 缓存项过期

Caffeine 支持设置缓存项的过期时间,当缓存项过期后,Caffeine 会自动将其从缓存中删除。默认情况下,Caffeine 使用 JVM 的当前时间作为过期时间的起始点,即从缓存项创建或最后一次修改时开始计时。当缓存项过期后,Caffeine 会触发一次刷新操作,重新加载缓存项的值。

  1. 懒加载

Caffeine 支持懒加载机制,即只有在访问已过期的缓存项时,才会触发刷新操作。这种机制可以减少不必要的缓存刷新操作,提高缓存的效率。当访问已过期的缓存项时,Caffeine 会异步地执行刷新操作,同时返回缓存项的旧值。一旦刷新操作完成,缓存项的值将被更新,下次访问时将返回新值。

  1. 主动刷新

除了基于过期时间和懒加载的自动刷新外,Caffeine 还支持主动刷新机制。通过调用 Caffeine 实例的 refresh 方法,可以强制刷新指定的缓存项。这种机制适用于在某些情况下需要立即获取最新数据的情况。需要注意的是,主动刷新可能会对缓存性能产生一定的影响,因此应该谨慎使用。

  1. 并发控制

在高并发环境下,多个线程可能同时访问已过期的缓存项,导致多个线程同时执行刷新操作。为了避免这种情况,Caffeine 提供了并发控制机制。通过设置 concurrencyLevel 参数,可以指定每个缓存项允许的最大并发刷新操作数。当超过这个限制时,后续的刷新请求将被阻塞或失败,直到有空闲的刷新槽位。这种机制可以有效地防止过多的线程同时执行刷新操作,提高缓存的性能和稳定性。

下面是一个使用 Caffeine 刷新机制的示例:

首先,定义一个实现了 Refreshable 接口的类,该接口有一个 refresh 方法用于刷新缓存项:

public class Data implements Refreshable<Data> {  
    private String value;  
    private long timestamp;  
  
    public Data(String value) {  
        this.value = value;  
        this.timestamp = System.currentTimeMillis();  
    }  
  
    public String getValue() {  
        return value;  
    }  
  
    public long getTimestamp() {  
        return timestamp;  
    }  
  
    @Override  
    public Data refresh(long ttlMillis) {  
        // 在这里实现刷新逻辑,例如从数据库或其他数据源获取最新数据  
        // 并更新 timestamp 和 value。  
        // 如果 ttlMillis 大于 0,表示设置一个时间限制,超过该时间后缓存项将被驱逐。  
        return this;  
    }  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

然后,在 Caffeine 缓存配置中,使用 refreshAfterWrite 方法指定刷新时间间隔:

Cache<String, Data> cache = Caffeine.newBuilder()  
    .maximumSize(100) // 设置最大容量为100  
    .expireAfterWrite(10, TimeUnit.MINUTES) // 设置写入后10分钟过期  
    .refreshAfterWrite(5, TimeUnit.MINUTES) // 设置写入后5分钟刷新一次  
    .build(key -> new Data(key)); // 自定义加载函数,将键转换为 Data 对象
  • 1
  • 2
  • 3
  • 4
  • 5

最后,在需要使用缓存的地方,从缓存中获取数据,并在必要时刷新缓存项:

String key = "exampleKey";  
Data data = cache.get(key);  
if (data != null && System.currentTimeMillis() - data.getTimestamp() > 5 * 60 * 1000) { // 判断是否超过5分钟未刷新  
    data = data.refresh(30 * 60 * 1000); // 调用 refresh 方法刷新缓存项,设置30分钟TTL(可选)  
    cache.put(key, data); // 将刷新后的数据重新放入缓存中(可选)  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

通过上述示例,你可以使用 Caffeine 的刷新机制来自动更新缓存中的数据。需要注意的是,刷新逻辑的具体实现取决于你的业务需求,可能需要从数据库或其他数据源获取最新数据。

4. 总结

Java Caffeine 是一个高效的缓存库,它通过灵活的配置和多种驱逐策略提供了强大的缓存功能。在 Java 应用程序中,Caffeine 可以帮助我们提高数据访问速度,减轻数据库负载,并改善应用程序的性能。

总的来说,Java Caffeine 是一个功能强大、易于使用的缓存库。通过合理地使用 Caffeine,我们可以提高应用程序的性能和响应速度,并降低对数据库的依赖。在处理大量数据和高并发请求时,Caffeine 可以发挥出其优势,成为 Java 开发者的有力助手。

点击下载《本地缓存之王Caffeine 保姆级教程(值得珍藏)》

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/389751
推荐阅读
相关标签
  

闽ICP备14008679号