JAVA缓存的使用RedisCache、LocalCache、复合缓存的操作

 更新时间:2026年02月05日 10:15:17   作者:Demon_Hao  
RedisCache是基于Redis的缓存,数据存储在内存中,并且可以被多个应用实例共享,属于分布式缓存,本文给大家介绍JAVA缓存的使用RedisCache、LocalCache、复合缓存的相关操作,感兴趣的朋友跟随小编一起看看吧

1️⃣RedisCache(分布式缓存)

概念
RedisCache 是基于 Redis 的缓存,数据存储在内存中,并且可以被多个应用实例共享,属于分布式缓存

优点

  • 高性能:Redis 在内存中读写,速度非常快(毫秒级甚至微秒级)。
  • 分布式共享:多个应用实例可以共享同一份缓存,保证数据一致性。
  • 持久化:可以选择持久化策略(RDB/AOF),重启后缓存不会完全丢失。
  • 功能丰富:支持数据结构(String、Hash、List、Set、SortedSet)、过期策略、订阅/发布等。
  • 扩展性强:可以通过 Redis Cluster 支持水平扩展,适合大规模系统。

缺点(顺便提醒)

  • 网络访问会增加延迟,尤其在高并发场景下。
  • 内存有限,数据量太大时成本高。
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
/**
 * Redis缓存工具类
 */
@Slf4j
@Component
public class RedisCacheTemplate {
    @Resource
    private StringRedisTemplate redisTemplate;
    @Resource
    private ObjectMapper objectMapper;
    /**
     * 指定TypeReference类型获取数据
     */
    public <T> T get(@NonNull String key, @NonNull Duration duration, @NonNull TypeReference<T> clazz, @NonNull Supplier<T> supplier) {
        return getT(key, duration, supplier, strValue -> {
            try {
                return objectMapper.readValue(strValue, clazz);
            } catch (Exception e) {
                log.error("Redis反序列化失败,KEY:{}", key, e);
                throw new RuntimeException(e);
            }
        });
    }
    /**
     * Class类型获取数据
     */
    public <T> T get(@NonNull String key, @NonNull Duration duration, @NonNull Class<T> clazz, @NonNull Supplier<T> supplier) {
        return getT(key, duration, supplier, strValue -> {
            try {
                return objectMapper.readValue(strValue, clazz);
            } catch (Exception e) {
                log.error("Redis反序列化失败,KEY:{}", key, e);
                throw new RuntimeException(e);
            }
        });
    }
    /**
     * 写入数据
     */
    public <T> void set(@NonNull String key, @NonNull Duration duration, @NonNull T object) {
        String redisKey = this.buildKey(key);
        try {
            String json = objectMapper.writeValueAsString(object);
            redisTemplate.opsForValue().set(redisKey, json, duration);
        } catch (JsonProcessingException e) {
            log.error("Redis序列化失败,KEY:{}", key, e);
            throw new RuntimeException(e);
        }
    }
    /**
     * 移除数据
     */
    public void del(@NonNull String key) {
        String redisKey = this.buildKey(key);
        redisTemplate.delete(redisKey);
    }
    /**
     * 公共业务代码处理
     */
    private <T> @Nullable T getT(@NotNull String key, @NotNull Duration duration, @NotNull Supplier<T> supplier, Function<String, T> deserialize) {
        String redisKey = this.buildKey(key);
        String strValue = redisTemplate.opsForValue().get(redisKey);
        if (Objects.isNull(strValue)) {
            T value = supplier.get();
            if (Objects.nonNull(value)) {
                this.set(key, duration, value);
            }
            return value;
        } else {
            return deserialize.apply(strValue);
        }
    }
    /**
     * Redis的KEY拼接
     */
    private String buildKey(String key) {
        return "test:cache:" + key;
    }
}
// 使用方式
public static void main(String[] args) {
    private static final TypeReference<List<String>> LIST_TEST = new TypeReference<>() {
    };
    @Resource
    private RedisCacheTemplate redisCacheTemplate;
    List<String> strings = redisCacheTemplate.get(RedisKey.TEST_LIST, Duration.ofMinutes(60), LIST_TEST, () -> {
        System.out.println("TEST");
        return List.of("Test");
    });
}

2️⃣LocalCache(本地缓存)

概念
LocalCache 是应用本地内存缓存,数据只存在于当前应用实例的内存里,常见实现有 Guava Cache、Caffeine

优点

  • 超低延迟:数据在本地内存,访问速度最快(纳秒到微秒级)。
  • 简单易用:不依赖外部组件,集成方便。
  • 降低网络压力:读写缓存不需要通过网络访问 Redis。
  • 可配置丰富:支持过期策略、容量限制、LRU/LFU 等缓存淘汰策略。

缺点

  • 数据不共享:多实例部署时,每个实例都有一份独立缓存,可能出现数据不一致。
  • 内存受限:缓存太大可能会占用应用内存,影响 JVM 性能。
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
/**
 * Caffeine本地缓存工具类
 */
@Slf4j
@Component
public class LocalCacheTemplate {
    /**
     * JVM 全局缓存
     * 存储 CacheValue 包装对象,支持每条缓存独立 TTL
     */
    private static final Cache<String, CacheValue<Object>> CACHE = Caffeine.newBuilder()
        // LRU 淘汰:数量满了移除长时间未访问或最早的数据
        .maximumSize(30000)
        // TTL 过期:按时间清理
        .expireAfter(new Expiry<String, CacheValue<Object>>() {
            @Override
            public long expireAfterCreate(@NotNull String key, @NotNull CacheValue<Object> value, long currentTime) {
                return value.ttlNanos();
            }
            @Override
            public long expireAfterUpdate(@NotNull String key, @NotNull CacheValue<Object> value, long currentTime, long currentDuration) {
                return value.ttlNanos();
            }
            @Override
            public long expireAfterRead(@NotNull String key, @NotNull CacheValue<Object> value, long currentTime, long currentDuration) {
                // 读取不改变 TTL
                return currentDuration;
            }
        }).build();
    @Resource
    private ObjectMapper objectMapper;
    /**
     * 判断KEY是否存在(非强一致,仅用于弱判断)
     */
    public boolean exists(@NonNull String key) {
        String redisKey = this.buildKey(key);
        return CACHE.getIfPresent(redisKey) != null;
    }
    /**
     * 获取KEY数据
     */
    public <T> T get(@NonNull String key, @NonNull Class<T> clazz) {
        String redisKey = this.buildKey(key);
        CacheValue<Object> wrapper = CACHE.getIfPresent(redisKey);
        return getT(key, clazz, wrapper);
    }
    /**
     * Class类型获取数据,没有就写入
     */
    public <T> T get(@NonNull String key, @NonNull Duration duration, @NonNull Class<T> clazz, @NonNull Supplier<T> supplier) {
        String redisKey = this.buildKey(key);
        CacheValue<Object> wrapper = CACHE.get(redisKey, k -> new CacheValue<>(supplier.get(), duration.toNanos()));
        return getT(key, clazz, wrapper);
    }
    /**
     * 指定TypeReference类型获取数据,没有就写入
     */
    public <T> T get(@NonNull String key, @NonNull Duration duration, @NonNull TypeReference<T> clazz, @NonNull Supplier<T> supplier) {
        String redisKey = this.buildKey(key);
        CacheValue<Object> wrapper = CACHE.get(redisKey, k -> new CacheValue<>(supplier.get(), duration.toNanos()));
        if (wrapper == null || wrapper.value() == null) {
            return null;
        }
        return getSerialize(key, wrapper.value(), strValue -> objectMapper.convertValue(strValue, clazz));
    }
    /**
     * 写入数据
     */
    public void set(@NonNull String key, @NonNull Duration duration, @NonNull Object value) {
        String redisKey = this.buildKey(key);
        CACHE.put(redisKey, new CacheValue<>(value, duration.toNanos()));
    }
    /**
     * 移除数据
     */
    public void del(@NonNull String key) {
        String redisKey = this.buildKey(key);
        CACHE.invalidate(redisKey);
    }
    /**
     * 公共业务代码处理
     */
    private <T> @Nullable T getT(@NotNull String key, @NotNull Class<T> clazz, CacheValue<Object> wrapper) {
        if (wrapper == null || wrapper.value() == null) {
            return null;
        }
        Object value = wrapper.value();
        // 已经是目标类型
        if (clazz.isInstance(value)) {
            return clazz.cast(value);
        }
        return getSerialize(key, value, strValue -> objectMapper.convertValue(strValue, clazz));
    }
    /**
     * 序列化操作
     */
    private <T> @Nullable T getSerialize(@NotNull String key, @NotNull Object object, Function<Object, T> deserialize) {
        try {
            return deserialize.apply(object);
        } catch (Exception e) {
            log.error("本地缓存序列化失败,KEY:{}", key, e);
            throw new RuntimeException(e);
        }
    }
    /**
     * Redis的KEY拼接
     */
    private String buildKey(String key) {
        return "test:cache:" + key;
    }
    /**
     * 存储数据和过期时间
     *
     * @param value    内容
     * @param ttlNanos 过期时间
     */
    public record CacheValue<T>(T value, long ttlNanos) {
    }
}
// 使用方式
public static void main(String[] args) {
    private static final TypeReference<List<String>> LIST_TEST = new TypeReference<>() {
    };
    @Resource
    private LocalCacheTemplate localCacheTemplate;
    List<String> strings = localCacheTemplate.get(RedisKey.TEST_LIST, Duration.ofMinutes(60), LIST_TEST, () -> {
        System.out.println("TEST");
        return List.of("Test");
    });
}

3️⃣复合缓存(Composite Cache / Two-level Cache)

概念
复合缓存结合了 LocalCache + RedisCache,常见模式是:

  • 一级缓存(LocalCache):应用本地内存,快速响应。
  • 二级缓存(RedisCache):共享分布式缓存,保证跨实例一致性。

优点

  • 速度快:大部分热点数据在本地缓存,访问本地即可,减少网络请求。
  • 一致性保证:Redis 做二级缓存,确保跨实例数据一致。
  • 降低压力:Redis 热点数据由本地缓存缓冲,降低 Redis 压力。
  • 灵活性高:可以对不同数据设置不同的缓存策略(本地缓存过期时间短,Redis 过期时间长)。
  • 可扩展性强:支持高并发,结合分布式和本地缓存的优势。

缺点

  • 实现复杂,需要处理缓存一致性问题(如本地缓存与 Redis 的同步)。
  • 本地缓存失效策略设计不当可能导致缓存雪崩或脏数据。
import com.fasterxml.jackson.core.type.TypeReference;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jspecify.annotations.NonNull;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.function.Supplier;
/**
 * 本地缓存和Redis缓存实现复合缓存工具类
 */
@Slf4j
@Component
public class CompoundCacheTemplate {
    // 默认10分钟
    public static Duration duration = Duration.ofMinutes(10);
    @Resource
    private RedisCacheTemplate redisCacheTemplate;
    @Resource
    private LocalCacheTemplate localCacheTemplate;
    /**
     * 获取KEY数据
     */
    public <T> T get(@NonNull String key, @NonNull Class<T> clazz) {
        // 先查本地缓存
        T value = localCacheTemplate.get(key, clazz);
        if (value != null) {
            return value;
        }
        // 再查Redis
        value = redisCacheTemplate.get(key, duration, clazz, () -> null);
        if (value != null) {
            // 回写本地缓存
            localCacheTemplate.set(key, Duration.ofMinutes(10), value);
        }
        return value;
    }
    /**
     * Class类型获取数据,没有就写入
     */
    public <T> T get(@NonNull String key, @NonNull Duration duration, @NonNull Class<T> clazz, @NonNull Supplier<T> supplier) {
        // 先查本地缓存
        T value = localCacheTemplate.get(key, clazz);
        if (value != null) {
            return value;
        }
        // 查Redis,如果没有就调用业务逻辑
        value = redisCacheTemplate.get(key, duration, clazz, supplier);
        if (value != null) {
            // 回写本地缓存
            localCacheTemplate.set(key, duration, value);
        }
        return value;
    }
    /**
     * 指定TypeReference类型获取数据,没有就写入
     */
    public <T> T get(@NonNull String key, @NonNull Duration duration, @NonNull TypeReference<T> clazz, @NonNull Supplier<T> supplier) {
        // 先查本地缓存
        T value = localCacheTemplate.get(key, duration, clazz, () -> null);
        if (value != null) {
            return value;
        }
        // 查Redis,如果没有就调用业务逻辑
        value = redisCacheTemplate.get(key, duration, clazz, supplier);
        if (value != null) {
            // 回写本地缓存
            localCacheTemplate.set(key, duration, value);
        }
        return value;
    }
    /**
     * 写入数据
     */
    public void set(@NonNull String key, @NonNull Duration duration, @NonNull Object value) {
        // 同步写入本地缓存和Redis
        localCacheTemplate.set(key, duration, value);
        redisCacheTemplate.set(key, duration, value);
    }
    /**
     * 移除数据
     */
    public void del(@NonNull String key) {
        // 同步删除本地缓存和Redis
        localCacheTemplate.del(key);
        redisCacheTemplate.del(key);
    }
}
// 使用方式
public static void main(String[] args) {
    private static final TypeReference<List<String>> LIST_TEST = new TypeReference<>() {
    };
    @Resource
    private CompoundCacheTemplate compoundCacheTemplate;
    List<String> strings = compoundCacheTemplate.get(RedisKey.TEST_LIST, Duration.ofMinutes(60), LIST_TEST, () -> {
        System.out.println("TEST");
        return List.of("Test");
    });
}

到此这篇关于JAVA缓存的使用RedisCache、LocalCache、复合缓存的操作的文章就介绍到这了,更多相关java rediscache localcache 复合缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • maven项目在svn中的上传与检出的方法

    maven项目在svn中的上传与检出的方法

    企业开发中经常使用svn来为我们控制代码版本,也经常使用maven来管理项目。下面将介绍一下如何将maven项目上传到svn中,如何将项目从svn中检出,感兴趣的小伙伴们可以参考一下
    2019-02-02
  • SpringBoot开发技巧之使用AOP记录日志示例解析

    SpringBoot开发技巧之使用AOP记录日志示例解析

    这篇文章主要为大家介绍了SpringBoot开发技巧之如何利用AOP记录日志的示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2021-10-10
  • java编译命令和启动命令的使用方式

    java编译命令和启动命令的使用方式

    Java开发中,编译源文件需使用javac命令,该命令能将.java文件编译成.class字节码文件,后者可在JVM上运行,常用编译选项包括-d指定输出目录,-classpath设置类搜索路径等,启动Java程序使用java命令,它加载并运行包含main方法的类
    2024-10-10
  • java中的分布式事务解决方式

    java中的分布式事务解决方式

    分布式事务是分布式系统中确保数据一致性的重要机制,它涉及多个数据源或参与者,要么所有操作全部成功,要么全部失败,常见的解决方案包括2PC(两阶段提交协议)、3PC(三阶段提交协议)和TCC(Try-Confirm-Cancel),2PC虽然简单但存在单点故障等问题
    2024-09-09
  • Java中this,static,final,const用法详解

    Java中this,static,final,const用法详解

    这篇文章主要介绍了Java中this,static,final,const用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • 关于Java中避免空指针的方法

    关于Java中避免空指针的方法

    这篇文章主要介绍了关于Java中避免空指针的方法,空指针异常就是我们在对空对象进行的任何操作都会报空指针异常,所谓的指针,就是java中的对象的引用,比如String s;这个就是指针,需要的朋友可以参考下
    2023-07-07
  • 基于list stream: reduce的使用实例

    基于list stream: reduce的使用实例

    这篇文章主要介绍了list stream: reduce的使用实例,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • 排序算法图解之Java希尔排序

    排序算法图解之Java希尔排序

    希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法,其也是一种特殊的插入排序,即将简单的插入排序进行改进后的一个更加高效的版本,也称缩小增量排序。本文通过图片和示例讲解了希尔排序的实现,需要的可以了解一下
    2022-11-11
  • 10个经典的Java main方法面试题

    10个经典的Java main方法面试题

    这篇文章主要为大家分享了10个经典的Java main方法面试题,与其说是Java面试题,其实也是Java的一些最基础知识问题,感兴趣的小伙伴们可以参考一下
    2016-01-01
  • 重新理解Java泛型

    重新理解Java泛型

    这篇文章主要介绍了重新理解Java泛型,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11

最新评论