introduction
Caching plays a crucial role in modern highly concurrent distributed systems. Spring Data Redis provides a powerful cache abstraction layer, allowing developers to easily integrate Redis caches in their applications. This article will explore in-depth how to customize the serialization mechanism and expiration strategies of Redis cache, helping developers solve key issues such as cached data consistency, memory usage and access efficiency. By rationally configuring Spring Cache annotations and RedisCache implementation, it can significantly improve application performance and reduce database pressure.
1. Basics of integration between Spring Cache and Redis
Spring Cache is a cache abstraction provided by the Spring framework, which allows developers to define cache behavior in a declarative manner without writing the underlying cache logic. Combining Redis as a cache provider, a high-performance distributed cache system can be built. Spring Cache supports a variety of annotations, such as @Cacheable, @CachePut, @CacheEvict, etc., which are used to cache query results, update caches and delete caches respectively. Redis's high performance and rich data structure make it an ideal cache storage option.
import ; import ; import ; @SpringBootApplication @EnableCaching // Enable Spring caching supportpublic class RedisCacheApplication { public static void main(String[] args) { (, args); } }
2. Redis cache configuration basics
Configuring Redis Cache requires creating a RedisCacheManager and defining basic cache properties. RedisCacheManager is responsible for creating and managing RedisCache instances, and RedisCache implements Spring's Cache interface. The basic configuration includes setting the Redis connection factory, default expiration time, cache name prefix, etc. RedisCacheConfiguration allows you to customize serialization methods, expiration policies, key prefixes, etc. These configurations have a direct impact on cache performance and availability.
import ; import ; import ; import ; import ; import ; @Configuration public class RedisCacheConfig { @Bean public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { // Create the default Redis cache configuration RedisCacheConfiguration config = () // Set the cache validity period to 1 hour .entryTtl((1)) // Set key prefix .prefixCacheNameWith("app:cache:"); return (connectionFactory) .cacheDefaults(config) .build(); } }
3. Customize serialization strategy
By default, Spring Data Redis uses JDK serialization, which has problems such as low efficiency, large space and poor readability. Custom serialization strategies can significantly improve these problems. Commonly used serialization methods include JSON, ProtoBuf, Kryo, etc. Among them, JSON serialization is easy to debug but has average performance, while ProtoBuf and Kryo provide higher performance and smaller storage space. Choosing the right serialization method requires a trade-off between performance, space efficiency, and readability.
import ; import ; import ; import ; import ; import ; import .GenericJackson2JsonRedisSerializer; import ; import ; @Configuration public class RedisSerializerConfig { @Bean public RedisCacheConfiguration cacheConfiguration() { // Create a custom ObjectMapper for JSON serialization ObjectMapper mapper = new ObjectMapper(); // Enable type information to ensure that object types can be restored correctly during deserialization ( , .NON_FINAL, ); // Create a Redis serializer based on Jackson GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(mapper); // Configure Redis cache to use String serializer to process keys, and JSON serializer to process values return () .serializeKeysWith( ( new StringRedisSerializer())) .serializeValuesWith( ( jsonSerializer)); } }
4. Implement a custom serializer
In some scenarios, the serializer provided by Spring may not meet specific needs, and a custom serializer needs to be implemented. Custom serializers need to implement the RedisSerializer interface, overriding the serialize and deserialize methods. Through a custom serializer, you can achieve efficient serialization of specific objects, or add additional security measures to serialization, such as encryption and decryption. When implementing, you need to pay attention to handling serialization exceptions and null values.
import ; import ; import ; import ; import ; import ; public class KryoRedisSerializer<T> implements RedisSerializer<T> { private final Class<T> clazz; private static final ThreadLocal<Kryo> kryoThreadLocal = (() -> { Kryo kryo = new Kryo(); // Configure Kryo instance (false); // Registered class is not required return kryo; }); public KryoRedisSerializer(Class<T> clazz) { = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } Kryo kryo = (); try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); Output output = new Output(baos)) { (output, t); (); return (); } catch (Exception e) { throw new SerializationException("Error serializing object using Kryo", e); } } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || == 0) { return null; } Kryo kryo = (); try (Input input = new Input(bytes)) { return (input, clazz); } catch (Exception e) { throw new SerializationException("Error deserializing object using Kryo", e); } } }
5. Multi-level cache configuration
In practical applications, different cache policies are often required for different types of data. Spring Cache supports defining multiple caches, each cache can have an independent configuration. The RedisCacheManagerBuilderCustomizer can be customized for different cache names, such as setting different expiration times, serialization methods and prefix policies. Multi-level caching configuration can optimize cache performance based on business characteristics.
import ; import ; import ; import ; import ; import ; import ; import ; import ; @Configuration public class MultiLevelCacheConfig { @Bean public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory, RedisCacheConfiguration defaultConfig) { // Create configuration maps for different cache spaces Map<String, RedisCacheConfiguration> configMap = new HashMap<>(); // User cache: Expiration time 30 minutes ("userCache", ((30))); // Product Cache: Expiration time 2 hours ("productCache", ((2))); // Hotspot data cache: Expiration time 5 minutes ("hotDataCache", ((5))); // Create and configure RedisCacheManager return (connectionFactory) .cacheDefaults(defaultConfig) .withInitialCacheConfigurations(configMap) .build(); } }
6. Customize expiration strategy
The cache expiration strategy directly affects the effectiveness of the cache and resource consumption. Spring Data Redis supports a variety of expiration settings, including global unified expiration time, setting expiration time by cache name, and dynamically setting expiration time according to cache content. A reasonable expiration strategy helps balance cache hit rate and data freshness. For data with different update frequencies, different expiration times should be set for the best results.
import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; @Configuration public class CustomExpirationConfig { @Bean public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { // Create a custom RedisCacheWriter RedisCacheWriter cacheWriter = (connectionFactory); // Default cache configuration RedisCacheConfiguration defaultConfig = () .entryTtl((1)); // Default expiration time is 1 hour // Create a RedisCacheManager that supports dynamic TTL return new DynamicTtlRedisCacheManager(cacheWriter, defaultConfig); } // Custom cache key generator, consider method names and parameters @Bean public KeyGenerator customKeyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); (().getSimpleName()) .append(":") .append(()); for (Object param : params) { if (param != null) { (":").append(()); } } return (); } }; } // Custom RedisCacheManager, supports dynamic TTL static class DynamicTtlRedisCacheManager extends RedisCacheManager { public DynamicTtlRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultConfig) { super(cacheWriter, defaultConfig); } @Override protected RedisCache createRedisCache(String name, RedisCacheConfiguration config) { // Dynamically set TTL based on cache name if (("userActivity")) { config = ((15)); } else if (("product")) { config = ((4)); } else if (("config")) { config = ((1)); } return (name, config); } } }
7. Advanced applications for cache annotations
Spring Cache provides rich annotations for managing caches, including @Cacheable, @CachePut, @CacheEvict, and @Caching. These annotations enable granular control over cache behavior, how to cache results, update caches, and clear caches. Through the condition and unless properties, condition caching can be implemented, and only the results that meet certain conditions will be cached. Use of these annotations reasonably can improve the hit rate and effectiveness of the cache.
import ; import ; import ; import ; import ; @Service public class ProductService { private final ProductRepository repository; public ProductService(ProductRepository repository) { = repository; } /** * Query the product based on the ID, the result will be cached * Conditions: Product price is greater than 100 before cache */ @Cacheable( value = "productCache", key = "#id", condition = "#id > 0", unless = "#result != null && # <= 100" ) public Product findById(Long id) { // Simulate query from the database return (id).orElse(null); } /** * Update product information and update cache */ @CachePut(value = "productCache", key = "#") public Product updateProduct(Product product) { return (product); } /** * Delete the product and clear the relevant cache * allEntries=true means clearing all productCache cache items */ @CacheEvict(value = "productCache", key = "#id", allEntries = false) public void deleteProduct(Long id) { (id); } /** *Compound cache operation: clear multiple caches at the same time */ @Caching( evict = { @CacheEvict(value = "productCache", key = "#id"), @CacheEvict(value = "categoryProductsCache", key = "#") } ) public void deleteProductWithRelations(Long id, Product product) { (id); } }
8. Implement cache warm-up and update strategies
Cache warm-up refers to loading hotspot data into the cache in advance when the system starts, to avoid performance problems caused by large cache misses in the early stage of system startup. The cache update strategy focuses on how to maintain consistency between cached data and database data. Common update strategies include invalid updates, timed updates and asynchronous updates. Reasonable cache warm-up and update strategies can improve the system's response speed and stability.
import ; import ; import ; import ; import ; import ; import ; @Component public class CacheWarmer implements CommandLineRunner { private final ProductRepository productRepository; private final CacheManager cacheManager; private final RedisTemplate<String, Object> redisTemplate; public CacheWarmer(ProductRepository productRepository, CacheManager cacheManager, RedisTemplate<String, Object> redisTemplate) { = productRepository; = cacheManager; = redisTemplate; } /** * Perform cache preheating when the system starts */ @Override public void run(String... args) { ("Performing cache warming..."); // Load popular products to cache List<Product> hotProducts = productRepository.findTop100ByOrderByViewsDesc(); for (Product product : hotProducts) { String cacheKey = "productCache::" + (); ().set(cacheKey, product); // Set differentiated expiration time to avoid expiration at the same time long randomTtl = 3600 + (long)(() * 1800); // Random values between 1 hour and 1.5 hours (cacheKey, randomTtl, ); } ("Cache warming completed, loaded " + () + " products"); } /** * Timely update the hotspot data cache, and execute it once an hour */ @Scheduled(fixedRate = 3600000) public void refreshHotDataCache() { ("Refreshing hot data cache..."); // Get the latest hotspot data List<Product> latestHotProducts = productRepository.findTop100ByOrderByViewsDesc(); // Update cache for (Product product : latestHotProducts) { ().set("productCache::" + (), product); } } }
9. Cache monitoring and statistics
Cache monitoring is an important part of cache management. Through monitoring, you can understand the cache usage, hit rate, memory usage and other key indicators. Spring Boot Actuator combined with Micrometer can collect cache statistics and visually display them through monitoring systems such as Prometheus. By monitoring data, cache problems can be discovered and optimized in a timely manner, such as adjusting cache size, expiration time and update strategy.
import ; import ; import ; import ; import ; import ; import ; import ; @Aspect @Component public class CacheMonitorAspect { private final MeterRegistry meterRegistry; private final ConcurrentHashMap<String, AtomicLong> cacheHits = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, AtomicLong> cacheMisses = new ConcurrentHashMap<>(); public CacheMonitorAspect(MeterRegistry meterRegistry) { = meterRegistry; } /** * Monitor the execution of cache methods */ @Around("@annotation()") public Object monitorCacheable(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = ().toShortString(); sample = (meterRegistry); // The method is marked before execution, used to determine whether the cache has gone away boolean methodExecuted = false; try { Object result = (); methodExecuted = true; return result; } finally { // Record the execution time of the method (("", "method", methodName)); // Update cache hit/miss count if (methodExecuted) { // The method is executed, indicating that the cache misses (methodName, k -> { AtomicLong counter = new AtomicLong(0); ("", counter); return counter; }).incrementAndGet(); } else { // The method is not executed, indicating that the hit cache is (methodName, k -> { AtomicLong counter = new AtomicLong(0); ("", counter); return counter; }).incrementAndGet(); } } } }
Summarize
Spring Data Redis Cache provides flexible configuration options to enable developers to customize serialization methods and expiration policies based on business needs. A reasonable serialization mechanism can significantly improve cache efficiency and reduce network transmission and storage space consumption. Scientific expiration strategies can balance data consistency and cache hit rate, avoiding problems such as cache penetration and avalanche. In actual applications, the cache strategy should be configured in differentiated manner in combination with business characteristics, such as setting a short expiration time for hotspot data to ensure data freshness, and setting a longer expiration time for configuration data that are not frequently changed to reduce database queries. Through the establishment of cache preheating, update strategies and monitoring systems, a high-performance and highly reliable distributed cache system can be built to effectively support the business needs of large-scale concurrent access.
This is the article about SpringData's serialization mechanism and expiration strategy to implement custom Redis caches. For more relevant SpringData's serialization mechanism and expiration strategy content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!