/* * Copyright 2017-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.redis.cache; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import org.jspecify.annotations.Nullable; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.util.Assert; /** * {@link RedisCacheWriter} provides low-level access to Redis commands ({@code SET, SETNX, GET, EXPIRE,...}) used for * caching. *
* The {@link RedisCacheWriter} may be shared by multiple cache implementations and is responsible for reading/writing * binary data from/to Redis. The implementation honors potential cache lock flags that might be set. *
* The default {@link RedisCacheWriter} implementation can be customized with {@link BatchStrategy} to tune performance
* behavior.
*
* @author Christoph Strobl
* @author Mark Paluch
* @author John Blum
* @since 2.0
*/
public interface RedisCacheWriter extends CacheStatisticsProvider {
/**
* Create new {@link RedisCacheWriter} configure it through {@link RedisCacheWriterConfigurer}. The cache writer
* defaults does not lock the cache by default using {@link BatchStrategies#keys()}.
*
* @param connectionFactory the connection factory to use.
* @param configurerConsumer a configuration function that configures {@link RedisCacheWriterConfigurer}.
* @return new instance of {@link DefaultRedisCacheWriter}.
* @since 4.0
*/
static RedisCacheWriter create(RedisConnectionFactory connectionFactory,
Consumer
* If possible (and configured for locking), implementations should ensure that the loading operation is synchronized
* so that the specified {@code valueLoader} is only called once in case of concurrent access on the same key.
*
* @param name must not be {@literal null}.
* @param key must not be {@literal null}.
* @param valueLoader value loader that creates the value if the cache lookup has been not successful.
* @param ttl {@link Duration} specifying the {@literal expiration timeout} for the cache entry.
* @param timeToIdleEnabled {@literal true} to enable Time to Idle when retrieving the value.
* @since 3.4
*/
default byte[] get(String name, byte[] key, Supplier
* The main factor for whether the {@literal retrieve} operation can be supported will primarily be determined by the
* Redis driver in use at runtime.
*
* Returns {@literal false} by default. This will have an effect of {@link RedisCache#retrieve(Object)} and
* {@link RedisCache#retrieve(Object, Supplier)} throwing an {@link UnsupportedOperationException}.
*
* @return {@literal true} if asynchronous {@literal retrieve} operations are supported by the implementation.
* @since 3.2
*/
default boolean supportsAsyncRetrieve() {
return false;
}
/**
* Asynchronously retrieves the {@link CompletableFuture value} to which the {@link RedisCache} maps the given
* {@code byte[] key}.
*
* This operation is non-blocking.
*
* @param name {@link String} with the name of the {@link RedisCache}.
* @param key {@code byte[] key} mapped to the {@link CompletableFuture value} in the {@link RedisCache}.
* @return the {@link CompletableFuture value} to which the {@link RedisCache} maps the given {@code byte[] key}.
* @see #retrieve(String, byte[], Duration)
* @since 3.2
*/
default CompletableFuture
* This operation is non-blocking.
*
* @param name {@link String} with the name of the {@link RedisCache}.
* @param key {@code byte[] key} mapped to the {@link CompletableFuture value} in the {@link RedisCache}.
* @param ttl {@link Duration} specifying the {@literal expiration timeout} for the cache entry.
* @return the {@link CompletableFuture value} to which the {@link RedisCache} maps the given {@code byte[] key}.
* @since 3.2
*/
CompletableFuture
* This operation is non-blocking.
*
* @param name cache name must not be {@literal null}.
* @param key key for the cache entry. Must not be {@literal null}.
* @param value value stored for the key. Must not be {@literal null}.
* @param ttl optional expiration time. Can be {@literal null}.
* @since 3.2
*/
CompletableFuture
* Actual eviction may be performed in an asynchronous or deferred fashion, with subsequent lookups possibly still
* seeing the entry.
*
* @param name cache name must not be {@literal null}.
* @param key key for the cache entry. Must not be {@literal null}.
* @deprecated since 4.0 in favor of {@link #evict(String, byte[])}
*/
@Deprecated(since = "4.0", forRemoval = true)
default void remove(String name, byte[] key) {
evict(name, key);
}
/**
* Remove the given key from Redis.
*
* Actual eviction may be performed in an asynchronous or deferred fashion, with subsequent lookups possibly still
* seeing the entry.
*
* @param name cache name must not be {@literal null}.
* @param key key for the cache entry. Must not be {@literal null}.
* @since 4.0
*/
void evict(String name, byte[] key);
/**
* Remove the given key from Redis if it is present, expecting the key to be immediately invisible for subsequent
* lookups.
*
* @param name cache name must not be {@literal null}.
* @param key key for the cache entry. Must not be {@literal null}.
* @return {@code true} if the cache was known to have a mapping for this key before, {@code false} if it did not (or
* if prior presence could not be determined).
* @since 4.0
*/
default boolean evictIfPresent(String name, byte[] key) {
evict(name, key);
return false;
}
/**
* Remove all keys following the given pattern.
*
* Actual clearing may be performed in an asynchronous or deferred fashion, with subsequent lookups possibly still
* seeing the entries.
*
* @param name cache name must not be {@literal null}.
* @param pattern pattern for the keys to remove. Must not be {@literal null}.
* @deprecated since 4.0 in favor of {@link #clear(String, byte[])}
*/
@Deprecated(since = "4.0", forRemoval = true)
default void clean(String name, byte[] pattern) {
clear(name, pattern);
}
/**
* Remove all keys following the given pattern.
*
* Actual clearing may be performed in an asynchronous or deferred fashion, with subsequent lookups possibly still
* seeing the entries.
*
* @param name cache name must not be {@literal null}.
* @param pattern pattern for the keys to remove. Must not be {@literal null}.
* @since 4.0
*/
void clear(String name, byte[] pattern);
/**
* Remove all keys following the given pattern expecting all entries to be immediately invisible for subsequent
* lookups.
*
* @param name cache name must not be {@literal null}.
* @param pattern pattern for the keys to remove. Must not be {@literal null}.
* @return {@code true} if the cache was known to have mappings before, {@code false} if it did not (or if prior
* presence of entries could not be determined).
*/
default boolean invalidate(String name, byte[] pattern) {
clear(name, pattern);
return false;
}
/**
* Reset all statistics counters and gauges for this cache.
*
* @since 2.4
*/
void clearStatistics(String name);
/**
* Executes the given {@link Function} with a {@link RedisConnection}.
*
* @param callback the callback action to invoke with a connection.
* @return return value of the callback.
* @param
* If no statistics collector is specified, no statistics will be recorded. Statistics collection can be
* reconfigured on the built RedisCacheWriter by invoking
* {@code RedisCacheWriter#withStatisticsCollector(CacheStatisticsCollector)}.
*
* @param cacheStatisticsCollector the statistics collector to use.
*/
RedisCacheWriterConfigurer collectStatistics(CacheStatisticsCollector cacheStatisticsCollector);
/**
* Configure the {@link BatchStrategy} when clearing the cache (i.e. bulk removal of cache keys).
*
* If no batch strategy is specified, the RedisCacheWriter uses {@link BatchStrategies#keys()};
*
* @param batchStrategy the batch strategy to use.
*/
RedisCacheWriterConfigurer batchStrategy(BatchStrategy batchStrategy);
/**
* Enable cache locking to synchronize cache access across multiple cache instances.
*/
default RedisCacheWriterConfigurer enableLocking() {
return cacheLocking(it -> it.enable(config -> {}));
}
/**
* Enable cache locking to synchronize cache access across multiple cache instances.
*
* @param configurerConsumer a configuration function that configures {@link CacheLockingConfiguration}.
*/
default RedisCacheWriterConfigurer enableLocking(Consumer
* Several {@link org.springframework.cache.Cache} operations can be performed asynchronously or deferred and this
* is the default behavior for {@link RedisCacheWriter}. Enable immediate writes in case a particular cache requires
* stronger consistency (i.e. Cache writes must be visible immediately).
*
* When using a {@link org.springframework.data.redis.connection.ReactiveRedisConnectionFactory reactive Redis
* driver}, immediate writes lead to blocking.
*/
default RedisCacheWriterConfigurer immediateWrites() {
return immediateWrites(true);
}
/**
* Configure whether to use immediate writes (i.e. write operations such as
* {@link RedisCacheWriter#put(String, byte[], byte[], Duration)} or {@link #clear(String, byte[])}) shall apply
* immediately.
*
* Several {@link org.springframework.cache.Cache} operations can be performed asynchronously or deferred and this
* is the default behavior for {@link RedisCacheWriter}. Enable immediate writes in case a particular cache requires
* stronger consistency (i.e. Cache writes must be visible immediately).
*
* When using a {@link org.springframework.data.redis.connection.ReactiveRedisConnectionFactory reactive Redis
* driver}, immediate writes lead to blocking.
*
* @param enableImmediateWrites whether write operations must be visible immediately.
*/
RedisCacheWriterConfigurer immediateWrites(boolean enableImmediateWrites);
}
/**
* Interface that allows for configuring cache locking.
*
* @author Mark Paluch
* @since 4.0
*/
interface CacheLockingConfigurer {
/**
* Disable cache locking (default).
*/
void disable();
/**
* Enable cache locking with a default sleep time of {@code 50 milliseconds} and persistent lock keys.
*/
default void enable() {
enable(it -> {});
}
/**
* Enable cache locking.
*/
void enable(Consumer
* If no TTL function is specified, the RedisCacheWriter persistent lock keys. Persistent lock keys need to be
* removed in case of failures (e.g. Redis crashes before a lock key is removed). Expiring lock keys can become
* subject to GC timing if lock keys expire while a garbage collection halts the JVM.
*
* @param ttlFunction the lock timeout function.
*/
CacheLockingConfiguration lockTimeout(TtlFunction ttlFunction);
}
/**
* Function to compute the time to live from the cache {@code key} and {@code value}.
*
* @author Mark Paluch
* @since 3.2
*/
@FunctionalInterface
interface TtlFunction {
Duration NO_EXPIRATION = Duration.ZERO;
/**
* Creates a {@literal Singleton} {@link TtlFunction} using the given {@link Duration}.
*
* @param duration the time to live. Can be {@link Duration#ZERO} for persistent values (i.e. cache entry does not
* expire).
* @return a singleton {@link TtlFunction} using {@link Duration}.
*/
static TtlFunction just(Duration duration) {
Assert.notNull(duration, "TTL Duration must not be null");
return new FixedDurationTtlFunction(duration);
}
/**
* Returns a {@link TtlFunction} to create persistent entries that do not expire.
*
* @return a {@link TtlFunction} to create persistent entries that do not expire.
*/
static TtlFunction persistent() {
return just(NO_EXPIRATION);
}
/**
* Compute a {@link Duration time-to-live (TTL)} using the cache {@code key} and {@code value}.
*
* The {@link Duration time-to-live (TTL)} is computed on each write operation. Redis uses millisecond granularity
* for timeouts. Any more granular values (e.g. micros or nanos) are not considered and will be truncated due to
* rounding. Returning {@link Duration#ZERO}, or a value less than {@code Duration.ofMillis(1)}, results in a
* persistent value that does not expire.
*
* @param key the cache key.
* @param value the cache value. Can be {@literal null} if the cache supports {@literal null} value caching.
* @return the computed {@link Duration time-to-live (TTL)}. Can be {@link Duration#ZERO} for persistent values
* (i.e. cache entry does not expire).
*/
Duration getTimeToLive(Object key, @Nullable Object value);
}
}