缓存Caffine

缓存Caffine

  • Caffine介绍
  • 添加
    • 手动加载
    • 自动加载
    • 异步加载
  • 驱逐
    • 基于容量
    • 基于时间
    • 基于引用
  • 移除
    • 显式移除
    • 移除监听器
  • 刷新
  • 计算
  • Interner
  • 规范

Caffine介绍


Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能的缓存库。

缓存和ConcurrentMap有点相似,但还是有所区别。最根本的区别是ConcurrentMap将会持有所有加入到缓存当中的元素,直到它们被从缓存当中手动移除。但是,Caffeine的缓存Cache 通常会被配置成自动驱逐缓存中元素,以限制其内存占用。在某些场景下,LoadingCache和AsyncLoadingCache 因为其自动加载缓存的能力将会变得非常实用。

Caffeine提供了灵活的构造器去创建一个拥有下列特性的缓存:

  • 自动加载元素到缓存当中,异步加载的方式也可供选择
  • 当达到最大容量的时候可以使用基于就近度和频率的算法进行基于容量的驱逐
  • 将根据缓存中的元素上一次访问或者被修改的时间进行基于过期时间的驱逐
  • 当向缓存中一个已经过时的元素进行访问的时候将会进行异步刷新
  • key将自动被弱引用所封装
  • value将自动被弱引用或者软引用所封装
  • 驱逐(或移除)缓存中的元素时将会进行通知
  • 写入传播到一个外部数据源当中
  • 持续计算缓存的访问统计指标

添加

Caffeine提供了四种缓存添加策略:手动加载,自动加载,手动异步加载和自动异步加载。

手动加载

@Testpublic void shoudong() {Cache<Object, Object> cache = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.SECONDS).maximumSize(10_000).build();String key = "1";//查找一个缓存元素,没有查找到的时候返回nullObject ifPresent = cache.getIfPresent(key);System.out.println(ifPresent); //null//查询缓存,如果缓存不存在则进行计算并缓存(建议使用)Object o = cache.get(key, k -> k + "aaa");System.out.println(o);//1aaaObject ifPresent1 = cache.getIfPresent(key);System.out.println(ifPresent1);//1aaa//缓存cache.put("2","b");Object ifPresent2 = cache.getIfPresent("2");System.out.println("ifPresent2 = " + ifPresent2);//ifPresent2 = b//移除一个缓存元素cache.invalidate("2");System.out.println(cache.getIfPresent("2"));//null}

推荐使用 cache.get(key, k -> value) 操作来在缓存中不存在该key对应的缓存元素的时候进行计算生成并直接写入至缓存内,而当该key对应的缓存元素存在的时候将会直接返回存在的缓存值。

自动加载

相当于在build中添加查询缓存不存在时的备选方案;

@Testpublic void zidong() {LoadingCache<Object, Object> cache = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.SECONDS).maximumSize(10_000).build(key -> key + "bbb");//当不存在时构建并缓存String key = "11";System.out.println(cache.get(key));//11bbbMap<Object, Object> all = cache.getAll(List.of("11"));System.out.println(all);//{11=11bbb}}

注意此处build返回的是LoadingCache,LoadingCahce需要在build中传入CacheLoader。
通过 getAll可以达到批量查找缓存的目的。 默认情况下,在getAll 方法中,将会对每个不存在对应缓存的key调用一次 CacheLoader.load 来生成缓存元素。 在批量检索比单个查找更有效率的场景下,你可以覆盖并开发CacheLoader.loadAll 方法来使你的缓存更有效率。
值得注意的是,你可以通过实现一个 CacheLoader.loadAll并在其中为没有在参数中请求的key也生成对应的缓存元素。打个比方,如果对应某个key生成的缓存元素与包含这个key的一组集合剩余的key所对应的元素一致,那么在loadAll中也可以同时加载剩下的key对应的元素到缓存当中。

异步加载

https://github.com/ben-manes/caffeine/wiki/Population-zh-CN

驱逐

Caffeine 提供了三种驱逐策略,分别是基于容量,基于时间和基于引用三种类型。

基于容量

public void rongliang(){// 基于缓存内的元素个数进行驱逐LoadingCache<Object, String> rongliang = Caffeine.newBuilder().maximumSize(10_000).build(key -> key + "aa");// 基于缓存内元素权重进行驱逐LoadingCache<Object, String> quanzhong = Caffeine.newBuilder().maximumWeight(10_000).weigher(Objects::hash).build(key -> key + "aa");}

如果你的缓存容量不希望超过某个特定的大小,那么记得使用Caffeine.maximumSize(long)。缓存将会尝试通过基于就近度和频率的算法来驱逐掉不会再被使用到的元素。
另一种情况,你的缓存可能中的元素可能存在不同的“权重”–打个比方,你的缓存中的元素可能有不同的内存占用–你也许需要借助Caffeine.weigher(Weigher) 方法来界定每个元素的权重并通过 Caffeine.maximumWeight(long)方法来界定缓存中元素的总权重来实现上述的场景。除了“最大容量”所需要的注意事项,在基于权重驱逐的策略下,一个缓存元素的权重计算是在其创建和更新时,此后其权重值都是静态存在的,在两个元素之间进行权重的比较的时候,并不会根据进行相对权重的比较。

基于时间

// 基于固定的过期时间驱逐策略
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build(key -> createExpensiveGraph(key));
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(key -> createExpensiveGraph(key));// 基于不同的过期驱逐策略
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().expireAfter(new Expiry<Key, Graph>() {public long expireAfterCreate(Key key, Graph graph, long currentTime) {// Use wall clock time, rather than nanotime, if from an external resourcelong seconds = graph.creationDate().plusHours(5).minus(System.currentTimeMillis(), MILLIS).toEpochSecond();return TimeUnit.SECONDS.toNanos(seconds);}public long expireAfterUpdate(Key key, Graph graph, long currentTime, long currentDuration) {return currentDuration;}public long expireAfterRead(Key key, Graph graph,long currentTime, long currentDuration) {return currentDuration;}}).build(key -> createExpensiveGraph(key));

Caffeine提供了三种方法进行基于时间的驱逐:

  • expireAfterAccess(long, TimeUnit): 一个元素在上一次读写操作后一段时间之后,在指定的时间后没有被再次访问将会被认定为过期项。在当被缓存的元素时被绑定在一个session上时,当session因为不活跃而使元素过期的情况下,这是理想的选择。(一般用于缓存)
  • expireAfterWrite(long, TimeUnit): 一个元素将会在其创建或者最近一次被更新之后的一段时间后被认定为过期项。在对被缓存的元素的时效性存在要求的场景下,这是理想的选择。(一般用于监控状态,对于时效性比较高)
  • expireAfter(Expiry): 一个元素将会在指定的时间后被认定为过期项。当被缓存的元素过期时间收到外部资源影响的时候(用到实体类内部时间属性),这是理想的选择。

在写操作,和偶尔的读操作中将会进行周期性的过期事件的执行。过期事件的调度和触发将会在O(1)的时间复杂度内完成。(默认情况下过期时间需要读写操作,如果没有读写操作,过期时间不会执行)
为了使过期更有效率,可以通过在你的Cache构造器中通过Scheduler接口和Caffeine.scheduler(Scheduler) 方法去指定一个调度线程代替在缓存活动中去对过期事件进行调度。使用Java 9以上版本的用户可以选择Scheduler.systemScheduler()利用系统范围内的调度线程。

基于引用

// 当key和缓存元素都不再存在其他强引用的时候驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().weakKeys().weakValues().build(key -> createExpensiveGraph(key));// 当进行GC的时候进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().softValues().build(key -> createExpensiveGraph(key));

Caffeine 允许你配置你的缓存去让GC去帮助清理缓存当中的元素,其中key支持弱引用,而value则支持弱引用和软引用。记住 AsyncCache不支持软引用和弱引用。
Caffeine.weakKeys() 在保存key的时候将会进行弱引用。这允许在GC的过程中,当key没有被任何强引用指向的时候去将缓存元素回收。由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等(= =)而不是对象相等 equals()去进行key之间的比较。
Caffeine.weakValues()在保存value的时候将会使用弱引用。这允许在GC的过程中,当value没有被任何强引用指向的时候去将缓存元素回收。由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等()而不是对象相等 equals()去进行value之间的比较。
Caffeine.softValues()在保存value的时候将会使用软引用。为了相应内存的需要,在GC过程中被软引用的对象将会被通过LRU算法回收。由于使用软引用可能会影响整体性能,我们还是建议通过使用基于缓存容量的驱逐策略代替软引用的使用。同样的,使用 softValues() 将会通过引用相等(
)而不是对象相等 equals()去进行value之间的比较。

移除

术语:

  • 驱逐 缓存元素因为策略被移除
  • 失效 缓存元素被手动移除
  • 移除 由于驱逐或者失效而最终导致的结果

显式移除

在任何时候,你都可以手动去让某个缓存元素失效而不是只能等待其因为策略而被驱逐。

// 失效key
cache.invalidate(key)
// 批量失效key
cache.invalidateAll(keys)
// 失效所有的key
cache.invalidateAll()

移除监听器

@Testpublic void removeListener() throws InterruptedException, IOException {Cache<Object, Object> cache = Caffeine.newBuilder()
//                .maximumSize(10_000).expireAfterAccess(5, TimeUnit.SECONDS).scheduler(Scheduler.systemScheduler())//定义执行移除任务的Scheduler.removalListener((k, v, cause) -> {System.out.println("k = " + k);System.out.println("v = " + v);System.out.println("cause.wasEvicted() = " + cause.wasEvicted());}).evictionListener((k, v, cause) -> {System.out.println("eviction k = " + k);System.out.println("eviction v = " + v);System.out.println("eviction cause.wasEvicted() = " + cause.wasEvicted());}).build();cache.put("1","a");cache.put("2","b");cache.put("3","b");cache.invalidate("1");TimeUnit.SECONDS.sleep(10);System.out.println("cache.getIfPresent(\"2\") = " + cache.getIfPresent("2"));//只有获取值的时候才进行过期操作}
k = 1
v = a
cause.wasEvicted() = false
cache.getIfPresent("2") = null
eviction k = 2
eviction v = b
eviction cause.wasEvicted() = true
eviction k = 3
eviction v = b
eviction cause.wasEvicted() = true
k = 3
v = b
cause.wasEvicted() = true
k = 2
v = b
cause.wasEvicted() = true

你可以为你的缓存通过Caffeine.removalListener(RemovalListener)方法定义一个移除监听器在一个元素被移除(失效和驱逐)的时候进行相应的操作。这些操作是使用 Executor 异步执行的,其中默认的 Executor 实现是ForkJoinPool.commonPool() 并且可以通过覆盖Caffeine.executor(Executor)方法自定义线程池的实现。
当元素被驱逐时(根据各种策略) ,你需要使用 Caffeine.evictionListener(RemovalListener) 。这个监听器将在 RemovalCause.wasEvicted() 为 true(代表被驱逐) 的时候被触发。为了移除操作能够明确生效, Cache.asMap() 提供了方法来执行原子操作。

刷新

一般用于一段时间把数据库中的数据刷新到缓存中,保证缓存和数据库的数据一致性。

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

刷新和驱逐并不相同。可以通过LoadingCache.refresh(K)方法,异步为key对应的缓存元素刷新一个新的值。与驱逐不同的是,在刷新的时候如果查询缓存元素,其旧值将仍被返回,直到该元素的刷新完毕后结束后才会返回刷新后的新值。
expireAfterWrite相反,refreshAfterWrite 将会使在写操作之后的一段时间后允许key对应的缓存元素进行刷新,但是只有在这个key被真正查询到的时候才会正式进行刷新操作。所以打个比方,你可以在同一个缓存中同时用到 refreshAfterWrite和expireAfterWrite ,这样缓存元素的在被允许刷新的时候不会直接刷新使得过期时间被盲目重置。当一个元素在其被允许刷新但是没有被主动查询的时候,这个元素也会被视为过期。
一个CacheLoader可以通过覆盖重写 CacheLoader.reload(K, V) 方法使得在刷新中可以将旧值也参与到更新的过程中去,这也使得刷新操作显得更加智能。

计算

在复杂的工作流中,当外部资源对key的操作变更顺序有要求的时候,Caffeine 提供了实现的扩展点。
https://github.com/ben-manes/caffeine/wiki/Compute-zh-CN

Interner

Interner<String> interner = Interner.newStrongInterner();
String s1 = interner.intern(new String("value"));
String s2 = interner.intern(new String("value"));
assert s1 == s2

一个 Interner 将会返回给定元素的标准规范形式。当调用 intern(e) 方法时,如果该 interner 已经包含一个由 Object.equals 方法确认相等的对象的时候,将会返回其中已经包含的对象。否则将会新添加该对象返回。这种重复数据删除通常用于共享规范实例来减少内存使用。 主要用于删除重复数据。

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().weakKeys().build(key -> createExpensiveGraph(key));
Interner<Key> interner = Interner.newWeakInterner();Key canonical = interner.intern(key);
Graph graph = graphs.get(canonical);

一个弱引用的 interner 允许其中的内部对象在没有别的强引用的前提下在被 gc 回收。与 caffeine 的 Caffeine.weakKeys() 不同的是, 这里通过 (==) 来进行比较而不是 equals()。这样可以确保应用程序所引用的 interner 中的值正是缓存中的实际实例。否则在缓存当中的所 持有的弱引用 key 很有可能是与应用程序所持有的实例不同的实例,这会导致这个 key 会在 gc 的时候被提早从缓存当中被淘汰。

规范

springboot中有用到

CaffeineSpec spec = CaffeineSpec.parse("maximumWeight=1000, expireAfterWrite=10m, recordStats");
LoadingCache<Key, Graph> graphs = Caffeine.from(spec).weigher((Key key, Graph graph) -> graph.vertices().size()).build(key -> createExpensiveGraph(key));

CaffeineSpec为Caffeine提供了一个简单的字符格式配置。这里的字符串语法是一系列由逗号隔开的键值对组成,其中每个键值对对应一个配置方法。但是这里的字符配置不支持需要对象来作为参数的配置方法,比如 removalListener,这样的配置必须要在代码中进行配置。
以下是各个配置键值对字符串所对应的配置方法。将maximumSize和maximumWeight或者将weakValues和weakValues 在一起使用是不被允许的。

  • initialCapacity=[integer]: 相当于配置 Caffeine.initialCapacity
  • maximumSize=[long]: 相当于配置 Caffeine.maximumSize
  • maximumWeight=[long]: 相当于配置 Caffeine.maximumWeight
  • expireAfterAccess=[持续时间]: 相当于配置 Caffeine.expireAfterAccess
  • expireAfterWrite=[持续时间]: 相当于配置 Caffeine.expireAfterWrite
  • refreshAfterWrite=[持续时间]: 相当于配置 Caffeine.refreshAfterWrite
  • weakKeys: 相当于配置 Caffeine.weakKeys
  • weakValues: 相当于配置 Caffeine.weakValues
  • softValues: 相当于配置 Caffeine.softValues
  • recordStats: 相当于配置 Caffeine.recordStats

持续时间可以通过在一个integer类型之后跟上一个"d",“h”,“m”,或者"s"来分别表示天,小时,分钟或者秒。另外,ISO-8601标准的字符串也被支持来配置持续时间,并通过Duration.parse来进行解析。出于表示缓存持续时间的目的,这里不支持配置负的持续时间,并将会抛出异常。两种持续时间表示格式的示例如下所示。

普通ISO-8601描述
50sPT50S50秒
11mPT11M11分钟
6hPT6H6小时
3dP3D3天
P3DT3H4M3天3小时4分钟
-PT7H3M-7小时,-3分钟(不支持)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/769131.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql 用户管理-账户管理

学习了《mysql 用户管理-权限表》。接着学习更常用的的账户管理。 2&#xff0c;账户管理 MySQL提供许多语句用来管理用户账号,这些语句可以用来管理包括登录和退出MySQL服务器、创建用户、删除用户、密码管理和权限管理等内容。MySQL 数据库的安全性&#xff0c;需要通过账户管…

注册马来西亚商标常见问题

马来西亚商标法于1983年9月1日正式生效。这部商标法废除了马来亚、沙巴和沙捞越三地区各自的商标法规和申请程序&#xff0c;使马来西亚有了一部统一商标法。此外&#xff0c;马来西亚有关商标的法规包括1983年9月1日同时生效的《1983年商标法实施细则》。在马来西亚&#xff0…

Xcode-双架构arm64 x86_64编译

要启用通用构建&#xff0c;在最新版本的 Xcode 中&#xff0c;请打开您的项目设置&#xff0c;然后依次选择&#xff1a; 1. “Build Settings” 选项卡。 2. 在顶部输入框中输入 “Architectures”。 3. 在 “Architectures” 下拉列表中选择 “Other”。 4. 在输入框中输入 …

国内git最新版本下载链接2.44

git官网地址:Git - Downloading Package (git-scm.com) 蓝奏云: ​​​​​​gGit-2.44.0-64-bit.exe - 蓝奏云 git仓库地址:git/git: Git Source Code Mirror - This is a publish-only repository but pull requests can be turned into patches to the mailing list via …

有哪些方式适合保存token

保存token的最佳方式取决于具体的应用场景和需求。以下是几种常见的保存token的方式及其优缺点&#xff0c;以便您根据实际情况进行选择&#xff1a; HTTP Only Cookies&#xff1a; 优点&#xff1a;Cookies可以自动随HTTP请求发送&#xff0c;且HTTP Only属性可以防止JavaS…

2024常用接口抓包以及接口测试工具总结【建议收藏】

接口 统称为API&#xff0c;程序与程序之间的对接、交接。 接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点&#xff0c;主要是为了检验不同组件&#xff08;模块&#xff09;之间数据的传递是否正确&#xff0c;同时接口测试还要测试当前系统与第三方…

贪心算法的魅力与应用

在算法的世界里&#xff0c;贪心算法&#xff08;Greedy Algorithm&#xff09;以其简洁而高效的策略吸引着我们的目光。本文将深入探讨贪心算法的原理、特点以及它在实际问题中的广泛应用。 一、什么是贪心算法&#xff1f; 贪心算法是一种在每一步选择中都采取当前看起来最优…

zoom 在 css中的用法

zoom怎么用&#xff1f;&#xff08; 缩放和清除浮动&#xff09; 1、用css中的zoom属性可以让网页实现IE7中的放大缩小功能。 比如你想让你的网页缩小为原来的一半&#xff0c;那么就在body中加入style"zoom:0.5"&#xff0c; eg: zoom:120%, zoom:1 &#xff0…

谈谈我对 AIGC 趋势下软件工程重塑的理解

作者&#xff1a;陈鑫 今天给大家带来的话题是 AIGC 趋势下的软件工程重塑。今天这个话题主要分为以下四大部分。 第一部分是 AI 是否已经成为软件研发的必选项&#xff1b;第二部分是 AI 对于软件研发的挑战及智能化机会&#xff0c;第三部分是企业落地软件研发智能化的策略…

MySQL的事务深入理解和存储系统

目录 一、事务的基本理论 1.事务的隔离 1.1事务之间的相互影响 1.2事物隔离级别 2.查询和设置事物隔离级别 2.1查询全局事务隔离级别 2.2查询会话事物隔离级别 2.3设置全局事务隔离级别 2.4设置会话事务隔离级别 ​编辑3.事务控制语句 ​编辑3.1提交事务 ​编辑3.2…

服务器相关问题以及见解

1、服务器组成主要包含哪些主要部件&#xff1f; 中央处理器&#xff08;CPU&#xff09;&#xff1a;服务器的大脑&#xff0c;负责执行计算任务和处理数据。内存&#xff08;RAM&#xff09;&#xff1a;临时存储设备&#xff0c;用于存放正在使用或即将使用的数据&#xff…

Python问题异常处理与日志结合

我们掌握了try-except来处理程序运行中可能遇到的异常&#xff0c;以及使用logging来记录程序运行日志&#xff0c;该篇文章就结合二者来讲讲如何记录程序运行过程中的各种事件、状态信息以及遇到的异常情况&#xff0c;以便于追踪、诊断和解决程序运行时的问题。 目录 一、配…

H3C技术大全复现之高级路由交换技术 1

华子目录 VLAN 基本技术VLANIEEE 802.1Q交换机端口类型MVRP协议实验测试 VLAN扩展技术Super VLAN产生背景Super vlan&#xff08;相当于vlanif接口&#xff0c;也属于虚拟接口&#xff0c;可以充当网关&#xff09;Sub vlan&#xff08;普通vlan&#xff09;关于代理ARP普通代理…

【C语言】编译链接

1、宏&#xff08;***&#xff09; 1.1#define定义宏 #define 机制包括了一个规定&#xff0c;允许把参数替换到文本中&#xff0c;这种实现通常称为宏&#xff08;macro&#xff09;或定义 宏&#xff08;define macro&#xff09;。 注意&#xff1a;用于对数值表达式进行求…

flutter 单例模式

总的思想就是: 确保整个应用程序中只有一个 TranslationService 实例。 避免重复创建相同的实例,节省资源。 为整个应用程序提供一个全局访问点,方便在不同地方使用同一个实例。 1.类创建个实例 2.然后用构造函数赋值给实例 3.其他地方调用时返回实例 import package:social…

Java毕业设计-基于springboot开发的网上图书商城平台-毕业论文+答辩PPT(附源代码+演示视频)

文章目录 前言一、毕设成果演示&#xff08;源代码在文末&#xff09;二、毕设摘要展示1、开发说明2、需求分析3、系统功能结构 三、系统实现展示1、系统功能模块2、管理员功能模块3、卖家功能模块 四、毕设内容和源代码获取总结 Java毕业设计-基于springboot开发的网上图书商城…

taro之Swiper的使用

图样&#xff1a; 往往我们需要轮播图去显示我们想要的图片之类的 这是工作的代码 <View classNametop-title><SwiperclassNamebanner-swiperinterval{3000}circularautoplay>{homeBannerList.map((item) > {return (<SwiperItem key{item.id}><View…

apisix创建https

总结了下apisix 使用https 的问题和方法 1、apisix 默认https 端口是9443 2、apisix 需要上传证书后才可以使用https 否二curl测试会报错 SSL routines:CONNECT_CR_SRVR_HELLO 3、apisix 上传证书方法 我是使用的自签名证书&#xff0c;注意自签名证书的Common Name 要写你…

HCIP —— 生成树 (下)

目录 STP&#xff08;生成树&#xff09;的角色选举 根网桥 根端口 选举规则&#xff1a; 指定端口 生成树的端口状态 STP的接口状态&#xff1a;禁用、阻塞、侦听、学习、转发 五种状态 禁用状态 阻塞状态 侦听状态 学习状态 转发状态 当生成树拓扑结构发生变化 …

【Linux】Vmware16虚拟机安装Ubuntu,在Ubuntu中编译程序,编译过程中卡死,无法关机

1. 问题 软件版本&#xff1a; VMware 16.2.1Ubuntu 18 现象 C程序通过Makefile编译&#xff0c;在make过程中卡死&#xff0c;没有任何报错。卡死后任何操作都无反应&#xff0c;无法关机&#xff0c;只能通过任务管理器强行结束VMware忽然出现的这个问题&#xff0c;之前用了…