写点什么

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

作者:FunTester
  • 2023-03-07
    北京
  • 本文字数:3466 字

    阅读完需:约 11 分钟

通常情况下,为了提升服务性能,使用缓存框架是一个非常常见的选择。在 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.SourceCodeimport com.github.benmanes.caffeine.cache.Cacheimport com.github.benmanes.caffeine.cache.Caffeineimport groovy.util.logging.Log4j2
import java.util.concurrent.TimeUnitimport java.util.function.Function
@Log4j2class 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 无缓存返回: null21:41:30.337 main 无缓存自定义返回: 321:41:30.338 main 手动赋值后返回: 221:41:31.360 main 缓存过期返回: null21:41:31.364 main 手动删除后返回: null

复制代码

同步写入

import com.funtester.frame.SourceCodeimport com.github.benmanes.caffeine.cache.CacheLoaderimport com.github.benmanes.caffeine.cache.Caffeineimport com.github.benmanes.caffeine.cache.LoadingCacheimport groovy.util.logging.Log4j2
import java.util.concurrent.TimeUnit
@Log4j2class 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 返回赋值: 121:54:54.903 main 无缓存返回: 10021:54:54.963 main 自定义返回: 3121:54:54.963 main 获取返回结果: 10021:54:54.964 main 返回赋值: 321:54:54.965 main 批量返回: {1=100, 2=31, 3=300}
复制代码

这里可以看到,自定义返回时,自定义的数值是优先于CacheLoader中的加载方法的。经过我测试,当自定义闭包里面如果报错的话,当前线程会中断。这时候可以用try-catch语法返回一个null即可。

异步加载


import com.funtester.frame.SourceCodeimport com.funtester.frame.execute.ThreadPoolUtilimport com.github.benmanes.caffeine.cache.AsyncCacheimport com.github.benmanes.caffeine.cache.Caffeineimport groovy.util.logging.Log4j2
import java.util.concurrent.CompletableFutureimport java.util.concurrent.TimeUnitimport java.util.function.Function
@Log4j2class 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 FunTester122:13:06.728 F-1  开始加载缓存22:13:08.738 main FunTester222:13:08.747 main 异步加载返回: 10022:13:10.748 main 缓存过期后Future返回: 10022:13:10.749 main 缓存过期后cache返回: null22: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'
复制代码

FunTester 原创专题推荐~

-- By FunTester

发布于: 刚刚阅读数: 2
用户头像

FunTester

关注

公众号:FunTester,800篇原创,欢迎关注 2020-10-20 加入

Fun·BUG挖掘机·性能征服者·头顶锅盖·Tester

评论

发布
暂无评论
Caffeine高性能本地缓存框架初探_FunTester_InfoQ写作社区