当前位置: 首页 > web >正文

本地缓存大杀器-Caffeine

本地缓存大杀器-Caffeine

    • 一、 背景
    • 二、 应用
    • 三、 实现原理
    • 四、 核心设计
    • 五、 总结

一、 背景

1、 本地缓存作为一种高效的缓存方式,能够显著减少对远程数据源的访问,从而快速响应请求。而在众多本地缓存工具中,Caffine 凭借其卓越的性能和丰富的功能脱颖而出,成为开发者们的得力助手,堪称本地缓存的 “大杀器”。
2、 在传统的应用架构中,每次请求数据都可能需要从数据库、远程 API 等数据源获取,这不仅会增加网络延迟,还会对数据源造成较大压力。随着业务的发展,当并发请求量逐渐增大时,这种方式会导致系统响应缓慢,用户体验急剧下降。本地缓存的出现,很好地缓解了这些问题,它将经常被访问的数据存储在应用程序本地内存中,下次请求相同数据时,可直接从内存读取,大大提高了数据读取速度。
3、 不止用于加速数据查询场景,在某些序列化或者对象转换的场景也可用本地缓存做到对象复用。

二、 应用

maven项目在pom.xml文件中添加如下依赖:

<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>3.1.8</version>
</dependency>

引入依赖后,就可以开始使用 Caffine 构建缓存实例。示例如下:

    public static void main(String[] args) {// 淘汰监听Caffeine<Object, Object> caffeine = Caffeine.newBuilder().initialCapacity(1024) // 初始化容量,避免频繁扩容.weakKeys() // 弱引用的key,只有这一种选择,在gc时会被回收.softValues() // 软引用的值,有两种选择,soft or weak,soft时内存不足时回收.evictionListener((key, val, removalCause) -> {if (removalCause == RemovalCause.EXPLICIT) { // 移除事件} else if (removalCause == RemovalCause.SIZE) { // 容量超限事件} else if (removalCause == RemovalCause.REPLACED) { // 更新事件} else if (removalCause == RemovalCause.COLLECTED) { // 回收事件} else if (removalCause == RemovalCause.EXPIRED) { // 过期事件}// nothing})/*Executor 主要用于执行异步操作,例如异步加载缓存项、异步清理过期缓存项等。*/.executor(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() - 1)) // 执行调度任务的执行器,不传默认 ForkJoinPool.commonPool().recordStats() // 统计数据.ticker(Ticker.systemTicker()) // 提供时间的度量,使得缓存能够根据时间来判断缓存项是否过期、是否需要刷新等。/*DisabledScheduler:这是一个禁用调度功能的调度器。使用它时,Caffeine 不会主动调度过期缓存项的清理任务,只有在进行读写操作时才会顺便检查并清理过期项。场景:当你希望减少调度带来的开销,或者缓存项的过期清理不需要严格的定时控制时,可以使用该调度器。默认调度器。ExecutorServiceScheduler:允许你使用自定义的 ExecutorService 来执行调度任务。这意味着你可以根据自身的并发需求和性能要求,灵活配置线程池的大小和行为。场景:当你有特定的线程池配置需求,例如需要控制线程数量、线程优先级等,以满足复杂的并发场景时,可以使用该调度器。GuardedScheduler:用于对另一个调度器进行包装,提供额外的保护机制。它可以防止在调度任务执行过程中抛出异常,确保调度器的稳定性。场景:当你使用的底层调度器可能会抛出异常,而你希望避免这些异常影响整个缓存系统的正常运行时,可以使用该调度器。SystemScheduler:使用系统默认的调度机制,依赖于 Java 的 ScheduledExecutorService 来执行调度任务。它提供了一个简单、方便的默认调度方案。场景:当你没有特殊的调度需求,只需要一个基本的调度功能时,可以使用该调度器。*/.scheduler(Scheduler.guardedScheduler(new Scheduler() {@Overridepublic @NonNull Future<?> schedule(@NonNull Executor executor, @NonNull Runnable runnable, @Positive long l, @NonNull TimeUnit timeUnit) {return CompletableFuture.runAsync(runnable, executor);}})) // 调度器:控制缓存项的过期调度,也就是决定何时对过期的缓存项进行清理。.maximumSize(2048) // 基于大小的淘汰策略.weigher(Weigher.boundedWeigher(new Weigher<>() { // 权重计算@Overridepublic @NonNegative int weigh(@NonNull Object object, @NonNull Object object2) {return object2.toString().length();}})).maximumWeight(1000) // 基于权重的淘汰策略,这里需要实现权重的逻辑,上述两种策略二选一.expireAfterWrite(Duration.ofMinutes(3)) // 写之后多久过期.expireAfterAccess(Duration.ofMinutes(3)) // 访问(包括读取和写入)之后多久过期.expireAfter(new Expiry<Object, Object>() {// nanos@Overridepublic long expireAfterCreate(@NonNull Object object, @NonNull Object object2, long l) {return 1000 * 1000 * 60 * 3L;}@Overridepublic long expireAfterUpdate(@NonNull Object object, @NonNull Object object2, long l, @NonNegative long l1) {return 1000 * 1000 * 60 * 3L;}@Overridepublic long expireAfterRead(@NonNull Object object, @NonNull Object object2, long l, @NonNegative long l1) {return 1000 * 1000 * 60 * 3L;}}) // 自定义 Expiry 接口来控制每项的过期时间。可以根据不同的键值对设置不同的过期时间。;// 同步处理LoadingCache<Object, Object> cache = caffeine.build(new CacheLoader<Object, Object>() {@Overridepublic @Nullable Object load(@NonNull Object object) throws Exception {return null;}});cache.put(new Object(), new Object());cache.get(new Object());cache.getIfPresent(new Object());// 异步处理AsyncLoadingCache<Object, Object> asyncCache = caffeine.buildAsync(new AsyncCacheLoader<Object, Object>() {@Overridepublic @NonNull CompletableFuture<Object> asyncLoad(@NonNull Object object, @NonNull Executor executor) {return CompletableFuture.supplyAsync(()->{return null;});}});asyncCache.put(new Object(), CompletableFuture.completedFuture(new Object()));asyncCache.getIfPresent(new Object());}

三、 实现原理

1、 Caffine 的高性能得益于其巧妙的实现原理。它采用了分段锁(Striped Locking)和分段哈希表(Striped Hash Table)的设计,大大减少了锁竞争,提高了并发性能。在多线程环境下,多个线程可以同时访问不同的分段,从而避免了单一锁带来的性能瓶颈。
2、 Caffine 在缓存淘汰策略方面也做了深入优化。它默认采用的是 W-TinyLFU 算法,这是一种结合了时间(Time)和频率(Frequency)的高效淘汰算法。该算法会记录数据的访问频率和时间,优先淘汰访问频率低且时间较久的数据。同时,Caffine 还提供了其他多种淘汰策略供开发者选择,如 LRU(最近最少使用)、LFU(最不经常使用)等,开发者可以根据实际业务需求进行配置。
3、 在数据加载方面,Caffine 支持异步加载和同步加载两种方式。异步加载可以在数据过期或缺失时,在后台线程中加载数据,避免了阻塞请求线程,提高了系统的响应速度。同步加载则适用于对数据一致性要求较高的场景,确保在获取数据时,数据已经被正确加载到缓存中。

四、 核心设计

1、 Caffine 的核心设计围绕着性能、易用性和灵活性展开。其架构设计使得缓存操作尽可能高效,减少不必要的开销。缓存的存储结构采用了高效的数据结构,如哈希表和双向链表等,保证了数据的快速插入、查询和删除操作。
在功能设计上,Caffine 提供了丰富的接口和配置选项,满足不同开发者和业务场景的需求。无论是简单的缓存存储,还是复杂的缓存过期、淘汰、刷新等功能,都能通过简洁的 API 实现。同时,Caffine 还支持与 Spring Cache 等框架的集成,方便在企业级应用中快速应用。
Caffine 对内存的管理也十分精细。它通过一系列优化策略,如弱引用(Weak Reference)、软引用(Soft Reference)等,合理控制缓存占用的内存大小,避免内存泄漏和过度占用,确保应用程序在长时间运行过程中保持稳定的性能。

五、 总结

1、 Caffine 作为一款强大的本地缓存工具,凭借其出色的性能、丰富的功能和灵活的设计,成为了开发者提升应用性能的重要选择。它在减少数据访问延迟、提高系统并发能力等方面表现卓越,能够有效降低对远程数据源的依赖,提升用户体验。
2、 无论是小型应用还是大型企业级项目,Caffine 都能发挥重要作用。在使用过程中,开发者可以根据实际业务需求,合理配置缓存的各种参数,选择合适的淘汰策略和数据加载方式,以达到最佳的缓存效果。
3、 随着技术的不断发展,相信 Caffine 会持续优化和完善,为开发者带来更多便利,在本地缓存领域继续发挥其 “大杀器” 的作用,助力各类应用程序实现更高效、更快速的发展。
今天很晚了,后续源码分析待定。

http://www.xdnf.cn/news/1673.html

相关文章:

  • HTML响应式网页设计与跨平台适配
  • vue element使用el-table时,切换tab,table表格列项发生错位问题
  • 驱动开发硬核特训 · Day 19:从字符设备出发,掌握 Linux 驱动的实战路径(含 gpio-leds 控制示例)
  • 成人高考难吗-录取线仅需120分?
  • Mysql主从复制和读写分离
  • 运维打铁:Centos 7 安装 redis_exporter 1.3.5
  • 大语言模型之提示词技巧
  • 多线程环境下的资源共享与线程安全问题
  • SpringBootTest报错
  • 更新英雄位置
  • CS144 Lab 6 实战记录:构建 IP 路由器
  • LLM量化技术全景:GPTQ、QAT、AWQ、GGUF与GGML
  • 如何在 Odoo 18 中配置自动化动作
  • 如何在 Unity 中导入 gltf /glb 文件
  • 机器人灵巧手有刷与无刷空心杯电机解析
  • Bean的生命周期
  • 免费的 HTML 网页托管服务
  • 图像预处理-霍夫变换
  • React学习路线
  • 解决高德地图AMapUtilCoreApi、NetProxy类冲突
  • 哑光蓝色调风光人像Lr调色教程,手机滤镜PS+Lightroom预设下载!
  • Java for循环中,如何在内循环跳出外循环?
  • AI | 最近比较火的几个生成式对话 AI
  • STM32 的 GPIO和中断
  • 为什么提示词能够提高大语言模型的理解能力
  • git Http改用户下载
  • 动手试一试 Spring Security入门
  • 加深对vector理解OJ题
  • WASM与Kotlin反编译难度对比分析
  • 【Yolo精读+实践+魔改系列】Yolov1论文超详细精讲(翻译+笔记)