Redis Cache

Spring Data Redis 在 org.springframework.data.redis.cache 包中提供了 Spring 框架 {spring-framework-docs}/integration.html#cache[缓存抽象] 的实现。要将 Redis 用作支持实现,请将 RedisCacheManager 添加到您的配置中,如下所示:

Spring Data Redis provides an implementation of Spring Framework’s {spring-framework-docs}/integration.html#cache[Cache Abstraction] in the org.springframework.data.redis.cache package. To use Redis as a backing implementation, add RedisCacheManager to your configuration, as follows:

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

可以使用“RedisCacheManagerBuilder”配置“RedisCacheManager”行为,让您设置默认的“RedisCacheConfiguration”、“transaction”行为和预定义缓存。

RedisCacheManager behavior can be configured with RedisCacheManagerBuilder, letting you set the default RedisCacheConfiguration, transaction behavior, and predefined caches.

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

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

As shown in the preceding example, RedisCacheManager allows custom configuration on a per-cache basis.

“RedisCacheManager”创建的“RedisCache”的行为用“RedisCacheConfiguration”定义。此配置让您可以设置键到期时间、前缀和“RedisSerializer”实现,以用于转换为二进制存储格式和从此格式转换,如下面的示例所示:

The behavior of RedisCache created by RedisCacheManager is defined with RedisCacheConfiguration. The configuration lets you set key expiration times, prefixes, and RedisSerializer implementations for converting to and from the binary storage format, as shown in the following example:

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

“RedisCacheManager”默认为无锁“RedisCacheWriter”,用于读取和写入二进制值。无锁缓存提高了吞吐量。缺乏条目锁定可能导致“Cache”的“putIfAbsent”和“clean”操作的重叠、非原子命令,因为这些命令需要向 Redis 发送多个命令。锁定对应的操作通过设置显式锁定密钥并根据此密钥的存在进行检查来防止命令重叠,它导致额外的请求和潜在的命令等待时间。

RedisCacheManager defaults to a lock-free RedisCacheWriter for reading and writing binary values. Lock-free caching improves throughput. The lack of entry locking can lead to overlapping, non-atomic commands for the Cache putIfAbsent and clean operations, as those require multiple commands to be sent to Redis. The locking counterpart prevents command overlap by setting an explicit lock key and checking against presence of this key, which leads to additional requests and potential command wait times.

锁定应用于*缓存级别*,而不是每个*缓存条目*。

Locking applies on the cache level, not per cache entry.

可以通过以下方式选择插入锁定行为:

It is possible to opt in to the locking behavior as follows:

RedisCacheManager cacheMangager = RedisCacheManager
    .build(RedisCacheWriter.lockingRedisCacheWriter(connectionFactory))
    .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
    ...

默认情况下,缓存条目的任何“密钥”都以实际缓存名称开头,后跟两个冒号(“::”)。此行为可以更改为静态前缀以及计算前缀。

By default, any key for a cache entry gets prefixed with the actual cache name followed by two colons (::). This behavior can be changed to a static as well as a computed prefix.

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

The following example shows how to set a static prefix:

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

The following example shows how to set a computed prefix:

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

缓存实现默认为使用“KEYS”和“DEL”来清除缓存。“KEYS”可能会在大键空间中造成性能问题。因此,默认“RedisCacheWriter”可以使用“BatchStrategy”创建,以切换到基于“SCAN”的批处理策略。“SCAN”策略需要一个批处理大小,以避免过多的 Redis 命令往返:

The cache implementation defaults to use KEYS and DEL to clear the cache. KEYS can cause performance issues with large keyspaces. Therefore, the default RedisCacheWriter can be created with a BatchStrategy to switch to a SCAN-based batch strategy. The SCAN strategy requires a batch size to avoid excessive Redis command round trips:

RedisCacheManager cacheManager = RedisCacheManager
    .build(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, BatchStrategies.scan(1000)))
    .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
    ...

“KEYS”批处理策略完全支持使用任何驱动程序和 Redis 操作模式(独立、群集)。当使用 Lettuce 驱动程序时,完全支持“SCAN”。Jedis 仅在非群集模式下支持“SCAN”。

The KEYS batch strategy is fully supported using any driver and Redis operation mode (Standalone, Clustered). SCAN is fully supported when using the Lettuce driver. Jedis supports SCAN only in non-clustered modes.

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

The following table lists the default settings for RedisCacheManager:

Table 1. RedisCacheManager defaults
Setting Value

Cache Writer

Non-locking, KEYS batch strategy

Cache Configuration

RedisCacheConfiguration#defaultConfiguration

Initial Caches

None

Transaction Aware

No

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

The following table lists the default settings for RedisCacheConfiguration:

Table 2. RedisCacheConfiguration defaults
Key Expiration None

Cache null

Yes

Prefix Keys

Yes

Default Prefix

The actual cache name

Key Serializer

StringRedisSerializer

Value Serializer

JdkSerializationRedisSerializer

Conversion Service

DefaultFormattingConversionService with default cache key converters

默认情况下,禁用“RedisCache”统计信息。使用“RedisCacheManagerBuilder.enableStatistics()”通过“RedisCache#getStatistics()”收集本地命中和未命中,返回收集的数据的快照。

By default RedisCache, statistics are disabled. Use RedisCacheManagerBuilder.enableStatistics() to collect local hits and misses through RedisCache#getStatistics(), returning a snapshot of the collected data.

Redis Cache Expiration

即使在不同的数据存储之间,空闲时间 (TTI) 以及生存时间 (TTL) 的实现也在定义和行为上有所不同。

The implementation of time-to-idle (TTI) as well as time-to-live (TTL) varies in definition and behavior even across different data stores.

一般情况下:

In general:

  • time-to-live (TTL) expiration - TTL 仅通过创建或更新数据访问操作设置和重置。只要在 TTL 过期超时之前写入该项,包括在创建时,该项的超时将重置为 TTL 过期超时的配置持续时间。例如,如果 TTL 过期超时设置为 5 分钟,那么该超时将在条目创建时设置为 5 分钟,并在随后的 5 分钟间隔期内每次更新条目时重置为 5 分钟。如果在 5 分钟内未更新,即使在 5 分钟间隔内多次读取或甚至只读取一次,该条目仍然会过期。必须写入条目以防止在声明 TTL 过期策略时条目过期。

  • time-to-live (TTL) expiration - TTL is only set and reset by a create or update data access operation. As long as the entry is written before the TTL expiration timeout, including on creation, an entry’s timeout will reset to the configured duration of the TTL expiration timeout. For example, if the TTL expiration timeout is set to 5 minutes, then the timeout will be set to 5 minutes on entry creation and reset to 5 minutes anytime the entry is updated thereafter and before the 5-minute interval expires. If no update occurs within 5 minutes, even if the entry was read several times, or even just read once during the 5-minute interval, the entry will still expire. The entry must be written to prevent the entry from expiring when declaring a TTL expiration policy.

  • time-to-idle (TTI) expiration - 每次读取条目以及更新条目时,TTI 都会重置,并且实际上是 TTL 过期策略的扩展。

  • time-to-idle (TTI) expiration - TTI is reset anytime the entry is also read as well as for entry updates, and is effectively and extension to the TTL expiration policy.

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

Some data stores expire an entry when TTL is configured no matter what type of data access operation occurs on the entry (reads, writes, or otherwise). After the set, configured TTL expiration timeout, the entry is evicted from the data store regardless. Eviction actions (for example: destroy, invalidate, overflow-to-disk (for persistent stores), etc.) are data store specific.

Time-To-Live (TTL) Expiration

Spring Data Redis 的“Cache”实现支持缓存条目上的生存时间 (TTL) 过期。用户可以通过一个固定的“持续时间”或通过提供新的“RedisCacheWriter.TtlFunction”界面的实现为每个缓存条目配置动态计算的“持续时间”来配置 TTL 过期超时。

Spring Data Redis’s Cache implementation supports time-to-live (TTL) expiration on cache entries. Users can either configure the TTL expiration timeout with a fixed Duration or a dynamically computed Duration per cache entry by supplying an implementation of the new RedisCacheWriter.TtlFunction interface.

“RedisCacheWriter.TtlFunction”界面是在 Spring Data Redis“3.2.0”中引入的。

The RedisCacheWriter.TtlFunction interface was introduced in Spring Data Redis 3.2.0.

如果所有缓存条目都应在设定的持续时间后过期,那么只需使用一个固定的“持续时间”配置 TTL 过期超时,如下所示:

If all cache entries should expire after a set duration of time, then simply configure a TTL expiration timeout with a fixed Duration, as follows:

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

但是,如果 TTL 过期超时应因缓存条目而异,那么你必须提供“RedisCacheWriter.TtlFunction”界面的自定义实现:

However, if the TTL expiration timeout should vary by cache entry, then you must provide a custom implementation of the RedisCacheWriter.TtlFunction interface:

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
    }
}

计算下,一个固定的“持续时间”TTL 过期将包装在“TtlFunction”实现中,返回提供的“持续时间”。

Under-the-hood, a fixed Duration TTL expiration is wrapped in a TtlFunction implementation returning the provided Duration.

然后,你可以使用以下方式在全局基础上配置固定的“持续时间”或动态的按缓存条目“持续时间”TTL 过期:

Then, you can either configure the fixed Duration or the dynamic, per-cache entry Duration TTL expiration on a global basis using:

Global fixed Duration TTL expiration timeout
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
    .cacheDefaults(fiveMinuteTtlExpirationDefaults)
    .build();

或者,也可以选择以下方式:

Or, alternatively:

Global, dynamically computed per-cache entry Duration TTL expiration timeout
RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(MyCustomTtlFunction.INSTANCE);

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

当然,你可以使用以下方式组合全局和按缓存配置:

Of course, you can combine both global and per-cache configuration using:

Global fixed Duration TTL expiration timeout
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();

Time-To-Idle (TTI) Expiration

Redis 本身不支持真实空闲时间 (TTI) 过期的概念。不过,使用 Spring Data Redis 的 Cache 实现,可以实现类似空闲时间 (TTI) 过期行为。

Redis itself does not support the concept of true, time-to-idle (TTI) expiration. Still, using Spring Data Redis’s Cache implementation, it is possible to achieve time-to-idle (TTI) expiration-like behavior.

Spring Data Redis 缓存实现中的 TTI 配置必须显式启用,即为选择加入。此外,你还必须使用固定的 DurationTtlFunction 接口的自定义实现来提供 TTL 配置,如前文 [Redis 缓存到期时间,redis:support:cache-abstraction:expiration] 所述。

The configuration of TTI in Spring Data Redis’s Cache implementation must be explicitly enabled, that is, is opt-in. Additionally, you must also provide TTL configuration using either a fixed Duration or a custom implementation of the TtlFunction interface as described above in redis:support:cache-abstraction:expiration.

例如:

For example:

@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();
    }
}

由于 Redis 服务器尚未实现正确的 TTI 概念,因此 TTI 只能使用接受到期选项的 Redis 命令实现。在 Redis 中,“到期”在技术上是生存时间 (TTL) 策略。但是,可以在读取密钥值时传递 TTL 到期时间,从而有效地重置 TTL 到期时间超时,就像现在 Spring Data Redis 的 Cache.get(key) 操作的情况一样。

Because Redis servers do not implement a proper notion of TTI, then TTI can only be achieved with Redis commands accepting expiration options. In Redis, the "expiration" is technically a time-to-live (TTL) policy. However, TTL expiration can be passed when reading the value of a key thereby effectively resetting the TTL expiration timeout, as is now the case in Spring Data Redis’s Cache.get(key) operation.

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

RedisCache.get(key) is implemented by calling the Redis GETEX command.

Redis GETEX 命令仅在 Redis 6.2.0 及更高版本中可用。因此,如果您未使用 Redis 6.2.0 或更高版本,则无法使用 Spring Data Redis 的 TTI 过期。如果您针对不兼容的 Redis(服务器)版本启用 TTI,则将抛出命令执行异常。不尝试确定 Redis 服务器版本是否正确且支持 GETEX 命令。

The Redis GETEX command is only available in Redis version 6.2.0 and later. Therefore, if you are not using Redis 6.2.0 or later, then it is not possible to use Spring Data Redis’s TTI expiration. A command execution exception will be thrown if you enable TTI against an incompatible Redis (server) version. No attempt is made to determine if the Redis server version is correct and supports the GETEX command.

为了在你的 Spring Data Redis 应用程序中实现真正的空闲时间到期 (TTI) 时间到期行为,必须在每次读取或写入操作时持续访问具有 (TTL) 到期时间的条目。此规则没有例外。如果你在 Spring Data Redis 应用程序中混合和匹配了不同的数据访问模式(例如:缓存、使用 RedisTemplate 调用操作,以及可能或特别是在使用 Spring Data Repository CRUD 操作时),则访问某个条目不一定可以防止该条目过期(如果设置了 TTL 到期时间)。例如,在 @Cacheable 服务方法调用期间,一个条目可能会“被放入”(写入)缓存,并带有 TTL 到期时间(即 SET <expiration options>),然后在到期时间超时之前使用 Spring Data Redis Repository 读取(使用 GET,不带到期选项)。不指定到期选项的简单 GET 不会重置条目上的 TTL 到期时间超时。因此,此条目在下次数据访问操作之前可能过期,即使它刚刚被读取过。由于无法在 Redis 服务器中强制执行此项操作,因此如果配置了空闲时间到期时间,则你的应用程序有责任在缓存内外(视情况而定)始终访问一个条目。

In order to achieve true time-to-idle (TTI) expiration-like behavior in your Spring Data Redis application, then an entry must be consistently accessed with (TTL) expiration on every read or write operation. There are no exceptions to this rule. If you are mixing and matching different data access patterns across your Spring Data Redis application (for example: caching, invoking operations using RedisTemplate and possibly, or especially when using Spring Data Repository CRUD operations), then accessing an entry may not necessarily prevent the entry from expiring if TTL expiration was set. For example, an entry maybe "put" in (written to) the cache during a @Cacheable service method invocation with a TTL expiration (i.e. SET <expiration options>) and later read using a Spring Data Redis Repository before the expiration timeout (using GET without expiration options). A simple GET without specifying expiration options will not reset the TTL expiration timeout on an entry. Therefore, the entry may expire before the next data access operation, even though it was just read. Since this cannot be enforced in the Redis server, then it is the responsibility of your application to consistently access an entry when time-to-idle expiration is configured, in and outside of caching, where appropriate.