Guava Cache 原理与实战

一、 什么是 Guava Cache?

简单来说,Guava Cache 是一个全内存的、线程安全的、类似于 Map 的本地缓存

如果你用过HashMap做缓存,你一定遇到过这些痛点:

  1. 内存溢出:Map 无限制增长,最终导致 OOM。
  2. 清理麻烦:需要自己写定时任务去清理过期数据。
  3. 并发安全:需要自己处理复杂的锁机制。

Guava Cache 就是为了解决这些问题而生的“增强版 Map”。它支持:

  • 自动过期:支持写入后多久过期、访问后多久过期。
  • 容量限制:支持最大缓存条数,基于 LRU(最近最少使用)算法淘汰。
  • 自动加载:缓存不存在时,自动回调加载数据。

二、 核心原理与快速入门

1. 核心组件:LoadingCache

Guava Cache 最常用的模式是LoadingCache(自动加载缓存)。它的核心思想是“Read-Through”策略:当调用方获取数据时,如果缓存中有,直接返回;如果没有,自动去数据源(DB/Redis)加载并回填。

2. 引入依赖

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

3. Hello World 示例

publicclassGuavaCacheDemo{// 定义缓存:Key是用户ID,Value是用户名privatestaticfinalLoadingCache<Long,String>USER_CACHE=CacheBuilder.newBuilder().maximumSize(1000)// 最多存1000条.expireAfterWrite(10,TimeUnit.MINUTES)// 写入10分钟后过期.build(newCacheLoader<Long,String>(){@OverridepublicStringload(Longkey){// 模拟查数据库return"User_"+key;}});publicstaticvoidmain(String[]args)throwsExecutionException{// 第一次:查库,存入缓存,返回System.out.println(USER_CACHE.get(1L));// 第二次:直接从内存返回,无需查库System.out.println(USER_CACHE.get(1L));}}

三、 进阶:串行刷新 vs 全异步刷新

这是 Guava Cache 最精髓,也是面试和实战中最容易踩坑的地方。针对“高并发下的缓存过期”,Guava 提供了两种完全不同的处理策略。

1. 串行刷新(同步阻塞)

使用build(loader)构建。

  • 机制:当缓存过期(refresh)时,当前请求线程会被阻塞,亲自去执行load方法加载数据。加载期间,其他线程返回旧值。
  • 优点上下文安全。因为是当前线程执行,ThreadLocal(如用户ID、租户ID)依然可用。
  • 缺点有卡顿。触发刷新的那个“倒霉”请求会变慢。
  • 适用场景:用户维度的缓存(如个人信息),依赖 ThreadLocal 上下文。
// 同步构建方式LoadingCache<K,V>syncCache=CacheBuilder.newBuilder().refreshAfterWrite(Duration.ofMinutes(1)).build(newCacheLoader<K,V>(){...});// 普通 loader

2. 并行刷新(全异步非阻塞)

使用build(CacheLoader.asyncReloading(loader, executor))构建。

  • 机制:当缓存过期时,当前请求线程不阻塞,直接返回旧值。同时,Guava 提交一个任务给后台线程池去加载新数据。
  • 优点极致性能。也就是所谓的“最终一致性”,用户永远感觉不到卡顿。
  • 缺点ThreadLocal 丢失。因为是后台线程池执行,拿不到主线程的 ThreadLocal 变量。
  • 适用场景:全局配置、热点榜单、白名单等与具体用户无关的数据。
// 异步构建方式// 这里的参数需要根据机器配置和业务量调整privatestaticfinalExecutorServiceASYNC_POOL=newThreadPoolExecutor(5,// 核心线程数:平时保留5个线程干活20,// 最大线程数:忙的时候最多扩容到20个60L,TimeUnit.SECONDS,// 空闲线程回收时间newLinkedBlockingQueue<>(100),// 队列容量:最多排队100个刷新任务// 给线程起个名字,方便出了 Bug 排查(强烈建议)newThreadFactoryBuilder().setNameFormat("guava-cache-refresh-%d").build(),// 【关键策略】拒绝策略:DiscardPolicy (丢弃策略)// 解释:如果线程池满了,队列也满了,说明系统负载极高。// 此时直接丢弃这次刷新任务,不抛异常,也不阻塞主线程。// 后果仅仅是缓存多旧了一会儿,等负载降下来下次请求再刷新即可。newThreadPoolExecutor.DiscardPolicy());// 2. 构建缓存publicstatic<K,V>LoadingCache<K,V>buildAsyncCache(CacheLoader<K,V>originalLoader){returnCacheBuilder.newBuilder().maximumSize(10000).refreshAfterWrite(Duration.ofMinutes(1)).build(CacheLoader.asyncReloading(originalLoader,// 原始的查库逻辑ASYNC_POOL// 【修正】传入自定义的安全线程池));}

四、 架构演进:Guava + Redis 多级缓存

单用 Redis 很快,但在面对突发热点 Key(如微博热搜、秒杀活动)时,Redis 的网络 IO 和单节点吞吐量依然可能成为瓶颈。

此时,Guava (L1) + Redis (L2)的多级缓存架构应运而生。

1. 架构流程

  1. 查询:先查本地 Guava -> 再查远程 Redis -> 最后查 DB。
  2. 命中率:通常 Guava 设置较短过期时间(如 5 秒),虽然短,但能拦截掉 99% 的瞬时高并发请求,保护 Redis。

2. 数据一致性挑战

本地缓存最大的痛点是各节点数据不一致

  • 场景:服务器 A 修改了配置,删除了 Redis,也清理了本地 Guava。但服务器 B 的 Guava 里还是旧配置。
  • 解决方案:引入Redis Pub/Sub(发布订阅)
    • 当数据更新时,发送一条消息到 Redis Channel。
    • 所有应用服务器监听该 Channel。
    • 收到消息后,各自清理本地的 Guava Cache。

五、 Guava Cache的异步刷新问题

1. 问题定义

Guava Cache 的asyncReloading特性虽然能极大提升接口响应速度(防止用户请求被数据库查询阻塞),但它引入了一个致命的上下文丢失问题。

1.1 场景还原

假设你的系统是多租户架构,使用ThreadLocal存储当前请求的TenantId(租户ID)。

  1. 主线程(HTTP请求线程):接收用户请求,拦截器将TenantId = 1001放入ThreadLocal
  2. 触发刷新:Guava Cache 发现数据过期,且使用了asyncReloading
  3. 异步切换:Guava 将“去数据库查询新值”的任务,提交给了一个独立的后台线程池
  4. 灾难发生
    • 后台线程池里的线程,和主线程不是同一个线程
    • Java 的ThreadLocal是线程隔离的,后台线程里的TenantIdnull
    • 结果CacheLoader.load()方法执行 SQL 时,因为取不到租户 ID,要么报错(空指针),要么查出了所有租户的数据(严重的数据泄露事故)。
1.2 为什么 JDK 自带的 InheritableThreadLocal (ITL) 不行?

JDK 其实有一个InheritableThreadLocal,允许子线程继承父线程的变量。但在线程池场景下它会失效:

  • ITL 的机制:只有在创建线程(new Thread)的那一瞬间,才会拷贝父线程的数据。
  • 线程池的机制:线程是复用的。线程池里的线程可能早就创建好了。
  • 第一次任务:线程创建,拷贝了上下文 A。
  • 第二次任务:线程复用,不再触发创建动作,上下文依然是 A,而不是当前主线程的 B。这就是所谓的“上下文污染”或“数据陈旧”。

2. TTL (TransmittableThreadLocal) 的原理是什么?

阿里开源的 TTL 专门为了解决在使用线程池等会池化复用线程的执行组件情况下,提供 ThreadLocal 值的传递

它的核心原理可以概括为CRR 模式Capture(捕获)、Replay(重放)、Restore(恢复)

2.1 核心流程

TTL 将上下文传递的时机,从“线程创建时”推迟到了任务提交/执行时

  1. Capture (捕获)
  • 时机:主线程将任务提交给线程池的那一刻(execute/submit)。
  • 动作:TTL 会把主线程里所有的TransmittableThreadLocal值捕获下来,保存在一个对象里。
  1. Replay (重放)
  • 时机:线程池里的工作线程即将开始执行任务的那一刻(run方法运行前)。
  • 动作:TTL 会把刚才捕获的值,拷贝到当前的工作线程中,覆盖工作线程原本的上下文。
  1. Execute (执行)
  • 动作:执行真正的业务逻辑(比如 Guava 的load方法)。此时代码里TTL.get()拿到的就是正确的主线程数据。
  1. Restore (恢复)
  • 时机:任务执行结束后。
  • 动作:TTL 会把工作线程的上下文恢复成执行任务之前的样子。
  • 目的:防止工作线程被“污染”,保证它回到线程池待命时是干净的(或者保持原样)。
2.2 实现方式

TTL 通过装饰者模式(Decorator Pattern)实现了对Runnable/Callable或者ExecutorService的包装,在run()方法外层包裹了上述的 CRR 逻辑。

3. 为什么能解决 Guava Cache 的问题?

结合 Guava Cache,通过 TTL 改造后的执行链路如下:

3.1 代码准备

必须做两件事:

  1. 容器替换:把存储租户 ID 的容器从ThreadLocal换成TransmittableThreadLocal
  2. 线程池修饰:把传给 Guava 的线程池用TtlExecutors包装。
// 1. 使用 TTL 容器staticTransmittableThreadLocal<String>context=newTransmittableThreadLocal<>();// 2. 包装线程池ExecutorServicettlExecutor=TtlExecutors.getTtlExecutorService(originalExecutor);// 3. 构建 Guava CacheCacheBuilder.newBuilder().refreshAfterWrite(Duration.ofMinutes(1)).build(CacheLoader.asyncReloading(loader,ttlExecutor));// 传入 TTL 线程池
3.2 执行步骤解析
步骤执行线程动作描述上下文状态
1. 请求进入主线程context.set("Tenant-A")Main: A
2. 触发刷新主线程Guava 调用ttlExecutor.execute(Task)Main: A
3. TTL 捕获主线程Capture: TTL 拦截任务提交,将 “Tenant-A” 存入 Task 对象Task 携带 A
4. 线程排队-任务在队列中等待…-
5. 线程捞取工作线程-1线程池分配Thread-1来执行任务Thread-1: Null (或旧值)
6. TTL 重放工作线程-1Replay: 任务开始前,TTL 将 Task 里的 “Tenant-A” 写入Thread-1Thread-1:A
7. 业务查库工作线程-1执行loader.load(),SQL 获取context.get()拿到 A,查库成功
8. TTL 恢复工作线程-1Restore: 任务结束,清空Thread-1的上下文Thread-1: Null

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

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

相关文章

机器学习工程师证书:智能制造时代入门票

在工业4.0时代的今天&#xff0c;制造业正经历一场智能化和数字化的变革。传统生产线不再是冰冷机械&#xff0c;而是逐渐被智能化的机器系统所取代的“大脑”&#xff0c;拥有“思考”和“学习”能力。但这一切&#xff0c;都离不开技术与制造交汇点的机器学习工程师。一、智能…

安达发|当APS计划排产排程排单软件,遇上最硬核的煤炭排程

引言&#xff1a;黑色宝藏背后的排产困境作为国家能源安全的“压舱石”&#xff0c;煤炭行业在保障能源供应中扮演着关键角色。然而&#xff0c;这个传统行业正面临前所未有的挑战&#xff1a;地质条件复杂多变、开采工艺多样、设备系统庞大、安全环保要求日益严格&#xff0c;…

自变量机器人获10亿融资,开源千寻模型登顶全球,欧姆龙升级工业机器人,OpenAI与丰田合作车载场景

自变量机器人获 10 亿元 A 轮融资 三大互联网巨头共同押注通用具身智能企业自变量机器人宣布完成 10 亿元 A 轮融资&#xff0c;本轮由字节跳动领投&#xff0c;红杉中国、北京信息产业发展基金等多家机构联合投资&#xff0c;这也是深创投 AI 基金成立后的首笔投资。值得关注的…

基于PLC的污水处理系统 程序文件 文档资料(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)

基于PLC的污水处理系统 程序文件 文档资料(设计源文件万字报告讲解)&#xff08;支持资料、图片参考_相关定制&#xff09; 基于PLC的污水处理系统 程序文件 文档资料&#xff5e;d82

Godot:独立开发者的开源超能力

“我试过 Unity&#xff0c;玩过 Unreal&#xff0c;最后在 Godot 里找到了做游戏的初心。” 如果你是一位有多年编程经验的开发者&#xff0c;曾想过亲手制作一款属于自己的游戏&#xff0c;却因引擎臃肿、授权复杂、学习曲线陡峭而望而却步——那么&#xff0c;请允许我向你介…

脑机接口行业发展报告:政策加码,临床加速,产业化进入关键阶段

摘要&#xff1a;本文聚焦脑机接口行业发展核心&#xff0c;系统梳理脑机接口技术原理、产品形式&#xff08;有创 / 无创 / 半侵入式&#xff09;、应用场景&#xff08;医疗为主&#xff0c;向工业安全、航空航天等非医疗领域延伸&#xff09;及产业链格局&#xff0c;深度复…

如何用耐达讯自动化Profibus总线光纤中继器解决变频器长距离通信干扰问题?

一、Profibus总线光纤中继器的协议特性与功能 Profibus总线光纤中继器是工业通信网络中的关键设备&#xff0c;其核心功能在于实现Profibus-DP协议的光电转换与信号增强。该设备通过将传统的RS485电信号转换为光信号&#xff0c;解决了长距离传输中的信号衰减问题&#xff0…

别让“不介入他人因果”成为冷漠的遮羞布

常听人说“不要介入他人因果”&#xff0c;这句话在社交媒体上悄然走红。它常被包装成“觉醒”“通透”“高维智慧”的代名词&#xff0c;频繁现身于各类心灵鸡汤、修行课程与短视频文案中。乍听之下&#xff0c;这句话似乎在传递尊重他人边界、放下控制欲的理念——这本是无可…

行李电子秤pcba方案开发设计

本文详细解读了行李电子秤的工作原理&#xff0c;涉及传感器、测力结构、参数规格&#xff08;如测量范围、精度等&#xff09;、并介绍了选择SIC8632单片机的应用。一、行李电子秤产品方案描述行李电子秤主要就是利用里传感器作为测量力的核心芯片&#xff0c;针对行李电子秤的…

域名信息查不到,是被屏蔽了吗?

在查询域名信息时&#xff0c;不少人都会遇到这种情况&#xff1a;输入域名后&#xff0c;结果显示“暂无数据”“无法查询”或信息不完整&#xff0c;于是很容易联想到——这个域名是不是被屏蔽了&#xff1f;是不是有问题&#xff1f;其实&#xff0c;域名信息查不到&#xf…

全网最全8个AI论文写作软件,研究生高效选题与格式规范必备!

全网最全8个AI论文写作软件&#xff0c;研究生高效选题与格式规范必备&#xff01; AI 工具如何助力论文写作&#xff0c;让研究更高效 在研究生阶段&#xff0c;论文写作是学术生涯中不可避免的一环。面对繁重的选题、资料整理、格式规范以及重复率控制等问题&#xff0c;许多…

域名中介和自己谈,有什么本质区别?

在域名交易中&#xff0c;很多买家都会纠结一个问题&#xff1a;到底是自己直接联系卖家谈&#xff0c;还是通过域名中介或交易平台来操作&#xff1f;看似只是路径不同&#xff0c;但在实际成交效率、价格结果和风险控制上&#xff0c;差别往往非常明显。一、信息获取能力的差…

上海精密机械工厂10个研发设计共用一台SolidWorks工作站

在上海精密机械工厂的研发设计部门&#xff0c;SolidWorks作为核心三维设计软件&#xff0c;每天承载着大量复杂机械结构的设计与仿真任务。传统模式下&#xff0c;每位工程师需要配备高性能图形工作站&#xff0c;不仅成本高昂&#xff0c;还存在数据分散、协作效率低等问题。…

当AI开始“淘汰”与“成就”:我们拆解出AGI重塑产业的五个逻辑 | 2025 想象·AGI产业全景报告发布

我们正站在一个时代的断层线上。不是AI会不会改变世界的问题&#xff0c;而是它正在以什么方式、多快速度、淘汰谁、又成就谁。2025年12月26日&#xff0c;极新主办的「2025极新AIGC峰会」在上海浦东浦软大厦盛大召开。会上&#xff0c;极新创始人姜稳解读并发布了《2025 想象A…

- Kappa架构:利用Kafka锻造的屠龙刀

你好&#xff0c;我是程序员贵哥。 今天我要分享的主题是Kappa架构。 同样身为大规模数据处理架构&#xff0c;Kappa架构这把利用Kafka锻造的“屠龙刀”&#xff0c;它与Lambda架构的不同之处在哪里呢&#xff1f; 上一讲中&#xff0c;我讲述了在处理大规模数据时所用到经典架…

漫画说:为什么你的“增量计算”越跑越慢?——90%的实时数仓团队都踩过的坑,藏在这几格漫画里

为什么每次只改一行数据&#xff0c;却要重算上亿条历史记录&#xff1f;你在构建实时看板、用户画像或风控特征时&#xff0c;是否也遇到过这样的困境&#xff1f;每天新增的订单可能只有几万条&#xff0c;但背后的用户、商品、支付表动辄上亿行。 为了刷新一个聚合指标&…

计算机专业学生考研失败如何快速就业?二战VS就业?

计算机专业学生考研失败如何快速就业&#xff1f;二战VS就业&#xff1f; 作为计算机专业的学生&#xff0c;面对考研失利后的抉择&#xff0c;需要结合个人情况和行业特点理性分析。以下是分步建议&#xff1a; 第一步&#xff1a;自我评估 技术能力盘点 梳理已掌握的编程语言…

Science子刊超绝idea:注意力机制+强化学习!足式机器人障碍穿越首次达成 100% 成功率

近期&#xff0c;注意力机制强化学习这个方向迎来了重磅突破。苏黎世联邦理工学院机器人系统实验室在《Science Robotics》&#xff08;IF26.1&#xff09;中提出了一种创新的控制框架&#xff1a;该框架通过结合强化学习和多头注意力机制&#xff0c;让机器人在面对不同类型地…

新年第一缕阳光,在牯牛山之巅迎接

云南那些超厉害的美食&#xff0c;以及昆明周边元旦徒步的隐秘之地&#xff0c;共同描绘出极具野趣的冬日玩法&#xff0c;既可以满足舌尖上的冒险&#xff0c;还能够遇到限定的自然风光。云南人&#xff0c;简直可以说好像处在食物链顶端&#xff0c;餐桌上满满的都是山野气息…

GISer大事件,保研考研竞赛时间线一览

GIS领域的重大事件和活动安排已经新鲜出炉&#xff0c;方便GIS专业的同学有一个更全面规划&#xff0c;覆盖了从学术竞赛到职业发展等多个方面。无论是寒假的实践培训&#xff0c;还是暑期的实习机会&#xff0c;从学术答辩到职业招聘会&#xff0c;每一个环节都是GIS专业人才成…