RedisCacheConfiguration 的全局定制
在 Spring Boot 整合 Redis 的过程中,默认的缓存配置(RedisCacheConfiguration)虽然开箱即用,但在生产环境中往往存在以下痛点:
- 数据不可读:默认使用 JDK 序列化,Redis 中存储的是二进制乱码。
- Key 命名冗余 :默认使用双冒号
::分隔,导致在 Redis 可视化工具中出现空文件夹层级。 - 缺乏过期限制:默认缓存永不过期,可能导致 Redis 内存堆积。
本章将详细解析如何通过自定义 RedisCacheConfiguration Bean,一次性解决上述问题,实现规范化的缓存存储策略。
一、 RedisCacheConfiguration 的核心职责
RedisCacheConfiguration 是 Spring Data Redis 提供的一个不可变(Immutable)配置类。它的核心职责是定义缓存数据的属性 和格式。
由于它是不可变的,任何修改方法(如 entryTtl)都不会修改当前对象,而是返回一个新的配置实例。我们主要通过以下四个维度进行定制:
- 序列化策略 (Serialization):Key 和 Value 的二进制转换规则。
- 前缀策略 (Prefixing):Key 的命名空间生成规则。
- 生存时间 (TTL):全局默认的过期时间。
- 空值处理 (Null Handling):防止缓存穿透的策略。
二、 核心定制点详解
1. 序列化定制:从 JDK 二进制到 JSON
Spring 默认使用 JdkSerializationRedisSerializer。
- 缺点 :生成的 Value 是
\xac\xed\x00\x05...这样的二进制流,无法人工阅读,且 Java 强依赖,这就限制了其他语言(如 Python/Go)读取该缓存。 - 解决 :通常替换为
GenericJackson2JsonRedisSerializer,将对象存储为标准的 JSON 字符串。需要注意处理LocalDateTime等 Java 8 时间类型的序列化问题。
2. 前缀策略定制:去除双冒号
Spring 默认生成的 Key 格式为 CacheName::Key。
- 缺点 :双冒号
::在 Redis Desktop Manager 等工具中会被解析为两层目录,其中一层是空的,视觉效果差。 - 解决 :通过
computePrefixWith方法,将分隔符修改为单冒号:,例如CacheName:Key。
3. 过期时间定制
默认配置是 Duration.ZERO(永不过期)。建议在全局配置中设置一个合理的默认值(如 1 小时),作为兜底策略。
三、 完整代码实践
下面是一个生产级可用的配置示例。将此代码放入你的 Spring Boot 项目的配置包下即可生效。
java
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching // 开启缓存注解支持
public class RedisConfig {
/**
* 自定义 RedisCacheConfiguration
* 作用:定制 Key/Value 的序列化方式、过期时间、Key 前缀格式
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
// 1. 构造 JSON 序列化器
// 使用 GenericJackson2JsonRedisSerializer 替换默认序列化
GenericJackson2JsonRedisSerializer jsonSerializer = buildJsonSerializer();
// 2. 构建配置
return RedisCacheConfiguration.defaultCacheConfig()
// 【核心 1】设置默认过期时间:1 小时
.entryTtl(Duration.ofHours(1))
// 【核心 2】定制 Key 的序列化:使用 String (默认就是 String,显式指定更清晰)
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
// 【核心 3】定制 Value 的序列化:使用 JSON
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer))
// 【核心 4】定制 Key 前缀策略:CacheName + ":" + Key
// 默认是 "::",这里改为 ":",避免 RDM 工具出现空目录
.computePrefixWith(name -> name + ":")
// 【可选】禁止缓存 Null 值 (根据业务需求决定是否开启)
// .disableCachingNullValues()
;
}
/**
* 辅助方法:构建支持 Java8 时间和类型信息的 JSON 序列化器
*/
private GenericJackson2JsonRedisSerializer buildJsonSerializer() {
ObjectMapper objectMapper = new ObjectMapper();
// 1. 注册 JavaTimeModule,解决 LocalDateTime 等时间类序列化问题
objectMapper.registerModule(new JavaTimeModule());
// 2. 启用多态类型验证,将类信息 (@class) 写入 JSON
// 这样从 Redis 读出来时,才能自动转回具体的 Java 对象,而不仅仅是 Map
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY
);
return new GenericJackson2JsonRedisSerializer(objectMapper);
}
}
四、 配置前后的效果对比
假设我们有一个 @Cacheable(value = "users", key = "1001") 的缓存操作,存储一个 User 对象。
1. 修改前(默认配置)
- Redis Key :
users::1001(双冒号) - Redis Value :
\xac\xed\x00\x05sr\x00\x1bcom.example.User...(二进制乱码) - TTL: -1 (永不过期)
2. 修改后(自定义配置)
- Redis Key :
users:1001(单冒号,目录结构清晰) - Redis Value:
json
{
"@class": "com.example.User",
"id": 1001,
"username": "admin",
"createTime": [2023, 10, 1, 12, 0, 0]
}
(标准 JSON,可读性强,其他语言可读)
- TTL: 3600 (1小时后自动删除)
进阶定制 RedisCacheManager实现动态过期时间
在上一章中,我们通过 RedisCacheConfiguration 解决了序列化和 Key 命名格式的问题。然而,在实际生产环境中,我们面临一个新的挑战:缓存的生命周期管理。
默认的 RedisCacheManager 虽然支持设置全局过期时间(例如统一 1 小时),但业务场景往往更加复杂:
- 验证码:需要 5 分钟过期。
- 用户会话:需要 30 分钟过期。
- 首页广告:需要 24 小时过期。
- 系统参数:可能需要永不过期。
如果使用 Spring 原生方式,我们需要在配置类中构建一个巨大的 Map<String, RedisCacheConfiguration> 来一一指定,这既繁琐又难以维护。
本章将介绍如何通过继承并扩展 RedisCacheManager,实现基于注解后缀的动态过期时间控制 (例如:@Cacheable(value = "users#30m"))。
一、 核心思路:重写工厂方法
RedisCacheManager 的本质是一个工厂(Factory) ,负责生产 RedisCache 对象。其核心方法是 createRedisCache(String name, RedisCacheConfiguration cacheConfig)。
我们的改造思路遵循 "约定优于配置" 的原则:
- 拦截 :在创建缓存实例前,拦截传入的
name(缓存名称)。 - 解析 :判断
name中是否包含特定的分隔符(如#),提取时间信息。 - 定制 :如果包含时间信息,则基于全局配置衍生出一个新的配置对象,并修改其 TTL。
- 还原 :将
name还原为干净的业务名称(去除时间后缀),创建缓存实例。
二、 代码实现
下面是一个生产级的 TimeoutRedisCacheManager 实现。它支持 d (天), h (小时), m (分钟), s (秒) 等时间单位。
1. 自定义 Manager 类
java
import cn.hutool.core.util.StrUtil; // 示例使用了 Hutool 工具类,也可使用原生 String 方法
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import java.time.Duration;
public class TimeoutRedisCacheManager extends RedisCacheManager {
private static final String SPLIT_FLAG = "#";
public TimeoutRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
/**
* 核心重写方法:在创建 Cache 对象时介入
*/
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
// 1. 如果名称为空或不包含分隔符,直接使用父类逻辑(应用全局默认配置)
if (StrUtil.isEmpty(name) || !name.contains(SPLIT_FLAG)) {
return super.createRedisCache(name, cacheConfig);
}
// 2. 切割字符串
// 格式约定:cacheName#ttl,例如 "system_users#30m"
String[] parts = name.split(SPLIT_FLAG);
if (parts.length != 2) {
return super.createRedisCache(name, cacheConfig);
}
// 3. 提取真实缓存名和时间字符串
String realName = parts[0]; // "system_users"
String ttlStr = parts[1]; // "30m"
// 4. 解析时间,并修改配置
// 注意:RedisCacheConfiguration 是不可变的,entryTtl 会返回新实例
Duration duration = parseDuration(ttlStr);
if (cacheConfig != null) {
cacheConfig = cacheConfig.entryTtl(duration);
}
// 5. 使用真实的名称和定制后的配置创建缓存
// 这样 Redis 中的 Key 就是 "prefix:system_users:key",不会带上 "#30m"
return super.createRedisCache(realName, cacheConfig);
}
/**
* 辅助方法:解析时间字符串
* 支持格式:1d, 4h, 30m, 60s
*/
private Duration parseDuration(String ttlStr) {
String timeUnit = StrUtil.subSuf(ttlStr, -1).toLowerCase(); // 获取最后一位单位
long timeValue = Long.parseLong(StrUtil.sub(ttlStr, 0, ttlStr.length() - 1)); // 获取数值
switch (timeUnit) {
case "d":
return Duration.ofDays(timeValue);
case "h":
return Duration.ofHours(timeValue);
case "m":
return Duration.ofMinutes(timeValue);
case "s":
return Duration.ofSeconds(timeValue);
default:
// 默认单位为秒,或者根据业务需求抛出异常
return Duration.ofSeconds(Long.parseLong(ttlStr));
}
}
}
2. 注册 Bean
将自定义的 Manager 注册到 Spring 容器中,替换默认的 CacheManager。
java
@Bean
public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate,
RedisCacheConfiguration redisCacheConfiguration) {
// 1. 获取连接工厂
RedisConnectionFactory factory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
// 2. 创建无锁的 CacheWriter
// BatchStrategies.scan(1000) 是为了在使用 evict(allEntries=true) 时
// 使用 SCAN 命令代替 KEYS 命令,防止阻塞 Redis 主线程
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(factory,
BatchStrategies.scan(1000));
// 3. 返回自定义的 TimeoutRedisCacheManager
return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
}
三、 使用效果演示
完成上述配置后,开发人员在编写业务代码时,可以极其灵活地控制缓存时间:
场景 A:使用默认时间
java
// 不带后缀,使用 RedisCacheConfiguration 配置的全局默认时间(如 1 小时)
@Cacheable(value = "system_dict", key = "#type")
public List<Dict> getDictByType(String type) { ... }
- Redis Key :
system_dict:sex - TTL: 1 小时
场景 B:自定义短缓存
java
// 后缀 #60s,指定 60 秒过期
@Cacheable(value = "sms_code#60s", key = "#mobile")
public String getVerifyCode(String mobile) { ... }
- Redis Key :
sms_code:13800138000(注意:后缀被去除了) - TTL: 60 秒
场景 C:自定义长缓存
java
// 后缀 #1d,指定 1 天过期
@Cacheable(value = "daily_ranking#1d", key = "#date")
public Ranking getDailyRanking(String date) { ... }
- Redis Key :
daily_ranking:20231001 - TTL: 24 小时
深度定制 RedisCache 实现多级缓存与监控
在掌握了 RedisCacheConfiguration(定制属性)和 RedisCacheManager(定制策略)之后,我们来到了 Spring Cache 定制的"深水区"------自定义 RedisCache。
前两者的定制局限于"配置"层面(数据存什么格式、存多久),而自定义 RedisCache 则是直接介入缓存的运行时行为(Runtime Behavior)。
当你遇到以下需求时,必须重写 RedisCache:
- 多级缓存:在访问 Redis 前,先访问本地堆内存(Caffeine/HashMap),以减少网络 IO。
- 读写分离/双写:写入缓存时需要同步写入其他存储介质。
- 埋点监控:需要精确统计缓存的命中率、读取耗时,并上报监控系统(如 Prometheus)。
- 容错降级:当 Redis 宕机时,不抛出异常,而是记录日志并回源查询数据库。
本章将通过实现一个简单的 "内存 + Redis 二级缓存" 组件,展示如何重写 RedisCache 的核心生命周期。
一、 核心原理:RedisCache 的职责
org.springframework.data.redis.cache.RedisCache 是 Spring 官方提供的核心实现类。它继承自 AbstractValueAdaptingCache。
我们主要关注以下三个方法的重写:
lookup(Object key):对应@Cacheable的查询操作。底层核心。put(Object key, Object value):对应@Cacheable的写入和@CachePut。evict(Object key):对应@CacheEvict。
二、 代码实现:构建二级缓存 (Level2RedisCache)
我们将构建一个 Level2RedisCache。它的逻辑是:
- 读:先查 L1(本地内存),有则返回;无则查 L2(Redis);如果 Redis 有,则回填 L1。
- 写:同时写入 L1 和 L2。
- 删:同时删除 L1 和 L2。
1. 自定义 Cache 类
java
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 自定义二级缓存实现
* L1: JVM 本地内存 (使用 ConcurrentHashMap 模拟,生产建议使用 Caffeine)
* L2: Redis (使用父类逻辑)
*/
public class Level2RedisCache extends RedisCache {
// 一级缓存容器
private final Map<Object, Object> localCache = new ConcurrentHashMap<>();
// 统计指标(可选)
private final String name;
public Level2RedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {
super(name, cacheWriter, cacheConfig);
this.name = name;
}
/**
* 重写查询逻辑 (核心)
* 对应 @Cacheable 注解的读取操作
*/
@Override
protected Object lookup(Object key) {
// 1. 【L1】先检查本地内存
Object localValue = localCache.get(key);
if (localValue != null) {
// log.debug("L1 缓存命中: key={}", key);
return localValue;
}
// 2. 【L2】本地没有,调用父类方法查 Redis
// super.lookup() 内部会处理序列化和反序列化
Object redisValue = super.lookup(key);
// 3. 【回填】如果 Redis 查到了,回写到本地内存
if (redisValue != null) {
// log.debug("L2 缓存命中并回填 L1: key={}", key);
localCache.put(key, redisValue);
}
return redisValue;
}
/**
* 重写写入逻辑
* 对应 @Cacheable 的写入和 @CachePut
*/
@Override
public void put(Object key, Object value) {
// 1. 【L2】先写 Redis (保证远程数据的强一致性)
super.put(key, value);
// 2. 【L1】再写本地内存
// 注意:如果是集群环境,这里只更新了当前节点的 L1。
// 生产环境通常需要结合 Redis Pub/Sub 通知其他节点失效 L1。
if (value != null) {
localCache.put(key, value);
}
}
/**
* 重写删除逻辑
* 对应 @CacheEvict
*/
@Override
public void evict(Object key) {
// 1. 【L2】删 Redis
super.evict(key);
// 2. 【L1】删本地
localCache.remove(key);
}
/**
* 重写清空逻辑
* 对应 @CacheEvict(allEntries = true)
*/
@Override
public void clear() {
super.clear();
localCache.clear();
}
}
2. 在 Manager 中启用自定义 Cache
有了自定义的"工人"(Cache),我们需要在"工厂"(Manager)中雇佣它。
我们需要再次回到 RedisCacheManager 的 createRedisCache 方法,将返回值修改为我们的 Level2RedisCache。
java
public class MultiLevelCacheManager extends RedisCacheManager {
public MultiLevelCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
// 这里可以结合上一章的逻辑,先处理 cacheConfig 的 TTL
// ... (省略 TTL 解析逻辑) ...
// 【关键】返回自定义的 Level2RedisCache 实例
// 这里的 super.createRedisCache 只是为了复用父类的一些初始化检查,
// 但我们直接 new 自己的对象是最直接的。
return new Level2RedisCache(name, getCacheWriter(), cacheConfig);
}
}
三、 进阶扩展:添加监控埋点
除了做多级缓存,自定义 RedisCache 也是做应用层监控 的最佳切入点。我们可以在 lookup 方法中加入耗时统计。
java
@Override
protected Object lookup(Object key) {
long start = System.nanoTime();
try {
Object value = super.lookup(key);
recordMetrics(key, value != null, System.nanoTime() - start);
return value;
} catch (Exception e) {
// 容错降级:如果 Redis 挂了,记录日志,返回 null 让业务查库
// log.error("Redis 异常", e);
return null;
}
}
private void recordMetrics(Object key, boolean hit, long durationNs) {
// 伪代码:上报给 Prometheus 或 Micrometer
// Metrics.counter("cache.requests", "name", this.name, "hit", String.valueOf(hit)).increment();
// Metrics.timer("cache.latency", "name", this.name).record(durationNs, TimeUnit.NANOSECONDS);
}
除了上面讨论的 RedisCacheConfiguration (配置属性)、RedisCacheManager (管理策略) 和 RedisCache (运行时行为) 这"三驾马车"之外,Spring Data Redis 体系非常庞大,还存在其他几个关键层面的定制。
如果把之前的三个层面比作"装修房子",那么剩下的层面涉及 "地基加固" 、"进门锁匙" 和 "应急预案"。
以下是另外 4 个 常见且重要的高级定制层面:
第四层:AOP 辅助层定制 (KeyGenerator & ErrorHandler)
这层定制发生在缓存逻辑执行的 "前" 和 "后"。
1. Custom KeyGenerator (定制键生成器)
- 场景 :你厌倦了在每个
@Cacheable里写key = "#user.id"。或者你的入参很复杂(比如是一个大对象),你想用一种通用的算法(如 SHA256)生成 Key。 - 默认行为 :
SimpleKeyGenerator(使用参数的hashCode)。 - 如何定制 :实现
KeyGenerator接口。
java
@Bean("md5KeyGenerator")
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
// 自定义逻辑:拼接类名+方法名+参数MD5
return method.getName() + "_" + Arrays.toString(params);
};
}
// 使用:@Cacheable(keyGenerator = "md5KeyGenerator")
2. CacheErrorHandler (定制异常处理器)
- 场景 :这是生产环境的保命符 。默认情况下,如果 Redis 挂了(连接超时),
@Cacheable会抛出异常,导致整个接口报错(500 Error)。大多数时候,我们希望 "缓存挂了降级查库,不要影响主流程"。 - 如何定制 :实现
CacheErrorHandler。
java
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
@Override
public CacheErrorHandler errorHandler() {
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
// 仅打印日志,不抛出异常 -> 视为未命中,自动穿透到数据库
log.error("Redis get failed, key: {}", key, exception);
}
// ... handlePutError, handleEvictError, handleClearError
};
}
}
第五层:客户端驱动层定制 (LettuceClientConfiguration)
这层定制属于 "基础设施" 级别,涉及网络连接、线程模型和读写分离。它不在 Spring Cache 的逻辑里,而是在连接工厂(RedisConnectionFactory)的底层。
1. 读写分离策略 (ReadFrom)
- 场景 :你搭建了 Redis Sentinel(哨兵)或 Cluster(集群),主节点压力大,希望 "写主读从"。
- 如何定制 :通过
LettuceClientConfiguration。
java
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
// 优先从从节点读取,如果没有从节点则读主节点
.readFrom(ReadFrom.REPLICA_PREFERRED)
.build();
return new LettuceConnectionFactory(new RedisStandaloneConfiguration(), clientConfig);
}
2. Netty 线程池调优
- 场景:Redis 吞吐量极高,默认的 Lettuce 线程数(CPU 核心数)成为瓶颈,或者发生了 Netty 堆外内存溢出。
- 如何定制 :自定义
ClientResources。
第六层:操作模版层定制 (RedisTemplate)
虽然我们主要讨论 @Cacheable(基于注解),但很多业务场景需要直接操作 Redis(基于代码)。这时就需要定制 RedisTemplate。
1. 脚本执行 (Scripting)
- 场景:需要执行复杂的原子操作(如"扣减库存"或"限流算法"),Java 代码分步执行有并发问题。
- 定制点 :封装
DefaultRedisScript<T>,加载 Lua 脚本。
2. 流水线 (Pipeline)
- 场景 :需要一次性写入 10000 条数据,循环调用
put会产生 10000 次网络 RTT(往返时延)。 - 定制点 :使用
redisTemplate.executePipelined(...),将命令打包发送,批量执行。
第七层:事件监听层定制 (MessageListener)
Redis 不仅仅是存储,还是一个消息总线。
1. 键过期监听 (Keyspace Notifications)
- 场景:当订单支付超时(30分钟未支付),Redis Key 过期删除,你需要监听到这个事件来自动取消订单。
- 如何定制 :配置
RedisMessageListenerContainer并开启 Redis 的notify-keyspace-events配置。
java
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
// 然后监听 __keyevent@0__:expired 频道
Spring Data Redis 全景定制图
为了让你一眼看清整个架构,我总结了这 7 个层面:
| 层面 | 核心类/接口 | 作用域 | 典型应用场景 |
|---|---|---|---|
| 1. 全局配置层 | RedisCacheConfiguration |
属性规范 | 改 JSON 序列化、统一 Key 前缀 |
| 2. 管理策略层 | RedisCacheManager |
调度策略 | 动态过期时间 (#30m) |
| 3. 运行时行为层 | RedisCache |
执行逻辑 | 多级缓存、埋点监控 |
| 4. AOP 辅助层 | CacheErrorHandler |
容错/切面 | Redis 宕机自动降级、自定义 Key 生成 |
| 5. 驱动配置层 | LettuceClientConfiguration |
网络/连接 | 读写分离、连接池参数、Netty 调优 |
| 6. 操作模版层 | RedisTemplate |
直接操作 | Lua 脚本、Pipeline 批量操作 |
| 7. 事件监听层 | KeyExpirationEvent |
异步回调 | 监听 Key 过期事件处理业务逻辑 |
本文主要重点在于前三层定制,也是最常见的三层。
在面对需求时,你应该自上而下 进行考虑:能用上一层解决的,绝不轻易下沉到下一层,以保持系统的简洁性和可维护性。
核心决策图谱
第一层:RedisCacheConfiguration (配置层)
这是最顶层,也是改动成本最低的一层。它控制的是数据的静态属性。
- 核心定义 :定义缓存的 格式 (Format) 和 默认规则 (Defaults)。
- 适用场景:
- 修改序列化方式:不想用 JDK 的乱码二进制,想用 JSON 格式(通用需求)。
- 规范 Key 的命名 :把 Spring 默认的双冒号
::改成单冒号:,或者统一添加全局前缀(如app_name:)。 - 设置兜底 TTL:为了防止 Redis 内存爆炸,设置一个全局默认过期时间(如 1 小时)。
- 空值防御 :全局禁止缓存
null值。
一句话判别 :如果你想改变存入 Redis 的数据 "长什么样" ,或者设置 "默认存多久",选它。
第二层:RedisCacheManager (策略层)
这是中间层,它控制的是配置的分发策略。
- 核心定义 :根据 缓存名称 (CacheName) 的不同,动态应用不同的 Configuration。
- 适用场景:
- 差异化过期时间 :这是最经典的需求。需要支持
@Cacheable("code#5m")和@Cacheable("data#1d")共存。 - 差异化序列化 :极少数情况下,某些缓存想存 JSON,某些想存纯 String(
#raw),需要 Manager 识别后缀并切换配置。 - 多租户隔离:根据缓存名动态添加租户 ID 前缀。
一句话判别 :如果你的需求是 "看菜下碟"(即不同的业务缓存需要不同的过期时间或配置),选它。
第三层:RedisCache (行为层)
这是最底层,也是"深水区"。它控制的是**"数据的读写逻辑"**。
- 核心定义 :介入
get、put、evict的 运行时行为 (Runtime Behavior)。 - 适用场景:
- 多级缓存:这是最硬核的理由。引入 Caffeine 做 L1 缓存,Redis 做 L2 缓存。
- 埋点监控:需要精确统计每次 Redis IO 的耗时,并上报给 Prometheus/SkyWalking。
- 容错降级 :当 Redis 连接超时或宕机时,捕获异常并打印日志,而不是让整个接口报错(虽然
CacheErrorHandler也能做,但这里更灵活)。 - 读写分离/双写:写入缓存时,需要同步写入另一个存储介质。
一句话判别 :如果你需要 "改变流程" (比如读 Redis 前先读内存,或者读完 Redis 后发个消息),或者需要 "强行介入" 读写过程,选它。
决策速查表 (Cheat Sheet)
在实际开发中,对照下表进行选型:
| 你的需求 | 推荐方案 | 复杂度 |
|---|---|---|
| "我想让 Redis 里存的是 JSON,不是乱码" | RedisCacheConfiguration |
⭐ (低) |
| "我想把 Key 中间的双冒号 :: 去掉" | RedisCacheConfiguration |
⭐ (低) |
| "我想让所有缓存默认 1 小时过期" | RedisCacheConfiguration |
⭐ (低) |
| "我的验证码要 5分钟过期,排行榜要 1天过期" | RedisCacheManager (解析 #ttl) |
⭐⭐ (中) |
| "我想根据缓存名决定是否存 Null 值" | RedisCacheManager (解析 #nonull) |
⭐⭐ (中) |
| "Redis 太慢了,我想加一层本地内存缓存" | 自定义 RedisCache |
⭐⭐⭐ (高) |
| "我想知道 Redis 缓存的命中率和平均耗时" | 自定义 RedisCache |
⭐⭐⭐ (高) |
| "Redis 挂了不能报错,要自动去查数据库" | 自定义 RedisCache (或 ErrorHandler) |
⭐⭐⭐ (高) |
最佳实践建议:
对于绝大多数(90%)的 Spring Boot 业务系统,"JSON 序列化配置 (Layer 1) + 动态过期时间支持 (Layer 2)" 的组合是最完美的黄金搭档。除非你有明确的性能瓶颈需要多级缓存,否则尽量不要触碰 Layer 3,以降低系统的维护成本。