高级篇 05. 多级缓存 - JVM 进程缓存之实现业务缓存

📚 高级篇 05. 多级缓存 - JVM 进程缓存之实现业务缓存

一、 架构图解:JVM 缓存拦截流程

在改造代码之前,我们先明确一下加入 Caffeine 之后的请求处理路径:

  1. 前端发来查询商品详情的请求(例如查询 id = 10001 的商品)。

  2. 请求到达 Tomcat 中的 ItemController,随后进入 ItemService

  3. ItemService 首先去查 Caffeine 缓存

    • 如果命中 (Cache Hit): 直接返回内存中的对象数据(耗时不到 1 毫秒),结束!
    • 如果未命中 (Cache Miss): 去 MySQL 数据库查询,查到数据后,将其写入 Caffeine 缓存,然后再返回给前端。

二、 实战改造:注入 Caffeine Bean

在 Spring Boot 项目中,为了统一管理缓存的配置(比如商品缓存的容量、库存缓存的容量可能不同),我们通常会创建一个专门的配置类来定义 Cache 对象,并将它们注册为 Spring 的 Bean。

第 1 步:引入依赖

确保你的 pom.xml 中已经有了 Caffeine 的依赖:

XML

xml 复制代码
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

第 2 步:编写配置类 (CaffeineConfig.java)

由于在真实的电商系统中,商品的基本信息(标题、图片)商品的库存信息 变化频率完全不同(库存几乎每秒都在变),我们强烈建议将它们分管在两个不同的缓存实例中

Java

kotlin 复制代码
package com.heima.item.config;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.heima.item.pojo.Item;
import com.heima.item.pojo.ItemStock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CaffeineConfig {

    /**
     * 商品基本信息缓存 (变化频率低)
     */
    @Bean
    public Cache<Long, Item> itemCache() {
        return Caffeine.newBuilder()
                .initialCapacity(100)       // 初始容量
                .maximumSize(10_000)        // 最大容量,防止 OOM
                // 可以配置基于大小或时间的驱逐策略
                .build();
    }

    /**
     * 商品库存信息缓存 (变化频率极高)
     */
    @Bean
    public Cache<Long, ItemStock> itemStockCache() {
        return Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(10_000)
                .build();
    }
}

三、 实战改造:业务层优雅集成

配置好了 Bean,接下来我们在 ItemService 中注入这些 Cache,并运用上一节学到的终极优雅写法 get(key, function)

第 3 步:改造 ItemService (核心逻辑)

打开你的商品查询业务类,进行如下改造:

Java

kotlin 复制代码
package com.heima.item.service.impl;

import com.github.benmanes.caffeine.cache.Cache;
import com.heima.item.mapper.ItemMapper;
import com.heima.item.pojo.Item;
import com.heima.item.service.IItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ItemServiceImpl implements IItemService {

    @Autowired
    private ItemMapper itemMapper;

    // 1. 注入我们在 Config 中配置的商品缓存 Bean
    @Autowired
    private Cache<Long, Item> itemCache;

    @Override
    public Item queryItemById(Long id) {
        // 2. 极其优雅的缓存处理逻辑 (一行代码搞定查缓存 + 查库 + 存缓存)
        // 逻辑:用 id 去 itemCache 里找。
        // 找不到?那就自动执行后面的 Lambda 表达式 (去数据库查 selectById(id)),然后存入缓存并返回。
        return itemCache.get(id, key -> {
            System.out.println("❌ 本地缓存未命中,正在查询 MySQL 数据库,id: " + key);
            return itemMapper.selectById(key);
        });
    }
}

(库存逻辑也是同理,注入 itemStockCache 并在查询库存的方法中使用即可。)


四、 见证奇迹:测试与验证

改造完成后,重启你的 Spring Boot 项目。

  1. 第一次请求前端页面(查询 id=10001 的商品):

    你会发现控制台打印了:❌ 本地缓存未命中,正在查询 MySQL 数据库,id: 10001,并且看到了一条打印出的 SQL 语句。这说明第一次请求穿透到了数据库。

  2. 疯狂刷新页面(第二次、第三次、第一万次):

    你会发现无论你按多少次 F5,控制台再也没有任何 SQL 语句打印出来!

    请求响应时间从原来的几十毫秒,瞬间骤降到了几毫秒甚至不到 1 毫秒

因为从第二次开始,所有的流量全被 Tomcat JVM 内存里的 Caffeine 拦截并光速返回了。数据库此时稳如泰山!


五、 大厂高频拷问:JVM 缓存的致命缺陷是什么?

在面试中,千万不要把 JVM 缓存吹得完美无缺。你需要主动抛出它的致命缺陷,展现你的全局架构观:

🗣️ 面试官发难: "既然 Caffeine 本地缓存这么快,我们以后所有项目只用它不就行了,为什么还要大费周章去搭 Redis 集群?"

💡 你的满分破局解析:

"本地缓存虽然速度达到了极致的纳秒级,但它有两个致命的缺陷,决定了它只能作为多级缓存中的前置防线,而不能完全替代 Redis:

  1. 内存容量极度受限: JVM 的堆内存通常配置只有几个 G 到十几个 G,还要留给业务对象和垃圾回收。把海量的业务数据全塞在 JVM 里一定会触发频繁的 Full GC,甚至 OOM 导致服务器宕机。而 Redis 分片集群可以无限横向扩容到 TB 级别。
  2. 极其头疼的数据一致性问题 (集群数据漂移): 在微服务架构下,商品服务通常会部署多个实例(例如 Tomcat 1、Tomcat 2)。如果后台修改了商品价格,通常只会清除其中一台 Tomcat 的本地缓存,或者请求还没来得及同步。此时,用户刷新页面,请求被网关随机路由到 Tomcat 1(看到新价格)和 Tomcat 2(看到旧价格),数据出现了严重的幻读现象

因此,本地缓存只适合存放极度热点且容量较小的数据。对于海量数据,我们依然需要一个统一的、全局可见的分布式缓存(Redis)来作为数据共享的兜底底座。"


学习总结

这节课,你的 Spring Boot 已经进化出了极速响应的能力。Caffeine 就像是给 Tomcat 穿上了一件防弹衣,完美解决了 Redis 热点 Key 可能导致的单节点雪崩问题。


相关推荐
Oneslide1 小时前
基于Nginx实现目录列表展示与文件下载服务(K8s ConfigMap配置版)
后端
Java编程爱好者1 小时前
对于java工程师(高级)的面试如果只考3道题,就能看出他的真实水平
后端
PFinal社区_南丞1 小时前
Go-1.26-五年最差版本-Bug 深度分析
后端
三水不滴2 小时前
Elasticsearch 实战系列(一):从核心基础概念入门到实战落地
后端·elasticsearch
三水不滴2 小时前
Elasticsearch 生产环境全栈最佳实践:从架构设计到故障排查一站式落地指南
后端·elasticsearch·搜索引擎
星纬智联技术2 小时前
从 OpenAI Harness Engineering 蒸馏出四个 Skill,Agent 跑了 25 小时
后端
神奇小汤圆2 小时前
横空出世!IDEA最强Spring插件来了,效率翻倍!
后端
aisifang002 小时前
SpringBoot Maven 项目 pom 中的 plugin 插件用法整理
spring boot·后端·maven
我还不赖2 小时前
skill-creator Skill 深度分析
后端