经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring Boot » 查看文章
地表最帅缓存Caffeine
来源:cnblogs  作者:敬YES  时间:2023/9/6 10:56:50  对本文有异议

简介

缓存是程序员们绕不开的话题,像是常用的本地缓存Guava,分布式缓存Redis等,是提供高性能服务的基础。今天敬姐带大家一起认识一个更高效的本地缓存——Caffeine

img

Caffeine Cache使用了基于内存的存储策略,并且支持高并发、低延迟,同时还提供了缓存过期、定时刷新、缓存大小控制等功能。Caffeine是一个Java高性能的本地缓存库。据其官方说明,因使用 Window TinyLfu 回收策略,其缓存命中率已经接近最优值。此处应有掌声????

它是Guava Cache的升级版本, 但是比Guava Cache更快,更稳定。Caffeine Cache最适合做数据量不大,但是读写频繁的应用场景。结合Redis等可以实现应用中的多级缓存策略。
!

还是老套路,先写代码示例,对Caffeine的帅有个肤浅的认识,后面再去研究他的内在机制

Caffeine缓存类型

新建项目,添加caffeine的maven引用

  1. <!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
  2. <dependency>
  3. <groupId>com.github.ben-manes.caffeine</groupId>
  4. <artifactId>caffeine</artifactId>
  5. <version>3.1.7</version>
  6. </dependency>

Caffeine四种缓存类型:Cache, LoadingCache, AsyncCache, AsyncLoadingCache

1. Cache

在获取缓存值时,如果想要在缓存值不存在时,原子地将值写入缓存,则可以调用get(key, k -> value)方法,该方法将避免写入竞争。
在多线程情况下,当使用get(key, k -> value)时,如果有另一个线程同时调用本方法进行竞争,则后一线程会被阻塞,直到前一线程更新缓存完成;而若另一线程调用getIfPresent()方法,则会立即返回null,不会被阻塞。

  1. public class CacheDemo {
  2. static Cache<Integer, Article> cache = Caffeine.newBuilder()
  3. .maximumSize(1000)
  4. .expireAfterWrite(10, TimeUnit.MINUTES)
  5. .build();
  6. public static void main(String[] args) {
  7. //get
  8. System.out.println(cache.get(1, x -> new Article(x)));//Article{id=1, title='title 1'}
  9. //getIfPresent
  10. System.out.println(cache.getIfPresent(2));//null
  11. //put 设置缓存
  12. cache.put(2, new Article(2));
  13. System.out.println(cache.getIfPresent(2));//Article{id=2, title='title 2'}
  14. //invalidate 移除缓存
  15. cache.invalidate(2);
  16. System.out.println(cache.getIfPresent(2));//null
  17. }
  18. }

2.LoadingCache

LoadingCache是一种自动加载的缓存。使用时需要指定CacheLoader,并实现其中的load()方法供缓存缺失时自动加载。
get()方法用来读取缓存,和上面的Cache方式不同之处在于,当缓存不存在或者已经过期时,会自动调用CacheLoader.load()方法加载最新值。加载过程是一种同步操作,将返回值插入缓存并且返回。

  1. /**
  2. * LoadingCache示例,自动加载的缓存
  3. *
  4. * @author chenjing
  5. */
  6. public class LoadingCacheDemo {
  7. private static LoadingCache<Integer, Article> cache = Caffeine.newBuilder()
  8. .build(new CacheLoader<>() {
  9. @Override
  10. public @Nullable Article load(Integer id) {
  11. return new Article(id);
  12. }
  13. });
  14. public static void main(String[] args) {
  15. System.out.println(cache.get(1));//Article{id=1, title='title 1'}
  16. //getIfPresent
  17. System.out.println(cache.getIfPresent(2));//null
  18. System.out.println(cache.getAll(List.of(10,20)));//{10=Article{id=10, title='title 10'}, 20=Article{id=20, title='title 20'}}
  19. }
  20. }

3.AsyncCache

和Cache类似,但是异步执行操作,并返回保存实际值的CompletableFuture,适用于需要进行并发执行提高吞吐量的场景。

  1. /**
  2. * Caffeine 异步缓存
  3. *
  4. * @author chenjing
  5. */
  6. public class AsyncCacheDemo {
  7. static AsyncCache<Integer, Article> cache = Caffeine.newBuilder()
  8. .maximumSize(1000)
  9. .expireAfterWrite(10, TimeUnit.MINUTES)
  10. .buildAsync();
  11. public static void main(String[] args) throws ExecutionException, InterruptedException {
  12. //get 返回的是CompletableFuture
  13. CompletableFuture<Article> future = cache.get(1, x -> new Article(x));
  14. System.out.println(future.get());//Article{id=1, title='title 1'}
  15. }
  16. }

4.AsyncLoadingCache

异步加载缓存

  1. /**
  2. * Caffeine AsyncLoadingCache 异步自动加载缓存
  3. * @author chenjing
  4. */
  5. public class AsyncLoadingCacheDemo {
  6. private static AsyncLoadingCache<Integer, Article> asyncLoadingCache =
  7. Caffeine.newBuilder()
  8. .maximumSize(1000)
  9. .expireAfterWrite(10, TimeUnit.MINUTES)
  10. .buildAsync(
  11. (key, executor) -> CompletableFuture.supplyAsync(() -> new Article(key), executor)
  12. );
  13. public static void main(String[] args) {
  14. CompletableFuture<Article> userCompletableFuture = asyncLoadingCache.get(66);
  15. System.out.println(userCompletableFuture.join());//Article{id=66, title='title 66'}
  16. }
  17. }

缓存过期

  1. 基于缓存大小
    通过 maximumSize() 指定缓存大小。
  1. /**
  2. * 测试Caffeine基于空间的驱逐策略
  3. *
  4. * @author chenjing
  5. */
  6. public class MaxSizeExpire {
  7. public static void main(String[] args) {
  8. System.out.println("测试基于容量过期的缓存");
  9. Integer size = 10;
  10. Cache<Integer, Article> cache = Caffeine.newBuilder()
  11. .maximumSize(size)
  12. .recordStats()
  13. .build();
  14. cache.put(1, new Article(1));
  15. System.out.println("放入一条数据,获取看看");
  16. System.out.println(cache.getIfPresent(1));//Article{id=1, title='title 1'}
  17. System.out.println("放入" + size + "条数据");
  18. for (int i = 2; i <= size + 5; i++) {
  19. cache.put(i, new Article(i));
  20. }
  21. System.out.println("再打印第一条数据看看");
  22. System.out.println(cache.getIfPresent(1));//Article{id=1, title='title 1'}
  23. //打印一下缓存状态
  24. System.out.println(cache.stats());//CacheStats{hitCount=2, missCount=0, loadSuccessCount=0, loadFailureCount=0, totalLoadTime=0, evictionCount=5, evictionWeight=5}
  25. }
  26. }
  1. 基于缓存写入时间
    一般最近一段时间缓存的数据才是有效的,缓存很久之前的业务数据是没有意义的。常见的一种场景就是,缓存写入之后N分钟之后自动失效。
  1. /**
  2. * Caffeine expireAfterWrite
  3. * @author chenjing
  4. */
  5. public class ExpireAfterWriteDemo {
  6. public static void main(String[] args) throws InterruptedException {
  7. System.out.println("测试基于时间过期的缓存");
  8. Integer seconds = 5;
  9. Integer size = 5;
  10. Cache<Integer, Article> cache = Caffeine.newBuilder()
  11. //基于时间过期
  12. .expireAfterWrite(seconds, TimeUnit.SECONDS)
  13. //监控缓存移除
  14. .removalListener((Integer key, Article value, RemovalCause cause) ->
  15. System.out.printf("移除key %s,原因是 %s %n", key, cause))
  16. .build();
  17. System.out.println("放入" + size + "条数据");
  18. for (int i = 1; i <= size; i++) {
  19. cache.put(i, new Article(i));
  20. System.out.println(cache.getIfPresent(i));
  21. }
  22. System.out.println("sleep 10 seconds");
  23. Thread.sleep(10000);
  24. for (int i = 1; i <= size; i++) {
  25. cache.put(i, new Article(i));
  26. System.out.println(cache.getIfPresent(i));
  27. }
  28. }
  29. }

执行结果:

  1. 测试基于时间过期的缓存
  2. 放入5条数据
  3. Article{id=1, title='title 1'}
  4. Article{id=2, title='title 2'}
  5. Article{id=3, title='title 3'}
  6. Article{id=4, title='title 4'}
  7. Article{id=5, title='title 5'}
  8. sleep 10 seconds
  9. 移除key 1,原因是 EXPIRED
  10. Article{id=1, title='title 1'}
  11. Article{id=2, title='title 2'}
  12. 移除key 2,原因是 EXPIRED
  13. 移除key 3,原因是 EXPIRED
  14. Article{id=3, title='title 3'}
  15. 移除key 4,原因是 EXPIRED
  16. 移除key 5,原因是 EXPIRED
  17. Article{id=4, title='title 4'}
  18. Article{id=5, title='title 5'}

缓存刷新

refreshAfterWrite()和expireAfterWrite()不同,在刷新的时候如果查询缓存元素,其旧值将仍被返回,直到该元素的刷新完毕后结束后才会返回刷新后的新值。这里的刷新操作默认也是由线程池ForkJoinPool.commonPool()异步执行的,我们也可以通过Caffeine.executor()重写来指定自定义的线程池。

  1. public class RefreshAfterWriteDemo {
  2. public static void main(String[] args) throws InterruptedException {
  3. LoadingCache<Integer, Article> cache = Caffeine.newBuilder()
  4. .refreshAfterWrite(2, TimeUnit.SECONDS)
  5. .build(new CacheLoader<Integer, Article>() {
  6. @Override
  7. public @Nullable Article load(Integer integer) {
  8. return new Article(integer);
  9. }
  10. @Override
  11. public @Nullable Article reload(Integer key, Article oldValue) {
  12. return new Article(key + 100);
  13. }
  14. });
  15. cache.put(1, new Article(1));
  16. for (int i = 0; i < 3; i++) {
  17. System.out.println(cache.get(1));
  18. Thread.sleep(3000);
  19. }
  20. }
  21. }

缓存监控

创建Caffeine时设置removalListener,可以监听缓存地清除或更新监听。

  1. Cache<Integer, Article> cache = Caffeine.newBuilder()
  2. //基于时间过期
  3. .expireAfterWrite(seconds, TimeUnit.SECONDS)
  4. //监控缓存移除
  5. .removalListener((Integer key, Article value, RemovalCause cause) ->
  6. System.out.printf("移除key %s,原因是 %s %n", key, cause))
  7. .build();

当缓存中的数据发送更新,或者被清除时,就会触发监听器,在监听器里可以自定义一些处理手段,比如打印出哪个数据被清除,原因是什么。这个触发和监听的过程是异步的,就是有可能数据都被删除一小会儿了,监听器才监听到。


本人公众号[ 敬YES ]同步更新,欢迎大家关注~

img

原文链接:https://www.cnblogs.com/janes/p/17678468.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号