Caffeine是一种高性能的缓存库,是基于Java 8的最佳(最优)缓存框架。

Cache(缓存),基于Google Guava,Caffeine提供一个内存缓存,大大改善了设计Guava's cache 和 ConcurrentLinkedHashMap 的体验。

1 LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
2 .maximumSize(10_000)
3 .expireAfterWrite(5, TimeUnit.MINUTES)
4 .refreshAfterWrite(1, TimeUnit.MINUTES)
5 .build(key -> createExpensiveGraph(key));

缓存类似于ConcurrentMap,但二者并不完全相同。最基本的区别是,ConcurrentMap保存添加到其中的所有元素,直到显式地删除它们。另一方面,缓存通常配置为自动删除条目,以限制其内存占用。在某些情况下,LoadingCache或AsyncLoadingCache可能很有用,因为它是自动缓存加载的。

Caffeine提供了灵活的结构来创建缓存,并且有以下特性:

  • 自动加载条目到缓存中,可选异步方式
  • 可以基于大小剔除
  • 可以设置过期时间,时间可以从上次访问或上次写入开始计算
  • 异步刷新
  • keys自动包装在弱引用中
  • values自动包装在弱引用或软引用中
  • 条目剔除通知
  • 缓存访问统计

1.  加载/填充

Caffeine提供以下四种类型的加载策略:

1.1.  Manual

 1 Cache<Key, Graph> cache = Caffeine.newBuilder()
2 .expireAfterWrite(10, TimeUnit.MINUTES)
3 .maximumSize(10_000)
4 .build();
5
6 // Lookup an entry, or null if not found
7 Graph graph = cache.getIfPresent(key);
8 // Lookup and compute an entry if absent, or null if not computable
9 graph = cache.get(key, k -> createExpensiveGraph(key));
10 // Insert or update an entry
11 cache.put(key, graph);
12 // Remove an entry
13 cache.invalidate(key);

Cache接口可以显式地控制检索、更新和删除条目。

1.2.  Loading

1 LoadingCache<Key, Graph> cache = Caffeine.newBuilder()
2 .maximumSize(10_000)
3 .expireAfterWrite(10, TimeUnit.MINUTES)
4 .build(key -> createExpensiveGraph(key));
5
6 // Lookup and compute an entry if absent, or null if not computable
7 Graph graph = cache.get(key);
8 // Lookup and compute entries that are absent
9 Map<Key, Graph> graphs = cache.getAll(keys);

LoadingCache通过关联一个CacheLoader来构建Cache

通过LoadingCache的getAll方法,可以批量查询

1.3.  Asynchronous (Manual)

1 AsyncCache<Key, Graph> cache = Caffeine.newBuilder()
2 .expireAfterWrite(10, TimeUnit.MINUTES)
3 .maximumSize(10_000)
4 .buildAsync();
5
6 // Lookup and asynchronously compute an entry if absent
7 CompletableFuture<Graph> graph = cache.get(key, k -> createExpensiveGraph(key));

AsyncCache是另一种Cache,它基于Executor计算条目,并返回一个CompletableFuture。

1.4.  Asynchronously Loading 

 1 AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()
2 .maximumSize(10_000)
3 .expireAfterWrite(10, TimeUnit.MINUTES)
4 // Either: Build with a synchronous computation that is wrapped as asynchronous
5 .buildAsync(key -> createExpensiveGraph(key));
6 // Or: Build with a asynchronous computation that returns a future
7 .buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
8
9 // Lookup and asynchronously compute an entry if absent
10 CompletableFuture<Graph> graph = cache.get(key);
11 // Lookup and asynchronously compute entries that are absent
12 CompletableFuture<Map<Key, Graph>> graphs = cache.getAll(keys);

AsyncLoadingCache 是关联了 AsyncCacheLoader 的 AsyncCache

2.  剔除

Caffeine提供三种剔除方式:基于大小、基于时间、基于引用

2.1.  Size-based

 1 // Evict based on the number of entries in the cache
2 LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
3 .maximumSize(10_000)
4 .build(key -> createExpensiveGraph(key));
5
6 // Evict based on the number of vertices in the cache
7 LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
8 .maximumWeight(10_000)
9 .weigher((Key key, Graph graph) -> graph.vertices().size())
10 .build(key -> createExpensiveGraph(key));

如果缓存的条目数量不应该超过某个值,那么可以使用Caffeine.maximumSize(long)。如果超过这个值,则会剔除很久没有被访问过或者不经常使用的那个条目。

如果,不同的条目有不同的权重值的话,那么你可以用Caffeine.weigher(Weigher)来指定一个权重函数,并且使用Caffeine.maximumWeight(long)来设定最大的权重值。

简单的来说,要么限制缓存条目的数量,要么限制缓存条目的权重值,二者取其一。限制数量很好理解,限制权重的话首先你得提供一个函数来设定每个条目的权重值是多少,然后才能显示最大的权重是多少。

2.2.  Time-based

 1 // Evict based on a fixed expiration policy
2 LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
3 .expireAfterAccess(5, TimeUnit.MINUTES)
4 .build(key -> createExpensiveGraph(key));
5 LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
6 .expireAfterWrite(10, TimeUnit.MINUTES)
7 .build(key -> createExpensiveGraph(key));
8
9 // Evict based on a varying expiration policy
10 LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
11 .expireAfter(new Expiry<Key, Graph>() {
12 public long expireAfterCreate(Key key, Graph graph, long currentTime) {
13 // Use wall clock time, rather than nanotime, if from an external resource
14 long seconds = graph.creationDate().plusHours(5)
15 .minus(System.currentTimeMillis(), MILLIS)
16 .toEpochSecond();
17 return TimeUnit.SECONDS.toNanos(seconds);
18 }
19 public long expireAfterUpdate(Key key, Graph graph,
20 long currentTime, long currentDuration) {
21 return currentDuration;
22 }
23 public long expireAfterRead(Key key, Graph graph,
24 long currentTime, long currentDuration) {
25 return currentDuration;
26 }
27 })
28 .build(key -> createExpensiveGraph(key));
  • expireAfterAccess(long, TimeUnit): 最后一次被访问(读或者写)后多久失效
  • expireAfterWrite(long, TimeUnit): 最后一次被创建或修改后多久失效
  • expireAfter(Expiry): 创建后多久失效

建议,主动维护缓存中条目,而不是等到访问的时候发现缓存条目已经失效了才去重新加载。意思就是,提前加载,定期维护。

可以在构建的时候Caffeine.scheduler(Scheduler)来指定调度线程

2.3.  Reference-based

 1 // Evict when neither the key nor value are strongly reachable
2 LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
3 .weakKeys()
4 .weakValues()
5 .build(key -> createExpensiveGraph(key));
6
7 // Evict when the garbage collector needs to free memory
8 LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
9 .softValues()
10 .build(key -> createExpensiveGraph(key));

Caffeine.weakKeys() 使用弱引用存储key。如果没有强引用这个key,则允许垃圾回收器回收该条目。注意,这是使用==判断key的。

Caffeine.weakValues() 使用弱引用存储value。如果没有强引用这个value,则允许垃圾回收器回收该条目。注意,这是使用==判断key的。

Caffeine.softValues() 使用软引用存储value。

3.  删除

术语:

  • eviction  指受策略影响而被删除
  • invalidation  值被调用者手动删除
  • removal  值因eviction或invalidation而发生的一种行为

3.1.  明确地删除

1 // individual key
2 cache.invalidate(key)
3 // bulk keys
4 cache.invalidateAll(keys)
5 // all keys
6 cache.invalidateAll()

3.2.  监听器

1 Cache<Key, Graph> graphs = Caffeine.newBuilder()
2 .removalListener((Key key, Graph graph, RemovalCause cause) ->
3 System.out.printf("Key %s was removed (%s)%n", key, cause))
4 .build();

4.  刷新

1 LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
2 .maximumSize(10_000)
3 .refreshAfterWrite(1, TimeUnit.MINUTES)
4 .build(key -> createExpensiveGraph(key)); 

通过LoadingCache.refresh(K)进行异步刷新,通过覆盖CacheLoader.reload(K, V)可以自定义刷新逻辑

5.  统计

1 Cache<Key, Graph> graphs = Caffeine.newBuilder()
2 .maximumSize(10_000)
3 .recordStats()
4 .build(); 

使用Caffeine.recordStats(),你可以打开统计功能。Cache.stats()方法会返回一个CacheStats对象,该对象提供以下统计信息:

  • hitRate(): 命中率
  • evictionCount(): 被剔除的条目数量
  • averageLoadPenalty(): 加载新值所花费的平均时间

6.  示例

终于要说到重点了

一般来讲,用Redis作为一级话缓存,Caffeine作为二级缓存

6.1.  示例一:单独使用

pom.xml

1 <groupId>com.github.ben-manes.caffeine</groupId>
2 <artifactId>caffeine</artifactId>
3 <version>2.8.0</version>
4 </dependency>

config

 1 package com.cjs.example.config;
2
3 import com.alibaba.fastjson.JSON;
4 import com.cjs.example.model.Student;
5 import com.github.benmanes.caffeine.cache.CacheLoader;
6 import com.github.benmanes.caffeine.cache.Caffeine;
7 import com.github.benmanes.caffeine.cache.LoadingCache;
8 import com.github.benmanes.caffeine.cache.Scheduler;
9 import lombok.extern.slf4j.Slf4j;
10 import org.checkerframework.checker.nullness.qual.NonNull;
11 import org.checkerframework.checker.nullness.qual.Nullable;
12 import org.springframework.beans.factory.annotation.Autowired;
13 import org.springframework.context.annotation.Bean;
14 import org.springframework.context.annotation.Configuration;
15 import org.springframework.data.redis.core.HashOperations;
16 import org.springframework.data.redis.core.StringRedisTemplate;
17 import org.springframework.util.StringUtils;
18
19 import java.util.concurrent.TimeUnit;
20
21 /**
22 * @author ChengJianSheng
23 * @date 2019-09-15
24 */
25 @Slf4j
26 @Configuration
27 public class CacheConfig {
28
29 @Autowired
30 private StringRedisTemplate stringRedisTemplate;
31
32 @Bean("studentCache")
33 public LoadingCache<Integer, Student> studentCache() {
34 return Caffeine.newBuilder()
35 .maximumSize().recordStats()
36 .expireAfterWrite(, TimeUnit.HOURS)
37 // .scheduler(Scheduler.systemScheduler()) // 需要自定义调度器,用定时任务去主动提前刷新
38 .build(new CacheLoader<Integer, Student>() {
39 @Nullable
40 @Override
41 public Student load(@NonNull Integer key) throws Exception {
42 log.info("从缓存中加载...key={}", key);
43 HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
44 String value = hashOperations.get("STU_HS", String.valueOf(key));
45 if (StringUtils.isEmpty(value)) {
46 return null;
47 }
48 return JSON.parseObject(value, Student.class);
49 }
50 });
51 }
52
53
54 }

service

 1 package com.cjs.example.service;
2
3 import com.cjs.example.model.Student;
4 import com.github.benmanes.caffeine.cache.LoadingCache;
5 import org.springframework.stereotype.Service;
6
7 import javax.annotation.Resource;
8 import java.util.Comparator;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.stream.Collectors;
12
13 /**
14 * @author ChengJianSheng
15 * @date 2019-09-15
16 */
17 @Service
18 public class StudentService {
19
20 @Resource(name = "studentCache")
21 private LoadingCache<Integer, Student> studentCache;
22
23 public Student getById(Integer id) {
24 return studentCache.get(id);
25 }
26
27 public List<Student> getAll(List<Integer> idList) {
28 Map<Integer, Student> studentMap = studentCache.getAll(idList);
29 return studentMap.values().parallelStream().sorted(Comparator.comparing(Student::getId)).collect(Collectors.toList());
30 }
31
32 public Double hitRate() {
33 return studentCache.stats().hitRate();
34 }
35
36 /**
37 * 不直接写到本地缓存,而是先写到Redis,然后从Redis中读到本地
38 */
39 }

补充一点:你都用本地缓存了,必定已经用了一级缓存了。一级缓存无法达到预期的性能,才会选择用本地缓存。

controller

 1 package com.cjs.example.controller;
2
3 import com.cjs.example.model.Student;
4 import com.cjs.example.service.StudentService;
5 import org.springframework.beans.factory.annotation.Autowired;
6 import org.springframework.web.bind.annotation.GetMapping;
7 import org.springframework.web.bind.annotation.PathVariable;
8 import org.springframework.web.bind.annotation.RequestMapping;
9 import org.springframework.web.bind.annotation.RestController;
10
11 import java.util.Arrays;
12 import java.util.List;
13
14 /**
15 * @author ChengJianSheng
16 * @date 2019-09-15
17 */
18 @RestController
19 @RequestMapping("/student")
20 public class StudentController {
21
22 @Autowired
23 private StudentService studentService;
24
25 @GetMapping("/info/{studentId}")
26 public Student info(@PathVariable("studentId") Integer studentId) {
27 return studentService.getById(studentId);
28 }
29
30 @GetMapping("/getAll")
31 public List<Student> getAll() {
32 return studentService.getAll(Arrays.asList(, , , , ));
33 }
34
35 @GetMapping("/hitRate")
36 public Double hitRate() {
37 return studentService.hitRate();
38 }
39 }

6.2.  示例二:和SpringBoot一起用

SpringBoot支持Caffeine,可以简化一些步骤,但同时也有诸多限制

application.yml

 1 spring:
2 redis:
3 host: 127.0.0.1
4 password:
5 port:
6 cache:
7 type: caffeine
8 cache-names: teacher
9 caffeine:
10 spec: maximumSize=500,expireAfterAccess=600s

service

 1 package com.cjs.example.service;
2
3 import com.alibaba.fastjson.JSON;
4 import com.cjs.example.model.Teacher;
5 import lombok.extern.slf4j.Slf4j;
6 import org.springframework.beans.factory.annotation.Autowired;
7 import org.springframework.cache.annotation.Cacheable;
8 import org.springframework.data.redis.core.HashOperations;
9 import org.springframework.data.redis.core.StringRedisTemplate;
10 import org.springframework.stereotype.Service;
11 import org.springframework.util.StringUtils;
12
13 /**
14 * @author ChengJianSheng
15 * @date 2019-09-15
16 */
17 @Slf4j
18 @Service
19 public class TeacherService {
20
21 @Autowired
22 private StringRedisTemplate stringRedisTemplate;
23
24 @Cacheable(cacheNames = "teacher", key = "#teacherId")
25 public Teacher getById(Integer teacherId) {
26 log.info("从缓存中读取...Key={}", teacherId);
27 HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
28 String value = hashOperations.get("TEA_HS", String.valueOf(teacherId));
29 if (StringUtils.isEmpty(value)) {
30 return null;
31 }
32 return JSON.parseObject(value, Teacher.class);
33 }
34
35 }

用注解方便是方便,但是不好控制,还是自定义的好

7.  工程结构

完整的pom.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5 <parent>
6 <groupId>org.springframework.boot</groupId>
7 <artifactId>spring-boot-starter-parent</artifactId>
8 <version>2.1.8.RELEASE</version>
9 <relativePath/> <!-- lookup parent from repository -->
10 </parent>
11 <groupId>com.cjs.example</groupId>
12 <artifactId>cjs-caffeine-example</artifactId>
13 <version>0.0.1-SNAPSHOT</version>
14 <name>cjs-caffeine-example</name>
15
16 <properties>
17 <java.version>1.8</java.version>
18 </properties>
19
20 <dependencies>
21 <dependency>
22 <groupId>org.springframework.boot</groupId>
23 <artifactId>spring-boot-starter-cache</artifactId>
24 </dependency>
25 <dependency>
26 <groupId>org.springframework.boot</groupId>
27 <artifactId>spring-boot-starter-data-redis</artifactId>
28 </dependency>
29 <dependency>
30 <groupId>org.springframework.boot</groupId>
31 <artifactId>spring-boot-starter-web</artifactId>
32 </dependency>
33
34 <dependency>
35 <groupId>com.github.ben-manes.caffeine</groupId>
36 <artifactId>caffeine</artifactId>
37 <version>2.8.0</version>
38 </dependency>
39
40 <dependency>
41 <groupId>org.projectlombok</groupId>
42 <artifactId>lombok</artifactId>
43 <optional>true</optional>
44 </dependency>
45 <dependency>
46 <groupId>com.alibaba</groupId>
47 <artifactId>fastjson</artifactId>
48 <version>1.2.60</version>
49 </dependency>
50 </dependencies>
51
52 <build>
53 <plugins>
54 <plugin>
55 <groupId>org.springframework.boot</groupId>
56 <artifactId>spring-boot-maven-plugin</artifactId>
57 </plugin>
58 </plugins>
59 </build>
60
61 </project>

https://github.com/chengjiansheng/cjs-caffeine-example

8.  文档

https://github.com/ben-manes/caffeine/wiki

https://github.com/ben-manes/caffeine

https://www.itcodemonkey.com/article/9498.html

最佳内存缓存框架Caffeine的更多相关文章

  1. cache4j轻量级java内存缓存框架,实现FIFO、LRU、TwoQueues缓存模型

    简介 cache4j是一款轻量级java内存缓存框架,实现FIFO.LRU.TwoQueues缓存模型,使用非常方便. cache4j为java开发者提供一种更加轻便的内存缓存方案,杀鸡焉用EhCac ...

  2. 一文深入了解史上最强的Java堆内缓存框架Caffeine

    它提供了一个近乎最佳的命中率.从性能上秒杀其他一堆进程内缓存框架,Spring5更是为了它放弃了使用多年的GuavaCache 缓存,在我们的日常开发中用的非常多,是我们应对各种性能问题支持高并发的一 ...

  3. Java高性能本地缓存框架Caffeine

    一.序言 Caffeine是一个进程内部缓存框架,使用了Java 8最新的[StampedLock]乐观锁技术,极大提高缓存并发吞吐量,一个高性能的 Java 缓存库,被称为最快缓存. 二.缓存简介 ...

  4. Memcache内存缓存框架

    转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10923221.html 一:Memcache是什么,为什么要用它 MemCache是一个高性能.“分布式”的 ...

  5. J2Cache 和普通缓存框架有何不同,它解决了什么问题?

    不少人看到 J2Cache 第一眼时,会认为这就是一个普普通通的缓存框架,和例如 Ehcache.Caffeine .Spring Cache 之类的项目没什么区别,无非是造了一个新的轮子而已.事实上 ...

  6. iOS缓存框架-PINCache解读

    文/Amin706(简书作者)原文链接:http://www.jianshu.com/p/4df5aad0cbd4著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. 在项目中总是需要缓存一 ...

  7. Caffeine缓存 最快缓存 内存缓存

    一.序言 Caffeine是一个进程内部缓存框架. 对比Guava Cache Caffeine是在Guava Cache的基础上做一层封装,性能有明显提高,二者同属于内存级本地缓存.使用Caffei ...

  8. ASP.NET Core 6框架揭秘实例演示[16]:内存缓存与分布式缓存的使用

    .NET提供了两个独立的缓存框架,一个是针对本地内存的缓存,另一个是针对分布式存储的缓存.前者可以在不经过序列化的情况下直接将对象存储在应用程序进程的内存中,后者则需要将对象序列化成字节数组并存储到一 ...

  9. 具体解说Android图片下载框架UniversialImageLoader之内存缓存(三)

    前面的两篇文章着重介绍的是磁盘缓存,这篇文章主要是解说一下内存缓存.对于内存缓存.也打算分两篇文章来进行解说.在这一篇文章中,我们主要是关注三个类, MemoryCache.BaseMemoryCac ...

随机推荐

  1. 《机器学习基石》---VC维

    1 VC维的定义 VC维其实就是第一个break point的之前的样本容量.标准定义是:对一个假设空间,如果存在N个样本能够被假设空间中的h按所有可能的2的N次方种形式分开,则称该假设空间能够把N个 ...

  2. Gin + Vue全栈开发实战(二)

    尝试地写了第一篇自己学习Go Web框架的感受和入门的文章,发现反响还不错,大家也提出了很多的问题来一起交流.近期也渐渐地出现了很多有关go语言开发的相关文章,包括有在蚂蚁金服的大牛的分享,我也一直有 ...

  3. 编程杂谈——Platform target x64

    在Visual Studio中选择.NET Framework框架并选用任意模板创建一个普通的Web应用工程,毫无疑问,此时应该是能够正常运行此工程的. 但是将工程属性->编译->Plat ...

  4. SQL TRUNCATE TABLE 命令

    SQL TRUNCATE TABLE 命令 SQL TRUNCATE TABLE 命令用于删除现有数据表中的所有数据. 你也可以使用 DROP TABLE 命令来删除整个数据表,不过 DROP TAB ...

  5. python编辑已存在的excel坑: BadZipFile: File is not a zip file

    背景-原代码如下,期望能自动创建excel,并且可以反复调用编辑: import xlwt,osfrom openpyxl.styles import Font, colors class Write ...

  6. 消息中间件——RabbitMQ(七)高级特性全在这里!(上)

    前言 前面我们介绍了RabbitMQ的安装.各大消息中间件的对比.AMQP核心概念.管控台的使用.快速入门RabbitMQ.本章将介绍RabbitMQ的高级特性.分两篇(上/下)进行介绍. 消息如何保 ...

  7. 启xin宝app的token算法破解——逆向篇(二)

    启xin宝app的token算法破解--抓包分析篇(一)文章已经对该app进行了抓包分析,现在继续对它进行逆向. 对于一个app而言,我们要逆向app,需要知道什么呢? 逆向工具 Java基础,甚至c ...

  8. 记录一则clear重做日志文件的案例

    1.官方文档描述 2.故障报错信息 3.分析解决问题 1.官方文档描述 关于Clearing a Redo Log File的官方文档描述: A redo log file might become ...

  9. CNN卷积中多通道卷积的参数问题

    通俗来讲参数[5,5,3,16],就是用16个卷积核的每一个,分别对3通道进行对应位置,对应3通道的乘积,再加和,输出作为一个输出核的对应位置,知道16个核全部完成. 下图是一个3d的RGB效果,每个 ...

  10. c++ 按位或

    |=是位操作运算符的一种,其形式为:a|=b代表的含义为a=a|b;即把a和b做按位或(|)操作,结果赋值给a.按位或的计算规则为:1 逐位进行计算:2 计算数的同位上值,如果均为0,则结果对应位上值 ...