跳到主要内容

Redis 缓存

DeepSeek V3 中英对照 Redis Cache

Spring Data Redis 在 org.springframework.data.redis.cache 包中提供了 Spring 框架的缓存抽象的实现。要将 Redis 作为后备实现,请将 RedisCacheManager 添加到您的配置中,如下所示:

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager.create(connectionFactory);
}
java

RedisCacheManager 的行为可以通过 RedisCacheManager.RedisCacheManagerBuilder 进行配置,允许你设置默认的 RedisCacheManager、事务行为以及预定义的缓存。

RedisCacheManager cacheManager = RedisCacheManager.builder(connectionFactory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
.transactionAware()
.withInitialCacheConfigurations(Collections.singletonMap("predefined",
RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()))
.build();
java

如前面的示例所示,RedisCacheManager 允许在每个缓存的基础上进行自定义配置。

RedisCacheManager 创建的 RedisCache 的行为通过 RedisCacheConfiguration 进行定义。该配置允许您设置键的过期时间、前缀以及用于与二进制存储格式之间进行转换的 RedisSerializer 实现,如下例所示:

RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(1))
.disableCachingNullValues();
java

RedisCacheManager 默认使用无锁的 RedisCacheWriter 来读写二进制值。无锁缓存提高了吞吐量。然而,缺乏条目锁定可能会导致 CacheputIfAbsentclean 操作出现重叠的非原子命令,因为这些操作需要向 Redis 发送多个命令。加锁的版本通过设置显式的锁键并检查该键的存在来防止命令重叠,但这会导致额外的请求和潜在的命令等待时间。

锁定应用于缓存级别,而不是每个缓存项

可以通过以下方式选择启用锁定行为:

RedisCacheManager cacheManager = RedisCacheManager
.builder(RedisCacheWriter.lockingRedisCacheWriter(connectionFactory))
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
...
java

默认情况下,任何缓存条目的 key 都会以实际的缓存名称作为前缀,后跟两个冒号(::)。此行为可以更改为静态前缀或计算前缀。

以下示例展示了如何设置静态前缀:

// static key prefix
RedisCacheConfiguration.defaultCacheConfig().prefixCacheNameWith("(͡° ᴥ ͡°)");

The following example shows how to set a computed prefix:

// computed key prefix
RedisCacheConfiguration.defaultCacheConfig()
.computePrefixWith(cacheName -> "¯\_(ツ)_/¯" + cacheName);
java

缓存实现默认使用 KEYSDEL 来清除缓存。KEYS 在键空间较大时可能会导致性能问题。因此,默认的 RedisCacheWriter 可以通过 BatchStrategy 创建,以切换到基于 SCAN 的批量策略。SCAN 策略需要指定批量大小,以避免过多的 Redis 命令往返:

RedisCacheManager cacheManager = RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, BatchStrategies.scan(1000)))
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
...
java
备注

KEYS 批量策略在使用任何驱动程序和 Redis 操作模式(单机模式、集群模式)时都完全支持。使用 Lettuce 驱动程序时,SCAN 完全支持。Jedis 仅在非集群模式下支持 SCAN

下表列出了 RedisCacheManager 的默认设置:

表 1. RedisCacheManager 默认值

设置项
缓存写入器非锁定,KEYS 批量策略
缓存配置RedisCacheConfiguration#defaultConfiguration
初始缓存
事务感知

下表列出了 RedisCacheConfiguration 的默认设置:

表 2. RedisCacheConfiguration 默认配置

键过期时间
缓存 null
前缀键
默认前缀实际的缓存名称
键序列化器StringRedisSerializer
值序列化器JdkSerializationRedisSerializer
转换服务带有默认缓存键转换器的 DefaultFormattingConversionService
备注

默认情况下,RedisCache 的统计功能是关闭的。使用 RedisCacheManagerBuilder.enableStatistics() 来收集本地的 命中未命中 数据,并通过 RedisCache#getStatistics() 返回收集到的数据快照。

Redis 缓存过期 {#redis:support:cache-abstraction:expiration}

时间空闲(TTI)和生存时间(TTL)的实现,在定义和行为上因不同的数据存储系统而异。

一般来说:

  • 生存时间(TTL)过期 - TTL 仅在创建或更新数据访问操作时设置和重置。只要在 TTL 过期超时之前写入条目(包括创建时),条目的超时将重置为配置的 TTL 过期超时持续时间。例如,如果 TTL 过期超时设置为 5 分钟,则在条目创建时超时将设置为 5 分钟,并且在此后的任何更新操作中(在 5 分钟间隔到期之前),超时将重置为 5 分钟。如果在 5 分钟内没有发生更新,即使条目被读取了多次,甚至在 5 分钟间隔内只读取了一次,条目仍然会过期。在声明 TTL 过期策略时,必须写入条目以防止条目过期。

  • 空闲时间(TTI)过期 - TTI 在条目被读取或更新时都会重置,实际上是 TTL 过期策略的扩展。

备注

某些数据存储会在配置 TTL 时使条目过期,无论对该条目执行何种类型的数据访问操作(读取、写入或其他操作)。在设置的配置 TTL 过期超时后,无论如何都会将该条目从数据存储中逐出。逐出操作(例如:销毁、失效、溢出到磁盘(对于持久存储)等)是特定于数据存储的。

生存时间 (TTL) 过期 {#redis:support:cache-abstraction:expiration:tti}

Spring Data Redis 的 Cache 实现支持缓存条目的 生存时间 (TTL) 过期机制。用户可以通过提供一个固定 Duration 来配置 TTL 过期时间,或者通过实现新的 RedisCacheWriter.TtlFunction 接口为每个缓存条目动态计算 Duration

提示

RedisCacheWriter.TtlFunction 接口在 Spring Data Redis 3.2.0 版本中引入。

如果所有缓存条目应该在设定的时间后过期,那么只需配置一个固定的 Duration 作为 TTL 过期超时,如下所示:

RedisCacheConfiguration fiveMinuteTtlExpirationDefaults =
RedisCacheConfiguration.defaultCacheConfig().enableTtl(Duration.ofMinutes(5));
java

然而,如果 TTL 过期超时需要根据缓存条目而变化,那么你必须提供 RedisCacheWriter.TtlFunction 接口的自定义实现:

enum MyCustomTtlFunction implements TtlFunction {

INSTANCE;

@Override
public Duration getTimeToLive(Object key, @Nullable Object value) {
// compute a TTL expiration timeout (Duration) based on the cache entry key and/or value
}
}
java
备注

在底层,固定的 Duration TTL 过期时间被包装在一个 TtlFunction 实现中,该实现返回提供的 Duration

然后,你可以使用以下方式全局配置固定的 Duration 或每个缓存条目的动态 Duration TTL 过期时间:

RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(fiveMinuteTtlExpirationDefaults)
.build();
java

或者,也可以:

RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(MyCustomTtlFunction.INSTANCE);

RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaults)
.build();
java

当然,你可以使用以下方式结合全局和每个缓存的配置:

RedisCacheConfiguration predefined = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(MyCustomTtlFunction.INSTANCE);

Map<String, RedisCacheConfiguration> initialCaches = Collections.singletonMap("predefined", predefined);

RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(fiveMinuteTtlExpirationDefaults)
.withInitialCacheConfigurations(initialCaches)
.build();
java

空闲时间过期 (TTI) {#redis:support:cache-abstraction:expiration:tti2}

Redis 本身并不支持真正的空闲时间(Time-to-Idle, TTI)过期概念。不过,通过使用 Spring Data Redis 的缓存实现,可以实现类似于空闲时间(TTI)过期的行为。

在 Spring Data Redis 的缓存实现中,TTI(Time To Idle)的配置必须显式启用,即需要手动开启。此外,你还必须提供 TTL(Time To Live)配置,可以使用固定的 Duration 或如上文 Redis 缓存过期 中所述的自定义 TtlFunction 接口实现。

例如:

@Configuration
@EnableCaching
class RedisConfiguration {

@Bean
RedisConnectionFactory redisConnectionFactory() {
// ...
}

@Bean
RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {

RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.enableTimeToIdle();

return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(defaults)
.build();
}
}
java

由于 Redis 服务器没有实现真正的 TTI(Time-To-Idle)概念,因此只能通过支持过期选项的 Redis 命令来实现 TTI。在 Redis 中,"过期"在技术上是基于生存时间(TTL)的策略。然而,当读取键的值时,TTL 过期可能会被重置,从而有效地重新设置 TTL 过期时间,正如现在在 Spring Data Redis 的 Cache.get(key) 操作中所实现的那样。

RedisCache.get(key) 是通过调用 Redis 的 GETEX 命令来实现的。

注意

Redis 的 GETEX 命令仅在 Redis 版本 6.2.0 及更高版本中可用。因此,如果你使用的不是 Redis 6.2.0 或更高版本,则无法使用 Spring Data Redis 的 TTI 过期功能。如果你在不兼容的 Redis(服务器)版本上启用了 TTI,将会抛出命令执行异常。系统不会主动检查 Redis 服务器版本是否正确以及是否支持 GETEX 命令。

注意

为了在 Spring Data Redis 应用程序中实现真正的空闲时间失效(Time-to-Idle, TTI)行为,必须在每次读取或写入操作时以生存时间(Time-to-Live, TTL)失效的方式一致地访问条目。此规则没有例外。如果在 Spring Data Redis 应用程序中混合使用了不同的数据访问模式(例如:缓存、使用 RedisTemplate 调用操作,或者特别是在使用 Spring Data Repository 的 CRUD 操作时),则访问条目可能无法阻止其失效(如果设置了 TTL 失效)。例如,可能在 @Cacheable 服务方法调用期间将条目“放入”(写入)缓存,并设置了 TTL 失效(即 SET <expiration options>),然后在失效超时之前使用 Spring Data Redis Repository 读取该条目(使用不带失效选项的 GET)。简单的 GET 操作(未指定失效选项)不会重置条目的 TTL 失效超时。因此,即使刚刚读取了条目,它也可能在下一次数据访问操作之前失效。由于 Redis 服务器无法强制执行此行为,因此在配置了空闲时间失效的情况下,应用程序有责任在适当的情况下(无论是否在缓存中)一致地访问条目。