缓存界的 "双保险":打工人救星来了!(本地缓存 + Redis 双剑合璧,轻松应对高并发)

为什么需要二级缓存? 在分布式系统开发中,缓存是提升性能的 "利器",但单一缓存方案总有局限: 纯本地缓存(如 HashMap、Caffeine)速度快但无法跨服务共享,分布式环境下数据一致性难保证; 纯 Redis 缓存解决了分布式共享问题,但远程网络调用存在延迟,高并发下仍可能成为瓶颈; 更麻烦的是,高并发场景下还可能遇到缓存穿透、击穿、雪崩 "老三样" 问题,稍不注意就会压垮数据库。 基于这些痛点,我设计了这套本地缓存 + Redis 二级缓存工具类,结合两者优势:本地缓存负责 "快",Redis 负责 "稳",再通过精心设计的机制解决缓存常见问题。今天就把这套工具类的设计思路、使用方法分享出来,方便大家在项目中直接复用~

核心功能与适用场景

什么是 "二级缓存"?

简单说,就是在应用本地维护一层缓存(本文基于 Guava Cache 实现),同时结合分布式 Redis 缓存,形成 "本地缓存→Redis→数据库" 的三级查询链路。查询时优先命中本地缓存,减少远程调用;更新时同步维护两级缓存,保证数据一致性。

适用场景

这套工具类尤其适合以下场景:

  • 高并发读场景:如商品详情、用户信息、配置参数等高频访问数据,本地缓存可将响应时间压到微秒级;
  • 分布式一致性要求:需要多服务共享缓存状态(如库存、订单状态),Redis 保证跨服务数据一致;
  • 缓存问题高发区:存在热点 Key、数据库压力大、需要防缓存穿透 / 击穿 / 雪崩的场景。

痛点案例: 在电商商品详情页、新闻资讯列表、用户个人中心等场景中,某类数据(如爆款商品信息、热门文章)会被数万用户同时访问,单次请求即使耗时 10ms,高并发下也会累积成秒级延迟,甚至触发数据库连接池耗尽。

设计架构与核心组件解析

整体架构

工具类采用 "注解驱动 + 管理器核心 + 自动初始化" 的架构,核心组件包括 3 个:

@LocalCacheConfig:缓存配置注解,定义缓存名称、大小、过期时间; LocalCacheConfigProcessor:缓存初始化处理器,自动扫描注解并初始化缓存; TwoLevelCacheManager:二级缓存核心管理器,封装本地缓存 + Redis 操作,解决缓存问题。

核心组件深度解析

  1. @LocalCacheConfig:用注解定义缓存规则 这是缓存的 "配置入口",通过注解在类上声明缓存属性,无需手动编写初始化代码。
less 复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LocalCacheConfig {
    String value(); // 缓存名称(必填)
    int maxSize() default 5000; // 最大条目数,默认5000
    int expireSeconds() default 60; // 过期时间(秒),默认60秒
}

作用:通过注解声明缓存元数据,后续由处理器自动读取并初始化缓存实例。

  1. LocalCacheConfigProcessor:自动初始化的 "幕后推手"

作为 Spring 的BeanPostProcessor,它会在 Bean 初始化前扫描@LocalCacheConfig注解,调用TwoLevelCacheManager初始化缓存。

kotlin 复制代码
@Component
public class LocalCacheConfigProcessor implements BeanPostProcessor {
    private final TwoLevelCacheManager cacheManager;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        LocalCacheConfig annotation = bean.getClass().getAnnotation(LocalCacheConfig.class);
        if (annotation != null) {
            // 初始化缓存:名称、最大大小、过期时间
            cacheManager.initLocalCache(annotation.value(), annotation.maxSize(), annotation.expireSeconds());
            log.info("✅ 初始化本地缓存: {} (maxSize={}, expireSeconds={})", 
                    annotation.value(), annotation.maxSize(), annotation.expireSeconds());
        }
        return bean;
    }
}

作用:实现缓存的 "零侵入" 初始化,开发者只需加注解,无需手动调用初始化方法。

  1. TwoLevelCacheManager:缓存逻辑的 "核心大脑"

这是工具类的核心,封装了本地缓存(Guava Cache)和 Redis 的交互逻辑,解决了缓存三大问题,同时提供丰富的 API。

  • 核心属性:
  • localCacheMap:存储本地缓存实例(key 为缓存名称);
  • redisTemplate:操作 Redis 的客户端;
  • refreshExecutor:异步刷新缓存的线程池;
  • keyLocks:防击穿的锁集合,配合双重检查机制。

防 "三高" 问题设计:从根源解决缓存痛点

在高并发场景下,缓存容易出现三大问题,工具类通过以下设计逐个击破:

  1. 防缓存击穿:锁住热点 Key,避免数据库 "单点过载"

当热点 Key 过期瞬间,大量请求会穿透到数据库,导致压力骤增。解决方案是加锁 + 双重检查:

csharp 复制代码
// 伪代码流程
public T get(...) {
    // 1. 查本地缓存 → 2. 查Redis
    if (缓存未命中) {
        ReentrantLock lock = getLock(key); // 获取锁
        lock.lock();
        try {
            // 双重检查:再次查Redis,避免锁等待期间缓存已更新
            if (Redis仍未命中) {
                查数据库 → 更新缓存
            }
        } finally {
            lock.unlock();
            异步清理锁(避免内存泄漏)
        }
    }
}

关键:通过keyLocks维护锁集合,结合cleanupLockAsync异步清理过期锁,防止内存泄漏。

  1. 防缓存雪崩:随机过期时间,避免 "集体失效"

当大量缓存同时过期,请求会集中穿透到数据库,导致雪崩。解决方案是给过期时间加随机偏移:

scss 复制代码
// 防雪崩:过期时间=基础时间+随机偏移
long randomOffset = redisExpireSeconds > 10 ? 
    ThreadLocalRandom.current().nextLong(1, redisExpireSeconds / 10 + 1) : 1;
redisTemplate.opsForValue().set(key, cacheStr, redisExpireSeconds + randomOffset, TimeUnit.SECONDS);

效果:将缓存过期时间打散,避免同一时间大量缓存失效。

  1. 防缓存穿透:谨慎处理空值,避免 "无效请求"

当请求不存在的数据(如 ID=-1),缓存和数据库都无结果,会导致请求持续穿透到数据库。工具类通过不缓存空值 + 快速返回处理:

kotlin 复制代码
// 数据库查询结果为null时,不写入缓存
T dbValue = dbLoader.get();
if (dbValue == null) {
    return null; // 直接返回,不缓存空值
}

说明:相比缓存空值,这种方式更节省空间,适合明确 "无效 Key" 场景;若需缓存空值,可根据业务调整逻辑。

设计亮点:那些藏在细节里的思考

  1. 自动初始化机制 通过LocalCacheConfigProcessor实现缓存 "声明式配置",开发者无需手动调用initLocalCache,降低使用成本。

  2. 线程安全与资源管理 锁机制:用ReentrantLock替代synchronized,支持更灵活的锁控制; 线程池:异步刷新使用ThreadPoolExecutor,核心线程 2 个,最大 10 个,拒绝策略为 "调用者执行",避免任务丢失; 资源清理:@PreDestroy和DisposableBean确保服务关闭时,线程池优雅退出、缓存和锁集合清空,无内存泄漏。

  3. 灵活的序列化与类型支持 通过 FastJSON 的TypeReference处理泛型类型(如List),支持基础类型(int、long 等)、日期类型(Date、LocalDateTime)和自定义对象的序列化 / 反序列化,兼容性强。

复制代码看这里

LocalCacheConfig

less 复制代码
/**
 * 本地缓存配置(用于自动初始化缓存维度)
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LocalCacheConfig {
    String value();
    int maxSize() default 5000;
    int expireSeconds() default 60;
}

LocalCacheConfigProcessor

kotlin 复制代码
@Component
@Slf4j
public class LocalCacheConfigProcessor implements BeanPostProcessor {

    private final TwoLevelCacheManager cacheManager;

    public LocalCacheConfigProcessor(TwoLevelCacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        LocalCacheConfig annotation = bean.getClass().getAnnotation(LocalCacheConfig.class);
        if (annotation != null) {
            cacheManager.initLocalCache(annotation.value(), annotation.maxSize(), annotation.expireSeconds());
            log.info("✅ 初始化本地缓存: {} (maxSize={}, expireMinutes={})%n",
                    annotation.value(), annotation.maxSize(), annotation.expireSeconds());
        }
        return bean;
    }

}

TwoLevelCacheManager

scss 复制代码
package com.barcke.component.utils.cache;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PreDestroy;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;

/**
 * 二级缓存管理器 - 支持本地缓存(Guava Cache)+ Redis 分布式缓存
 * 具备防击穿、防穿透、防雪崩等功能
 *
 * @author Barcke
 * @version 2.0
 * @projectName self-barcke
 * @className TwoLevelCacheManager
 * @date 2025/8/9 11:56
 * @slogan: 源于生活 高于生活
 */
@Component
@Slf4j
public class TwoLevelCacheManager implements DisposableBean {

    private final Map<String, Cache<String, String>> localCacheMap = new ConcurrentHashMap<>();
    private final StringRedisTemplate redisTemplate;

    // 线程池状态控制
    private volatile boolean isShutdown = false;
    private final AtomicInteger activeRefreshTasks = new AtomicInteger(0);

    // 优化的线程池配置(异步刷新用)
    private final ExecutorService refreshExecutor = new ThreadPoolExecutor(
            2, // 核心线程
            10, // 最大线程
            60L, TimeUnit.SECONDS, // 空闲线程存活时间
            new ArrayBlockingQueue<>(1000), // 队列容量
            r -> {
                Thread thread = new Thread(r, "TwoLevelCache-Refresh-" + System.currentTimeMillis());
                thread.setDaemon(true); // 设置为守护线程
                return thread;
            },
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:由调用线程执行
    );

    // 使用ReentrantLock替代Object锁,提供更好的性能和可控性
    private final Map<String, ReentrantLock> keyLocks = new ConcurrentHashMap<>();

    // 锁的最大数量限制,防止内存泄漏
    private static final int MAX_LOCKS = 10000;

    public TwoLevelCacheManager(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        log.info("TwoLevelCacheManager initialized successfully");
    }

    /**
     * 初始化本地缓存
     * @param cacheName 缓存名称,不能为空
     * @param maxSize 最大缓存条目数,默认5000
     * @param expireSeconds 过期时间(秒),默认1分钟
     */
    public void initLocalCache(String cacheName, Integer maxSize, Integer expireSeconds) {
        if (!StringUtils.hasText(cacheName)) {
            throw new IllegalArgumentException("Cache name cannot be null or empty");
        }

        try {
            Cache<String, String> cache = CacheBuilder.newBuilder()
                    .maximumSize(Optional.ofNullable(maxSize).orElse(5000))
                    .expireAfterWrite(Optional.ofNullable(expireSeconds).orElse(5), TimeUnit.SECONDS)
                    .recordStats() // 启用统计信息
                    .build();

            localCacheMap.put(cacheName, cache);
            log.info("Local cache [{}] initialized with maxSize={}, expireMinutes={}",
                    cacheName, maxSize, expireSeconds);
        } catch (Exception e) {
            log.error("Failed to initialize local cache [{}]", cacheName, e);
            throw new RuntimeException("Failed to initialize local cache: " + cacheName, e);
        }
    }

    /**
     * 获取锁对象,如果锁数量超过限制则清理
     */
    private ReentrantLock getLock(String key) {
        // 简单的清理策略:当锁数量过多时,清空所有锁
        if (keyLocks.size() > MAX_LOCKS) {
            synchronized (keyLocks) {
                if (keyLocks.size() > MAX_LOCKS) {
                    keyLocks.clear();
                    log.warn("Key locks size exceeded limit, cleared all locks");
                }
            }
        }
        return keyLocks.computeIfAbsent(key, k -> new ReentrantLock());
    }

    /**
     * 获取缓存(带防击穿/穿透/雪崩)
     * @param cacheName 缓存名称
     * @param key 缓存键
     * @param clazz 返回类型
     * @param redisExpireSeconds Redis过期时间(秒)
     * @param dbLoader 数据加载器
     * @return 缓存值
     */
    public <T> T get(String cacheName, String key, Class<T> clazz, long redisExpireSeconds, Supplier<T> dbLoader) {
        return get(cacheName, key, clazz, null, redisExpireSeconds, dbLoader);
    }

    /**
     * 支持基础类型、List等复杂类型的缓存获取
     * @param cacheName 缓存名称
     * @param key 缓存键
     * @param clazz 返回类型
     * @param typeReference List等复杂类型时传入
     * @param redisExpireSeconds Redis过期时间
     * @param dbLoader 数据加载器
     * @return 缓存值
     */
    public <T> T get(String cacheName, String key, Class<T> clazz, TypeReference<T> typeReference, long redisExpireSeconds, Supplier<T> dbLoader) {
        // 参数校验
        if (!StringUtils.hasText(cacheName) || !StringUtils.hasText(key) || clazz == null || dbLoader == null) {
            throw new IllegalArgumentException("Invalid parameters for cache get operation");
        }

        if (isShutdown) {
            log.warn("Cache manager is shutdown, executing dbLoader directly");
            return dbLoader.get();
        }

        Cache<String, String> localCache = localCacheMap.get(cacheName);
        if (localCache == null) {
            throw new IllegalArgumentException("Local cache not initialized: " + cacheName);
        }

        try {
            // 1. 本地缓存查询
            String localValue = localCache.getIfPresent(key);
            if (localValue != null) {
                log.debug("Cache hit from local cache: key={}", key);
                return "null".equals(localValue) ? null : parseValue(localValue, clazz, typeReference);
            }

            // 2. Redis缓存查询
            String redisValue = null;
            try {
                redisValue = redisTemplate.opsForValue().get(key);
            } catch (Exception e) {
                log.warn("Failed to get value from Redis for key: {}", key, e);
            }

            if (redisValue != null) {
                log.debug("Cache hit from Redis: key={}", key);
                localCache.put(key, redisValue);
                return "null".equals(redisValue) ? null : parseValue(redisValue, clazz, typeReference);
            }

            // 3. 防击穿:加锁查询数据库
            ReentrantLock lock = getLock(key);
            lock.lock();
            try {
                // 双重检查:再次检查缓存
                try {
                    redisValue = redisTemplate.opsForValue().get(key);
                    if (redisValue != null) {
                        localCache.put(key, redisValue);
                        return "null".equals(redisValue) ? null : parseValue(redisValue, clazz, typeReference);
                    }
                } catch (Exception e) {
                    log.warn("Failed to double-check Redis for key: {}", key, e);
                }

                // 4. 查询数据库
                log.debug("Cache miss, loading from database: key={}", key);
                T dbValue;
                try {
                    dbValue = dbLoader.get();
                } catch (Exception e) {
                    log.error("Failed to load data from database for key: {}", key, e);
                    throw new RuntimeException("Failed to load data from database", e);
                }

                // 5. 结果处理和缓存设置
                if (dbValue == null) {
                    // 如果dbValue为null,直接返回,不进行缓存
                    return null;
                }

                // 序列化并缓存
                String cacheStr;
                try {
                    cacheStr = toCacheString(dbValue, clazz, typeReference);
                } catch (Exception e) {
                    log.error("Failed to serialize object for key: {}", key, e);
                    return dbValue; // 返回数据,但不缓存
                }

                // 防雪崩:添加随机过期时间偏移
                long randomOffset = redisExpireSeconds > 10 ?
                        ThreadLocalRandom.current().nextLong(1, redisExpireSeconds / 10 + 1) : 1;

                try {
                    redisTemplate.opsForValue().set(key, cacheStr, redisExpireSeconds + randomOffset, TimeUnit.SECONDS);
                    localCache.put(key, cacheStr);
                } catch (Exception e) {
                    log.warn("Failed to cache value for key: {}", key, e);
                }

                return dbValue;
            } finally {
                lock.unlock();
                // 异步清理锁(避免内存泄漏)
                cleanupLockAsync(key);
            }
        } catch (Exception e) {
            log.error("Unexpected error in cache get operation for key: {}", key, e);
            try {
                return dbLoader.get();
            } catch (Exception dbException) {
                log.error("Fallback database query also failed for key: {}", key, dbException);
                throw new RuntimeException("Both cache and database query failed", dbException);
            }
        }
    }

    /**
     * 支持直接指定List元素类型的缓存获取(简化版)
     * @param cacheName 缓存名称
     * @param key 缓存键
     * @param elementType List元素类型
     * @param redisExpireSeconds Redis过期时间
     * @param dbLoader 数据加载器
     * @return List<T>
     */
    public <T> List<T> getList(String cacheName, String key, Class<T> elementType, long redisExpireSeconds, Supplier<List<T>> dbLoader) {
        // 由于泛型擦除,List.class 不能直接用于反序列化泛型列表,需用TypeReference<List<T>>
        TypeReference<List<T>> typeReference = new TypeReference<List<T>>() {
            @Override
            public Type getType() {
                // 这里必须返回 List<T> 的 ParameterizedType
                return new com.alibaba.fastjson.util.ParameterizedTypeImpl(
                        new Type[]{elementType}, null, List.class);
            }
        };
        // 这里clazz参数必须传List.class,typeReference为List<T>,dbLoader需要适配Supplier<T>
        // 由于主get方法的泛型T,需强转dbLoader
        Supplier<List<T>> safeDbLoader = dbLoader;
        return get(cacheName, key, List.class, (TypeReference) typeReference, redisExpireSeconds, (Supplier) safeDbLoader);
    }

    /**
     * 兼容基础类型、List等复杂类型的解析
     */
    @SuppressWarnings("unchecked")
    private <T> T parseValue(String value, Class<T> clazz, TypeReference<T> typeReference) {
        try {
            // 基础类型直接返回
            if (clazz == String.class) {
                return (T) value;
            }
            if (clazz == Integer.class || clazz == int.class) {
                return (T) Integer.valueOf(value);
            }
            if (clazz == Long.class || clazz == long.class) {
                return (T) Long.valueOf(value);
            }
            if (clazz == Boolean.class || clazz == boolean.class) {
                return (T) Boolean.valueOf(value);
            }
            if (clazz == Double.class || clazz == double.class) {
                return (T) Double.valueOf(value);
            }
            if (clazz == Float.class || clazz == float.class) {
                return (T) Float.valueOf(value);
            }
            if (clazz == Short.class || clazz == short.class) {
                return (T) Short.valueOf(value);
            }
            if (clazz == Byte.class || clazz == byte.class) {
                return (T) Byte.valueOf(value);
            }
            if (clazz == java.util.Date.class) {
                // fastjson默认格式
                return (T) new java.util.Date(Long.parseLong(value));
            }
            if (clazz == LocalDateTime.class) {
                // fastjson默认格式为yyyy-MM-ddTHH:mm:ss
                // 兼容ISO_LOCAL_DATE_TIME和yyyy-MM-dd HH:mm:ss
                try {
                    return (T) LocalDateTime.parse(value, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
                } catch (Exception e) {
                    return (T) LocalDateTime.parse(value, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                }
            }
            // List等复杂类型
            if (typeReference != null) {
                return JSON.parseObject(value, typeReference);
            }
            // 其他对象
            return JSON.parseObject(value, clazz);
        } catch (Exception e) {
            log.error("Failed to parse value: {} to type: {}", value, clazz, e);
            throw new RuntimeException("Failed to parse cached value", e);
        }
    }

    /**
     * 兼容基础类型、List等复杂类型的序列化
     */
    @SuppressWarnings("unchecked")
    private <T> String toCacheString(T value, Class<T> clazz, TypeReference<T> typeReference) {
        if (value == null) {
            return "null";
        }
        // 基础类型直接toString
        if (clazz == String.class || clazz == Integer.class || clazz == int.class
                || clazz == Long.class || clazz == long.class
                || clazz == Boolean.class || clazz == boolean.class
                || clazz == Double.class || clazz == double.class
                || clazz == Float.class || clazz == float.class
                || clazz == Short.class || clazz == short.class
                || clazz == Byte.class || clazz == byte.class) {
            return value.toString();
        }
        if (clazz == java.util.Date.class) {
            return String.valueOf(((java.util.Date) value).getTime());
        }
        if (clazz == LocalDateTime.class) {
            // 使用ISO_LOCAL_DATE_TIME格式
            return ((LocalDateTime) value).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        }
        // List等复杂类型
        if (typeReference != null) {
            return JSON.toJSONString(value);
        }
        // 其他对象
        return JSON.toJSONString(value);
    }

    /**
     * 安全解析JSON(兼容老接口)
     */
    private <T> T parseJson(String json, Class<T> clazz) {
        return parseValue(json, clazz, null);
    }

    /**
     * 异步清理锁
     */
    private void cleanupLockAsync(String key) {
        if (!isShutdown && !refreshExecutor.isShutdown()) {
            refreshExecutor.submit(() -> {
                try {
                    // 延迟清理,给其他可能等待的线程一些时间
                    Thread.sleep(1000);
                    keyLocks.remove(key);
                } catch (Exception e) {
                    log.debug("Error during lock cleanup for key: {}", key, e);
                }
            });
        }
    }

    /**
     * 异步刷新缓存
     * @param cacheName 缓存名称
     * @param key 缓存键
     * @param redisExpireSeconds Redis过期时间
     * @param dbLoader 数据加载器
     */
    public <T> void refreshAsync(String cacheName, String key, long redisExpireSeconds, Supplier<T> dbLoader) {
        refreshAsync(cacheName, key, null, redisExpireSeconds, dbLoader);
    }

    /**
     * 支持基础类型、List等复杂类型的异步刷新
     */
    public <T> void refreshAsync(String cacheName, String key, TypeReference<T> typeReference, long redisExpireSeconds, Supplier<T> dbLoader) {
        if (!StringUtils.hasText(cacheName) || !StringUtils.hasText(key) || dbLoader == null) {
            log.warn("Invalid parameters for async refresh operation");
            return;
        }

        if (isShutdown || refreshExecutor.isShutdown()) {
            log.warn("Cache manager or refresh executor is shutdown, skipping async refresh");
            return;
        }

        Cache<String, String> localCache = localCacheMap.get(cacheName);
        if (localCache == null) {
            log.warn("Local cache not initialized: {}", cacheName);
            return;
        }

        // 控制并发刷新任务数量
        if (activeRefreshTasks.get() > 100) {
            log.warn("Too many active refresh tasks, skipping refresh for key: {}", key);
            return;
        }

        activeRefreshTasks.incrementAndGet();
        refreshExecutor.submit(() -> {
            try {
                log.debug("Starting async refresh for key: {}", key);
                T dbValue = dbLoader.get();

                // 如果dbValue为null,不进行缓存,直接返回
                if (dbValue == null) {
                    log.debug("Async refresh returned null for key: {}", key);
                    return;
                }

                String cacheStr = toCacheString(dbValue, (Class<T>) dbValue.getClass(), typeReference);
                localCache.put(key, cacheStr);

                long randomOffset = redisExpireSeconds > 10 ?
                        ThreadLocalRandom.current().nextLong(1, redisExpireSeconds / 10 + 1) : 1;

                redisTemplate.opsForValue().set(key, cacheStr, redisExpireSeconds + randomOffset, TimeUnit.SECONDS);
                log.debug("Async refresh completed for key: {}", key);

            } catch (Exception e) {
                log.error("Async refresh failed for key: {}", key, e);
            } finally {
                activeRefreshTasks.decrementAndGet();
            }
        });
    }

    /**
     * 支持直接指定List元素类型的异步刷新(简化版)
     */
    public <T> void refreshListAsync(String cacheName, String key, Class<T> elementType, long redisExpireSeconds, Supplier<List<T>> dbLoader) {
        refreshAsync(cacheName, key, new TypeReference<List<T>>() {
            @Override
            public Type getType() {
                return new com.alibaba.fastjson.util.ParameterizedTypeImpl(new Type[]{elementType}, null, List.class);
            }
        }, redisExpireSeconds, dbLoader);
    }

    /**
     * 设置缓存
     * @param cacheName 缓存名称
     * @param key 缓存键
     * @param value 缓存值
     * @param redisExpireSeconds Redis过期时间
     */
    public void put(String cacheName, String key, Object value, long redisExpireSeconds) {
        put(cacheName, key, value, null, redisExpireSeconds);
    }

    /**
     * 支持基础类型、List等复杂类型的put
     */
    @SuppressWarnings("unchecked")
    public <T> void put(String cacheName, String key, T value, TypeReference<T> typeReference, long redisExpireSeconds) {
        if (!StringUtils.hasText(cacheName) || !StringUtils.hasText(key)) {
            throw new IllegalArgumentException("Cache name and key cannot be null or empty");
        }

        // 如果value为null,直接返回,不进行缓存
        if (value == null) {
            return;
        }

        Cache<String, String> localCache = localCacheMap.get(cacheName);
        if (localCache == null) {
            throw new IllegalArgumentException("Local cache not initialized: " + cacheName);
        }

        try {
            String cacheStr = toCacheString(value, (Class<T>) (value != null ? value.getClass() : Object.class), typeReference);
            localCache.put(key, cacheStr);

            long randomOffset = redisExpireSeconds > 10 ?
                    ThreadLocalRandom.current().nextLong(1, redisExpireSeconds / 10 + 1) : 1;

            redisTemplate.opsForValue().set(key, cacheStr, redisExpireSeconds + randomOffset, TimeUnit.SECONDS);
            log.debug("Cache put successful for key: {}", key);
        } catch (Exception e) {
            log.error("Failed to put cache for key: {}", key, e);
            throw new RuntimeException("Failed to put cache", e);
        }
    }

    /**
     * 支持直接指定List元素类型的put(简化版)
     */
    public <T> void putList(String cacheName, String key, List<T> value, Class<T> elementType, long redisExpireSeconds) {
        put(cacheName, key, value, new TypeReference<List<T>>() {
            @Override
            public Type getType() {
                return new com.alibaba.fastjson.util.ParameterizedTypeImpl(new Type[]{elementType}, null, List.class);
            }
        }, redisExpireSeconds);
    }

    /**
     * 删除缓存
     * @param cacheName 缓存名称
     * @param key 缓存键
     */
    public void evict(String cacheName, String key) {
        if (!StringUtils.hasText(key)) {
            log.warn("Key cannot be null or empty for evict operation");
            return;
        }

        try {
            // 删除本地缓存
            if (StringUtils.hasText(cacheName)) {
                Cache<String, String> localCache = localCacheMap.get(cacheName);
                if (localCache != null) {
                    localCache.invalidate(key);
                    log.debug("Local cache evicted for key: {}", key);
                }
            } else {
                // 如果没有指定缓存名称,清理所有本地缓存
                localCacheMap.values().forEach(cache -> cache.invalidate(key));
                log.debug("All local caches evicted for key: {}", key);
            }

            // 删除Redis缓存
            redisTemplate.delete(key);
            log.debug("Redis cache evicted for key: {}", key);
        } catch (Exception e) {
            log.error("Failed to evict cache for key: {}", key, e);
        }
    }

    /**
     * 清理指定缓存的所有数据
     * @param cacheName 缓存名称
     */
    public void clear(String cacheName) {
        if (!StringUtils.hasText(cacheName)) {
            log.warn("Cache name cannot be null or empty for clear operation");
            return;
        }

        Cache<String, String> localCache = localCacheMap.get(cacheName);
        if (localCache != null) {
            localCache.invalidateAll();
            log.info("Local cache [{}] cleared", cacheName);
        }
    }

    /**
     * 获取缓存统计信息
     * @param cacheName 缓存名称
     * @return 统计信息字符串
     */
    public String getCacheStats(String cacheName) {
        if (!StringUtils.hasText(cacheName)) {
            return "Invalid cache name";
        }

        Cache<String, String> localCache = localCacheMap.get(cacheName);
        if (localCache == null) {
            return "Cache not found: " + cacheName;
        }

        return String.format("Cache[%s] - Size: %d, Stats: %s",
                cacheName, localCache.size(), localCache.stats());
    }

    /**
     * 资源清理
     */
    @PreDestroy
    @Override
    public void destroy() {
        log.info("Starting TwoLevelCacheManager shutdown...");
        isShutdown = true;

        if (!refreshExecutor.isShutdown()) {
            refreshExecutor.shutdown();
            try {
                if (!refreshExecutor.awaitTermination(10, TimeUnit.SECONDS)) {
                    log.warn("Refresh executor did not terminate gracefully, forcing shutdown");
                    refreshExecutor.shutdownNow();
                }
            } catch (InterruptedException e) {
                log.warn("Interrupted while waiting for refresh executor termination");
                refreshExecutor.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }

        // 清理本地缓存
        localCacheMap.values().forEach(Cache::invalidateAll);
        localCacheMap.clear();

        // 清理锁
        keyLocks.clear();

        log.info("TwoLevelCacheManager shutdown completed");
    }

}

如何使用(LocalCacheConfigTest)

kotlin 复制代码
package com.barcke.component.utils.cache;

import cn.hutool.json.ObjectMapper;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.barcke.Main;
import com.barcke.component.utils.cache.annotation.LocalCacheConfig;
import com.barcke.pojo.entity.Visitor;
import com.google.common.collect.Lists;
import com.rometools.rome.feed.atom.Person;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Order;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.cache.Cache;
import javax.cache.CacheManager;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * @author Barcke
 * @version 1.0
 * @projectName self-barcke
 * @className LocalCacheConfigTest
 * @date 2025/8/9 12:26
 * @slogan: 源于生活 高于生活
 * @description:
 **/
@Slf4j
@SpringBootTest(classes = Main.class,webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)
@LocalCacheConfig("test")
public class LocalCacheConfigTest {

    @Autowired
    private TwoLevelCacheManager twoLevelCacheManager;

    @Test
    public void test(){
        assertThat(twoLevelCacheManager.get("test", "test", String.class, 10, () -> {
            return "a";
        })).isEqualTo("a");

        assertThat(twoLevelCacheManager.get("test", "test", String.class, 10, () -> {
            return "a";
        })).isEqualTo("a");

        assertThat(twoLevelCacheManager.get("test", "test1", Integer.class, 10, () -> {
            return 1;
        })).isEqualTo(1);

        assertThat(twoLevelCacheManager.get("test", "test1", Integer.class, 10, () -> {
            return 1;
        })).isEqualTo(1);

        LocalDateTime now = LocalDateTime.now();
        assertThat(twoLevelCacheManager.get("test", "test2", LocalDateTime.class, 10, () -> {
            return now;
        })).isEqualTo(now);

        assertThat(twoLevelCacheManager.get("test", "test2", LocalDateTime.class, 10, () -> {
            return now;
        })).isEqualTo(now);

        List<Visitor> list = twoLevelCacheManager.get("test", "test3", List.class, 10, () -> {
            return null;
        });

        System.out.println(JSONObject.toJSONString(list));
        Map map = twoLevelCacheManager.get("test", "test4", Map.class, 10, () -> {
            HashMap hashMap = new HashMap();
            hashMap.put("a", "a");
            return hashMap;
        });

        Visitor visitor = twoLevelCacheManager.get("test", "test5", Visitor.class, 10, () -> {
            Visitor hashMap = new Visitor();
            hashMap.setId(1111L);
            hashMap.setLoginTime(new Date());
            return hashMap;
        });

        List<Visitor> test = twoLevelCacheManager.getList("test", "test3", Visitor.class, 10, () -> {
            return Lists.newArrayList(new Visitor().setNickName("aaa"));
        });

        List<Visitor> aa = twoLevelCacheManager.getList("test", "test3", Visitor.class, 10, () -> {
            return null;
        });

        System.out.println(JSONObject.toJSONString(test));
    }

}

总结与展望

这套二级缓存工具类通过 "本地缓存 + Redis" 的组合,在性能与一致性之间找到了平衡,同时解决了缓存穿透、击穿、雪崩等高频问题。核心优势在于:

易用性:注解驱动配置,API 简洁直观; 可靠性:完善的线程安全和资源管理; 扩展性:预留统计接口、支持自定义序列化,方便后续扩展。

后续计划加入缓存预热、动态配置刷新等功能,让工具类更强大。

相关推荐
AI风老师19 分钟前
5、docker镜像管理命令
java·docker·eureka
cwkiller23 分钟前
伏魔挑战赛-ASP/ASP.NET赛道10+绕过样本思路分享
后端
用户849137175471631 分钟前
JustAuth实战系列(第5期):建造者模式进阶 - AuthRequestBuilder设计解析
java·设计模式·架构
Code季风1 小时前
深入理解 Gin 框架的路由机制:从基础使用到核心原理
ide·后端·macos·go·web·xcode·gin
励志成为糕手1 小时前
从反射到方法句柄:深入探索Java动态编程的终极解决方案
java·开发语言
是乐谷2 小时前
饿了么招java开发咯
java·开发语言·人工智能·程序人生·面试·职场和发展
zhysunny2 小时前
20.万物皆可变身术:状态模式架构全景解析
java·状态模式
hongjunwu2 小时前
Java集合的遍历方式(全解析)
java·开发语言·windows
cccc来财2 小时前
Golang的本地缓存freecache
java·开发语言·jvm
Vallelonga2 小时前
关于 Rust 异步(无栈协程)的相关疑问
开发语言·后端·rust