多级缓存实现(本地缓存 Caffeine/Guava + 远程 Redis)

标准结构

请求 → 本地缓存(Caffeine)
→ 有 → 直接返回
→ 无 → 查 Redis
→ 有 → 写回本地缓存 → 返回
→ 无 → 查 DB → 写 Redis + 写本地缓存

实现步骤

1. 引入本地缓存(Caffeine 最强)

<dependency>
    <groupId>com.github.benmanes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.9.2</version>
</dependency>

2. 构建本地缓存

// 本地缓存:5分钟过期,最大10000个key
public static final LoadingCache<String, Object> LOCAL_CACHE = Caffeine.newBuilder()
    .expireAfterWrite(5, TimeUnit.MINUTES)  // 本地缓存5分钟
    .maximumSize(10000)
    .build(key -> null);

3.实现

public Object get(String key) {
    // ========== 1. 先读本地缓存 ==========
    Object obj = LOCAL_CACHE.getIfPresent(key);
    if (obj != null) {
        return obj;
    }

    // ========== 2. 再读 Redis ==========
    obj = redisTemplate.opsForValue().get(key);
    if (obj != null) {
        // 写回本地缓存
        LOCAL_CACHE.put(key, obj);
        return obj;
    }

    // ========== 3. 互斥锁,防止击穿 ==========
    RLock lock = redissonClient.getLock("lock:" + key);
    try {
        lock.lock(3, TimeUnit.SECONDS);

        // 双重检查
        obj = redisTemplate.opsForValue().get(key);
        if (obj != null) {
            LOCAL_CACHE.put(key, obj);
            return obj;
        }

        // ========== 4. 查询 DB ==========
        obj = dbMapper.selectById(key);

        // ========== 5. 防雪崩核心:TTL 加随机值 ==========
        long ttl = 60 * 60 + new Random().nextInt(600); // 1小时 ±10分钟

        // 热点key直接永不过期
        if (isHotKey(key)) {
            redisTemplate.opsForValue().set(key, obj);
        } else {
            redisTemplate.opsForValue().set(key, obj, ttl, TimeUnit.SECONDS);
        }

        // 写本地缓存
        LOCAL_CACHE.put(key, obj);

    } finally {
        lock.unlock();
    }

    return obj;
}

数据库缓存(默认开启)

以 MySQL 为例,主要包括:
InnoDB Buffer Pool(缓冲池)
查询缓存(MySQL 8.0 已去掉)
磁盘数据页缓存、索引缓存
它的特点:
在 MySQL 进程内部
缓存的是:数据页、索引页
作用:减少磁盘 IO,让重复查询更快
对应用透明,你代码完全感知不到
只要你查库,它就自动生效
此处注意与常规的数据库持久化落盘区别。

此处甄别MyBatis 一级缓存,二级缓存

一级缓存 = SqlSession 级别的缓存
特点:
在 你的 Java 应用进程内
默认开启
同一个 SqlSession(同一次请求 / 同一个事务)
相同 SQL + 参数 → 直接返回缓存,不查库
事务提交 / SqlSession 关闭,缓存就清空
适用:
一次请求里多次查同一条数据
范围极小,基本不会有脏数据问题
二级缓存 = Mapper 级别的全局缓存(跨 SqlSession,整个应用共享)

特点:
默认关闭,需要手动配置开启
缓存存在 应用内存
多个请求、多个线程共用
增删改会自动清空该表缓存
问题很大:
分布式下极易脏数据
高并发非常不安全
实际项目基本不用

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容