草庐IT

Caffeine高性能本地缓存框架初探

FunTester 2023-03-28 原文
通常情况下,为了提升服务性能,使用缓存框架是一个非常常见的选择。在Java语境下,经过我查阅,Caffeine被称作地标最强Java本地缓存框架。Caffeine是站在巨人(Guava Cache)的肩膀上,优化了算法发展而来。

在之前的性能测试框架开发中,通常用的缓存的时候都直接用java.util.concurrent.ConcurrentHashMap,但一涉及到过期策略就有点难以为继,搞不定了。经过简单学习实践,也算是Caffeine入门了。下面分享一下学习成果。

简介

Caffeine是Java语言的本地缓存性能框架,兼容Groovy语言,其他各位可以自行搜索。

常用功能

我主要用到Caffeine功能3点:

  1. 灵活的过期策略,可以访问计时过期、写入计时过期、自定义
  2. 灵活的写入策略,可以手动,还能同步,还可以异步
  3. API简单,上手快
其他高级功能暂时用不到,Caffeine性能数据,下次我单独JMH测试一下。

功能演示

主要实践3中写入策略的实践,过期策略其实只用前两种(访问、写入)即可满足现在的需求。

手动写入

import com.funtester.frame.SourceCode import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Caffeine import groovy.util.logging.Log4j2 import java.util.concurrent.TimeUnit import java.util.function.Function @Log4j2 class CaffeineManual extends SourceCode { static void main(String[] args) { Cache<Integer, Integer> cache = Caffeine.newBuilder() .maximumSize(100) .expireAfterWrite(100, TimeUnit.MILLISECONDS) .recordStats() .build() int key = 1 log.info("无缓存返回: {}", cache.getIfPresent(key)) log.info("无缓存自定义返回: {}", cache.get(key, new Function<Integer, Integer>() { @Override Integer apply(Integer integer) { return 3 } })) cache.put(key, 2) log.info("手动赋值后返回: {}", cache.getIfPresent(key)) sleep(1.0) log.info("缓存过期返回: {}", cache.getIfPresent(key)) cache.put(key, 2) cache.invalidate(key) log.info("手动删除后返回: {}", cache.getIfPresent(key)) } } 控制台打印:

21:41:30.329 main 无缓存返回: null 21:41:30.337 main 无缓存自定义返回: 3 21:41:30.338 main 手动赋值后返回: 2 21:41:31.360 main 缓存过期返回: null 21:41:31.364 main 手动删除后返回: null

同步写入

import com.funtester.frame.SourceCode import com.github.benmanes.caffeine.cache.CacheLoader import com.github.benmanes.caffeine.cache.Caffeine import com.github.benmanes.caffeine.cache.LoadingCache import groovy.util.logging.Log4j2 import java.util.concurrent.TimeUnit @Log4j2 class CaffeineSync extends SourceCode { static int cacheInit(int key) { log.info("返回赋值: {}", key) return key * 100; } static void main(String[] args) { LoadingCache<Integer, Integer> cache = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.MINUTES) .maximumSize(100) .build(new CacheLoader<Integer, Integer>() { @Override Integer load(Integer integer) throws Exception { return cacheInit(integer) } }); Integer value = cache.get(1) log.info("无缓存返回: {}", value) log.info("自定义返回: {}", cache.get(2, { return 31 })) log.info("获取返回结果: {}", value) Map<Integer, Integer> resMap = cache.getAll([1, 2, 3]) log.info("批量返回: {}",resMap) } } 控制台打印:

21:54:54.900 main 返回赋值: 1 21:54:54.903 main 无缓存返回: 100 21:54:54.963 main 自定义返回: 31 21:54:54.963 main 获取返回结果: 100 21:54:54.964 main 返回赋值: 3 21:54:54.965 main 批量返回: {1=100, 2=31, 3=300} 这里可以看到,自定义返回时,自定义的数值是优先于CacheLoader中的加载方法的。经过我测试,当自定义闭包里面如果报错的话,当前线程会中断。这时候可以用try-catch语法返回一个null即可。

异步加载

import com.funtester.frame.SourceCode import com.funtester.frame.execute.ThreadPoolUtil import com.github.benmanes.caffeine.cache.AsyncCache import com.github.benmanes.caffeine.cache.Caffeine import groovy.util.logging.Log4j2 import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit import java.util.function.Function @Log4j2 class CaffeineAsync extends SourceCode { static int cacheInit(int key) { return key * 100 } static void main(String[] args) { AsyncCache<Integer, Integer> asyncCache = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.SECONDS) .maximumSize(100).executor(ThreadPoolUtil.getFunPool()).buildAsync() CompletableFuture<Integer> future = asyncCache.get(1, new Function<Integer, Integer>() { @Override Integer apply(Integer integer) { log.info("开始加载缓存") sleep(1.0) return cacheInit(integer) } }) log.info("FunTester1") sleep(2.0) log.info("FunTester2") log.info("异步加载返回: {}", future.get()) sleep(2.0) log.info("缓存过期后Future返回: {}", future.get()) log.info("缓存过期后cache返回: {}", asyncCache.getIfPresent(1)) log.info("无缓存返回: {}", asyncCache.getIfPresent(2)) } } 控制台打印:

22:13:06.728 main FunTester1 22:13:06.728 F-1 开始加载缓存 22:13:08.738 main FunTester2 22:13:08.747 main 异步加载返回: 100 22:13:10.748 main 缓存过期后Future返回: 100 22:13:10.749 main 缓存过期后cache返回: null 22:13:10.750 main 无缓存返回: null 这里我们看2个信息:

  1. 加载程序是在CompletableFuture执行get之前完成的。
  2. 缓存过期之后,CompletableFuture还是可以获取值的。但是asyncCache.getIfPresent(1)返回值就是null了。
关于Caffeine功能的实践就到这里了,基本上就是半小时之内上手。这里友情提醒一下,Caffeine最新版本不支持JDK8了,目前我使用JDK8的Caffeine版本信息如下:

compile group: 'com.github.ben-manes.caffeine', name: 'caffeine', version: '2.9.3'

有关Caffeine高性能本地缓存框架初探的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  3. ruby - 如何在 Ubuntu 中清除 Ruby Phusion Passenger 的缓存? - 2

    我试过重新启动apache,缓存的页面仍然出现,所以一定有一个文件夹在某个地方。我没有“公共(public)/缓存”,那么我还应该查看哪些其他地方?是否有一个URL标志也可以触发此效果? 最佳答案 您需要触摸一个文件才能清除phusion,例如:touch/webapps/mycook/tmp/restart.txt参见docs 关于ruby-如何在Ubuntu中清除RubyPhusionPassenger的缓存?,我们在StackOverflow上找到一个类似的问题:

  4. ruby-on-rails - Ruby on Rails 计数器缓存错误 - 2

    尝试在我的RoR应用程序中实现计数器缓存列时出现错误Unknownkey(s):counter_cache。我在这个问题中实现了模型关联:Modelassociationquestion这是我的迁移:classAddVideoVotesCountToVideos0Video.reset_column_informationVideo.find(:all).eachdo|p|p.update_attributes:videos_votes_count,p.video_votes.lengthendenddefself.downremove_column:videos,:video_vot

  5. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  6. ruby - 在 Rails 项目中测试本地版本的 gem - 2

    我的Rails站点使用了一个确实不是很好的gem。每次我需要做一些新的事情时,我最终不得不花费与向实际Rails项目添加代码一样多的时间来为gem添加功能。但我不介意,我将我的Gemfile设置为指向我的gem的GitHub分支(我尝试提交PR,但维护者似乎已经下台)。问题是我真的没有找到一种合理的方法来测试我添加到gem的新东西。在railsc中测试它会特别好,但我能想到的唯一方法是a)更改~/.rvm/gems/.../foo。rb,这看起来不对或者b)升级版本,推送到Github,然后运行​​bundleup,这除了耗时之外显然是一场灾难,因为我不确定我所做的promise是否正

  7. Ruby 的数字方法性能 - 2

    我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0

  8. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  9. ruby-on-rails - 如果条件与 &&,是否有任何性能提升 - 2

    如果用户是所有者,我有一个条件来检查说删除和文章。delete_articleifuser.owner?另一种方式是user.owner?&&delete_article选择它有什么好处还是它只是一种写作风格 最佳答案 性能不太可能成为该声明的问题。第一个要好得多-它更容易阅读。您future的自己和其他将开始编写代码的人会为此感谢您。 关于ruby-on-rails-如果条件与&&,是否有任何性能提升,我们在StackOverflow上找到一个类似的问题:

  10. ruby - sinatra 框架的 MVC 模式 - 2

    我想开始使用“Sinatra”框架进行编码,但我找不到该框架的“MVC”模式。是“MVC-Sinatra”模式或框架吗? 最佳答案 您可能想查看Padrino这是一个围绕Sinatra构建的框架,可为您的项目提供更“类似Rails”的感觉,但没有那么多隐藏的魔法。这是使用Sinatra可以做什么的一个很好的例子。虽然如果您需要开始使用这很好,但我个人建议您将它用作学习工具,以对您来说最有意义的方式使用Sinatra构建您自己的应用程序。写一些测试/期望,写一些代码,通过测试-重复:)至于ORM,你还应该结帐Sequel其中(imho

随机推荐