Loading... # 高性能进程缓存caffeine ## 简介 缓存是一个很好的增加查询速度的解决方案,也就是用空间换时间,通常本机缓存可选方案有多种: - ConcurrentHashMap/HashMap: 利用HashMap作为缓存方案,有点是JDK原生支持,缺点是没有淘汰策略与过期机制,需要自己实现。 - GuavaCache: Google实现的一套本地缓存方案,有超时机制,使用简单。 - caffeine: 在设计上参考了GuavaCache的经验,也进行了大量优化。 ## 填充策略 ### 手动填充 ```java Cache<String, String> cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(100).weakKeys().build(); cache.put("key", "key1"); cache.get("key", key -> "null"); ``` 可以用put方法存入一个值,也可以用get方法取值,get方法传入一个Function接口,在key不存在的时候返回默认值。 - maximumSize:设置缓存最大条目数,超过条目则触发回收。 - maximumWeight:设置缓存最大权重,设置权重是通过weigher方法, 需要注意的是权重也是限制缓存大小的参数,并不会影响缓存淘汰策略,也不能和maximumSize方法一起使用。 - weakKeys:将key设置为弱引用,在GC时可以直接淘汰 - weakValues:将value设置为弱引用,在GC时可以直接淘汰 - softValues:将value设置为软引用,在内存溢出前可以直接淘汰 - expireAfterWrite:写入后隔段时间过期 - expireAfterAccess:访问后隔断时间过期 - refreshAfterWrite:写入后隔断时间刷新 - removalListener:缓存淘汰监听器,配置监听器后,每个条目淘汰时都会调用该监听器 - writer:writer监听器其实提供了两个监听,一个是缓存写入或更新是的write,一个是缓存淘汰时的delete,每个条目淘汰时都会调用该监听器 ### 同步填充 ```java LoadingCache<String, String> cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(100).weakKeys().build(new CacheLoader<String, String>() { @Override public @Nullable String load(@NonNull String key) throws Exception { return load(key); } @Override public @NonNull Map<String, String> loadAll(@NonNull Iterable<? extends String> keys) throws Exception { return loadAll(keys); } }); ``` 通过在build方法中传入一个CacheLoader的实现来进行同步填充,CacheLoader中的load方法制定了对key的计算,也可以重写loadAll来进行批量计算。 ### 异步填充 ```java AsyncLoadingCache<String, String> cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(100).weakKeys().buildAsync(new AsyncCacheLoader<String, String>() { @Override public @NonNull CompletableFuture<String> asyncLoad(@NonNull String key, @NonNull Executor executor) { return asyncLoad(key, executor); } @Override public @NonNull CompletableFuture<Map<String, String>> asyncLoadAll(@NonNull Iterable<? extends String> keys, @NonNull Executor executor) { return asyncLoadAll(keys, executor); } @Override public @NonNull CompletableFuture<String> asyncReload(@NonNull String key, @NonNull String oldValue, @NonNull Executor executor) { return asyncReload(key, oldValue, executor); } }); try { cache.get("key", key -> "value").get(3,TimeUnit.SECONDS); } catch (InterruptedException | TimeoutException | ExecutionException e) { e.printStackTrace(); } ``` 异步填充与同步填充大致相似,区别是传入一个执行器进行异步执行,并且返回一个CompletableFuture对象,可以通过CompletableFuture.get来获取数据并设置超时时间。 ## springboot集成 ### 引入依赖 ```xml <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.6.0</version> </dependency> ``` ### 开启缓存支持 ```java @EnableCaching @SpringBootApplication public class SpringbootCaffeineApplication { public static void main(String[] args) { SpringApplication.run(SpringbootCaffeineApplication.class, args); } } ``` 使用`@EnableCaching`注解让springboot开启对缓存的支持 ### 配置文件 ```yaml spring: cache: cache-names: - foo - bar - people caffeine: spec: - maximumSize=500 - expireAfterAccess=600s ``` - spring.cache.cache-names: 可以在启动时创建缓存。 - spring.cache.caffeine.spec: 定义的特殊缓存 - com.github.benmanes.caffeine.cache.CaffeineSpec: bean定义 - com.github.benmanes.caffeine.cache.Caffeine: bean定义 如果使用了refreshAfterWrite配置还必须指定一个CacheLoader,如: ```java @Configuration public class CaffeineConfiguration { @Bean public CacheLoader<Object,Object> cacheLoader(){ return new CacheLoader<Object, Object>() { @Override public Object load(Object o) throws Exception { return null; } // 重写这个方法将oldValue值返回回去,进而刷新缓存 @Override public Object reload(Object key, Object oldValue) throws Exception { return oldValue; } }; } } ``` * expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准。 * maximumSize和maximumWeight不可以同时使用 * weakValues和softValues不可以同时使用 ### 示例代码 ```java @Service public class PersonService { @Autowired private PersonDao personDao; @CachePut(value = "people", key = "#person.id") public Person save(Person person) { return personDao.save(person); } @Cacheable(value = "people", key = "#id", sync = true) public Person findOne(Integer id) { return personDao.findOne(id); } } ``` * @CachePut: 更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行) * @Cacheable: 可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。 * value: 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 * key: 缓存的key,可以为空,如果指定要按照SpEL表达式编写,如不指定,则按照方法所有参数组合 * #id: 表示方法参数名是id * #person.id: 表示person中的id值 * #p0: 表示第一个参数 * #p0.id: 表示第一个参数里的id属性值 * condition: 属性指定发生的条件,condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如`#person.id%2==0`表示只有当person的id为偶数时才会进行缓存。 * @CacheEvict: 用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。下面我们来介绍一下新出现的两个属性allEntries和beforeInvocation。 * allEntries: allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。 * beforeInvocation属性: 清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。 * @Caching: 可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。 Last modification:November 27th, 2020 at 05:28 pm © 允许规范转载