谷歌Guava LoadingCache介绍

简介: CacheLoader的作用就是为了在Cache中数据缺失时加载数据,其中最重要的方法就是load()方法,你可以在load() 方法中实现对应key加载数据的逻辑。在调用LoadingCache的get(key)方法时,如果key对应的value不存在,LoadingCache就会调起你在创建cache时传入的CacheLoader的load方法。

在工作中,加Cache是非常常见的一种性能优化手段,操作系统底层、计算机硬件层为了性能优化加了各种各样的Cache,当然大多数都是对应用层透明的。但如果你想在应用层加Cache的话,可能就需要你自己实现了。


 其实在Java环境下,Cache有各种各样的选择,比如最初级的你可以直接用HashMap实现一个Cache,不过你得自己关注下数据加载和淘汰的策略。更高级的有像spring-cache,代码都不需要改,只需要简单加几个注解就可以实现对关键数据的缓存,相当方便(后续我也会出一篇博客介绍下spring-cache)。 今天我们要介绍的是谷歌guava包中的LoadingCache, 也是功能完善,简单好用。


 LoadingCache是Guava包中提供一个一种本地Cache,本地Cache的优势就是没有网络IO,速度快。但劣势也很明显,Cache容量受限于本地内存大小,Cache中的数据没法共享。所以它就只适合少量热点数据的缓存,其使用方法也很简单,我们拿maven为例,你只需要添加一下Maven依赖即可引入guava包:


<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>31.1-jre</version>
</dependency>

使用代码也非常简单,如下:

private static LoadingCache<String, String> cache =
            CacheBuilder.newBuilder()
                        // 初始化容量
                        .initialCapacity(4)
                        // 缓存池大小,在缓存数量到达该大小时, Guava开始回收旧的数据
                        .maximumSize(8)
                        // 设置时间对象没有被读/写访问则对象从内存中删除(在另外的线程里面不定期维护)
                        .expireAfterAccess(5, TimeUnit.SECONDS)
                        // 设置缓存在写入之后 设定时间 后失效
                        .expireAfterWrite(5, TimeUnit.SECONDS)
                        // 数据被移除时的监听器, 缓存项被移除时会触发执行
                        .removalListener((RemovalListener<String, String>) rn -> {
                            System.out.println(String.format("数据key:%s value:%s 因为%s被移除了", rn.getKey(), rn.getValue(),
                                    rn.getCause().name()));
                        })
                        // 开启Guava Cache的统计功能
                        .recordStats()
                        // 数据写入后被多久刷新一次
                        .refreshAfterWrite(5, TimeUnit.SECONDS)
                        // 数据并发级别
                        .concurrencyLevel(16)
                        // 当缓存中没有数据时的数据加载器
                        .build(new CacheLoader<String, String>() {
                            @Override
                            public String load(String key) throws Exception {
                                return key + "_" + System.currentTimeMillis();
                            }
                        });

 然后我们就可以直接在代码的其他地方用cache.get("myKey") 来愉快地使用LoadingCache了,它会主动加载数据,并在存储空间不够或者数据过期时清理掉不需要的数据,非常省心且方便。


这里有些重点参数,下面详细介绍下:


参数 作用 注意事项

maximumSize 缓存的k-v最大数据,当总缓存的数据量达到或者快达到这个值时,就会淘汰它认为不太用的一份数据,近似LRU或者LFU策略 并不一定是达到这个值才开始淘汰旧数据,可能接近时就会开始淘汰

expireAfterAccess 数据被访问后多久就会过期,这个策略主要是为了淘汰长时间不被访问的数据 数据过期不是立即淘汰,而是有数据访问时才会触发

expireAfterWrite 数据写入后多久过期,这个策略是为了防止旧数据被缓存过久 同上

refreshAfterWrite 数据写入后多久刷新一次,这个类似于expireAfterWrite,但它会主动更新数据 同上

concurrencyLevel 数据的并发级别,LoadingCache为了实现线程安全,它里面采用了类似Java7中ConcurrentHashMap的实现,采用了分段加锁的方式,分段数影响了它的最大并发量

recordStats 开启Cache的状态统计(默认是开启的) 开启这个是会影响到性能的,如果要求极致性能的话关注下个

 我们来重点介绍下CacheLoader CacheStats和RemovalListener,因为这三者涉及到了数据的加载、使用和删除的完整生命周期,先来看下CacheLoader。


CacheLoader

 CacheLoader的作用就是为了在Cache中数据缺失时加载数据,其中最重要的方法就是load()方法,你可以在load() 方法中实现对应key加载数据的逻辑。在调用LoadingCache的get(key)方法时,如果key对应的value不存在,LoadingCache就会调起你在创建cache时传入的CacheLoader的load方法。


CacheStats

image.png


 使用CacheStats cacheStats = cache.stats(); 我就可以获取到cache的stats数据。从cacheStats中我们可以看到cache的命中率、命中数、异常率、加载时延……等数据,通过这些数据就可以直观地看出我们cache的一些性能指标,如果做出一些参数调整。 比如如果命中率过低,我们是不是可以调整大下maximumSize,或者调整下数据的过期策略?


RemovalListener

 RemovalListener会在LoadingCache中数据被清理时调起,其实就是个监听器模式,这样你可以通过Listener实现对数据淘汰事件的监听,比如在数据淘汰时打一行日志啥的。使用方法也很简单,在Java8+上你可以直接使用lambda表达式,或者也可以自己实现RemovalListener接口,并在构建Cache时注册进去即可。

public enum RemovalCause {
  EXPLICIT {
    @Override
    boolean wasEvicted() {
      return false;
    }
  },
  REPLACED {
    @Override
    boolean wasEvicted() {
      return false;
    }
  },
  COLLECTED {
    @Override
    boolean wasEvicted() {
      return true;
    }
  },
  EXPIRED {
    @Override
    boolean wasEvicted() {
      return true;
    }
  },
  SIZE {
    @Override
    boolean wasEvicted() {
      return true;
    }
  };
  abstract boolean wasEvicted();
}

 在RemovalListener内,我们可以通过RemovalListener获取到被删除的数据的key和value,也可以知晓数据被删除的原因。可以看到有个RemovalCause枚举类,详细说明了几种数据被清除的原因,比如被用户主动删除(RemovalCause.EXPLICIT),被替换(RemovalCause.REPLACED),过期淘汰(RemovalCause.EXPIRED),被GC收集器删除(RemovalCause.COLLECTED),容量不够导致的删除(RemovalCause.SIZE)。


  关于LoadingCache的介绍就到这了。再说下谷歌的guava包,其实guava是一个很好用的Java开源开发包,里面除了cache之外,还有各种集合工具、并发工具,Cache只是其中很小的一部分,后续有机会我们在详细探索下guava。今天的文章就到这了,大家觉得有用请点赞,喜欢请关注。

目录
相关文章
|
10月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
856 0
|
9月前
|
Arthas 数据可视化 Java
深入理解JVM《火焰图:性能分析的终极可视化利器》
火焰图是Brendan Gregg发明的性能分析利器,将复杂调用栈可视化为“火焰”状图形,直观展示函数耗时与调用关系。通过宽度识别热点函数,结合async-profiler或Arthas工具生成,助力快速定位CPU、内存等性能瓶颈,提升优化效率。
|
人工智能 IDE 测试技术
一文教会你如何用好通义灵码,让这款 AI 编码工具帮你做更多工作,更高效
如何用好通义灵码?欢迎收藏最佳使用指南。本文提供通义灵码使用指南,涵盖快捷键、配置调整、跨文件索引及上下文管理等内容,帮助用户更高效地使用通义灵码。
11945 3
|
Arthas 监控 Java
Arthas profiler(使用async-profiler对应用采样,生成火焰图)
Arthas profiler(使用async-profiler对应用采样,生成火焰图)
2150 10
|
SQL 关系型数据库 MySQL
MySQL 中exists与in及any的用法详解
MySQL 中exists与in及any的用法详解
595 3
|
存储 监控 Java
一文看懂分布式链路监控系统
本文通过阿里的Eagleeye(鹰眼)和开源的Skywalking,从数据模型、数据埋点以及数据存储三个方面介绍分布式链路监控系统的实现细节,其中将重点介绍Skywalking字节码增强的实现方案。
92383 6
|
安全 Java 测试技术
谈谈springboot的单例模式
【4月更文挑战第13天】在 Spring Boot 和更广泛的 Spring 框架中,单例模式扮演着核心的角色,特别是在 Spring 的 Bean 生命周期和管理中。这里我们详细探讨一下 Spring Boot 中单例模式的运作原理、优势及其潜在问题。
766 7
|
XML JSON 前端开发
深入理解 Spring MVC Controller —— 请求映射
前言 基于注解的 Spring MVC 的项目中,Controller 应该是我们接触最多的类了,这里提到的 Controller 并非是某一个具体的接口或类,而是一种概念,只要我们我们定义的类中包含了处理请求的方法,这个类就可以称为 Controller,而处理请求的方法被称为处理器方法。
1933 0
深入理解 Spring MVC Controller —— 请求映射
|
NoSQL 关系型数据库 MySQL
Redis入门到通关之Redis实现Session共享
Redis入门到通关之Redis实现Session共享
714 0
|
小程序 JavaScript 开发者
微信小程序——事件监听
微信小程序——事件监听
830 0