Guava Cache 实战:构建高并发场景下的字典数据缓存

一、场景背景

在系统开发中,字典数据(如状态类型、分类数据)具有以下特点:

  • 高频读取(每个请求都可能涉及)
  • 低频变化(管理员修改后才会变更)
  • 数据一致性要求适中(允许分钟级延迟)
  • 传统方案每次查询数据库的方式会造成性能瓶颈,本文展示如何基于 Guava Cache 构建缓存层。

二、技术选型分析

为什么选择 Guava Cache?

  • 轻量级:无需引入 Redis 等中间件
  • 自动加载:提供 LoadingCache 自动回源能力
  • 灵活策略:支持基于时间/权重的淘汰策略
  • 线程安全:内置并发控制机制

三、实战代码解析

1.相关代码文件

KeyValue

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class KeyValue<K, V> implements Serializable {

    private K key;
    private V value;

}

CacheUtils

java 复制代码
public class CacheUtils {
    // 异步刷新缓存(适合全局数据)
    public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
        return CacheBuilder.newBuilder()
                .refreshAfterWrite(duration) // 写后刷新时间
                .build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool()));
    }
    
    // 同步刷新缓存(适合用户关联数据)
    public static <K, V> LoadingCache<K, V> buildCache(Duration duration, CacheLoader<K, V> loader) {
        return CacheBuilder.newBuilder()
                .refreshAfterWrite(duration)
                .build(loader);
    }
}

DictFrameworkUtils

java 复制代码
public class DictFrameworkUtils {

    private static DictDataApi dictDataApi;

    private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO();

    /**
     * 针对 {@link #getDictDataLabel(String, String)} 的缓存
     */
    private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> GET_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
            Duration.ofMinutes(1L), // 过期时间 1 分钟
            new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {

                @Override
                public DictDataRespDTO load(KeyValue<String, String> key) {
                    return ObjectUtil.defaultIfNull(dictDataApi.getDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);
                }

            });

    /**
     * 针对 {@link #getDictDataLabelList(String)} 的缓存
     */
    private static final LoadingCache<String, List<String>> GET_DICT_DATA_LIST_CACHE = CacheUtils.buildAsyncReloadingCache(
            Duration.ofMinutes(1L), // 过期时间 1 分钟
            new CacheLoader<String, List<String>>() {

                @Override
                public List<String> load(String dictType) {
                    return dictDataApi.getDictDataLabelList(dictType);
                }

            });

    /**
     * 针对 {@link #parseDictDataValue(String, String)} 的缓存
     */
    private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> PARSE_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
            Duration.ofMinutes(1L), // 过期时间 1 分钟
            new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {

                @Override
                public DictDataRespDTO load(KeyValue<String, String> key) {
                    return ObjectUtil.defaultIfNull(dictDataApi.parseDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);
                }

            });

    public static void init(DictDataApi dictDataApi) {
        DictFrameworkUtils.dictDataApi = dictDataApi;
        log.info("[init][初始化 DictFrameworkUtils 成功]");
    }


    @SneakyThrows
    public static String getDictDataLabel(String dictType, String value) {
        return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, value)).getLabel();
    }

    @SneakyThrows
    public static List<String> getDictDataLabelList(String dictType) {
        return GET_DICT_DATA_LIST_CACHE.get(dictType);
    }

    @SneakyThrows
    public static String parseDictDataValue(String dictType, String label) {
        return PARSE_DICT_DATA_CACHE.get(new KeyValue<>(dictType, label)).getValue();
    }

}

2. 核心工具类封装(CacheUtils)

java 复制代码
public class CacheUtils {
    // 异步刷新缓存(适合全局数据)
    public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
        return CacheBuilder.newBuilder()
                .refreshAfterWrite(duration) // 写后刷新时间
                .build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool()));
    }
}

关键配置说明:

  • refreshAfterWrite:写入后指定时间触发异步刷新
  • asyncReloading:使用独立线程池执行刷新任务
  • Executors.newCachedThreadPool:弹性线程池应对突发流量

3. 字典缓存实现(DictFrameworkUtils)

3.1 缓存初始化

java 复制代码
   private static DictDataApi dictDataApi;

    private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO();

  	// 使用复合 Key 缓存字典项(类型+值 → 标签)
    private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> GET_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
            Duration.ofMinutes(1L), // 过期时间 1 分钟
            new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {

                @Override
                public DictDataRespDTO load(KeyValue<String, String> key) {
                    return ObjectUtil.defaultIfNull(dictDataApi.getDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);
                }

            });

3.2 缓存使用示例

java 复制代码
@SneakyThrows // 通过 Lombok 隐藏异常
public static String getDictDataLabel(String dictType, String value) {
    return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, value)).getLabel();
}

四、最佳实践总结

1. 缓存策略设计

策略 说明 本案例实现
缓存穿透 非法 Key 导致频繁回源 返回 DICT_DATA_NULL 空对象
缓存雪崩 大量缓存同时失效 随机过期时间(可扩展)
缓存击穿 热点 Key 失效导致并发回源 使用 LoadingCache 原子加载

2. 性能优化点

  • 异步刷新:通过 asyncReloading 实现后台线程刷新,避免阻塞请求线程
  • 分层缓存:同时缓存 字典项 → 标签 和 标签 → 字典项 两种关系
  • 弹性线程池:使用 CachedThreadPool 应对突发刷新请求
相关推荐
全栈弄潮儿²⁰²⁴2 天前
AI Agent 开发实战(30):限流、缓存与成本控制
人工智能·gpt·缓存·agent·限流·agi·成本控制
Together_CZ2 天前
DeepSeek-V4.1-Flash:Pushing the Limits of KV Cache Compression——推动 KV 缓存压缩的极限
缓存·llm·compression·kv cache·deepseek·v4.1-flash·推动 kv 缓存压缩的极限
Pioneer000013 天前
我用 Redis + 网关做多模型 API 路由:缓存命中率 95%+ 的工程实践
人工智能·redis·后端·缓存·性能优化·架构
hweiyu003 天前
Redis命令:MSETNX
redis·缓存
wdfk_prog4 天前
ROS教程08:从 TransportTCP::connect() 追到 TCPROS Connection Header、序列化与 Socket 数据传输
运维·缓存·docker·容器·ros
是Dream呀4 天前
Harness 工程:让 Agent 真正把任务做完
人工智能·分布式·缓存·agent
努力努力再努力wz4 天前
【Redis进阶系列】:从主从复制到 Sentinel,一文建立故障检测、Leader 选举与 Failover 的完整心智模型
数据库·redis·缓存
wdfk_prog5 天前
ROS教程10:从 gtest 到 rostest——Unit Test、Node 集成测试、rosbag 与 rqt 验证闭环
运维·缓存·docker·容器·ros
程序猿乐锅5 天前
【黑马点评 | 第四篇】Redis缓存雪崩
java·数据库·spring boot·redis·缓存
HanhahnaH5 天前
Redis单线程和Tair多线程架构设计对比
分布式·缓存