SpringData Redis缓存:自定义序列化与过期策略

embedded/2025/3/19 2:59:50/

在这里插入图片描述

文章目录

    • 引言
    • 一、Spring Cache与Redis集成基础
    • 二、Redis缓存配置基础
    • 三、自定义序列化策略
    • 四、实现自定义序列化器
    • 五、多级缓存配置
    • 六、自定义过期策略
    • 七、缓存注解的高级应用
    • 八、实现缓存预热与更新策略
    • 九、缓存监控与统计
    • 总结

引言

在现代高并发分布式系统中,缓存扮演着至关重要的角色。Spring Data Redis提供了强大的缓存抽象层,使开发者能够轻松地在应用中集成Redis缓存。本文将深入探讨如何自定义Redis缓存的序列化机制和过期策略,帮助开发者解决缓存数据一致性、内存占用和访问效率等关键问题。通过合理配置Spring Cache注解和RedisCache实现,可显著提升应用性能,减轻数据库压力。

一、Spring Cache与Redis集成基础

Spring Cache是Spring框架提供的缓存抽象,它允许开发者以声明式方式定义缓存行为,而无需编写底层缓存逻辑。结合Redis作为缓存提供者,可以构建高性能的分布式缓存系统。Spring Cache支持多种注解,如@Cacheable、@CachePut、@CacheEvict等,分别用于缓存查询结果、更新缓存和删除缓存。Redis的高性能和丰富的数据结构使其成为理想的缓存存储选择。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;@SpringBootApplication
@EnableCaching  // 启用Spring缓存支持
public class RedisCacheApplication {public static void main(String[] args) {SpringApplication.run(RedisCacheApplication.class, args);}
}

二、Redis缓存配置基础

配置Redis缓存需要创建RedisCacheManager和定义基本的缓存属性。RedisCacheManager负责创建和管理RedisCache实例,而RedisCache则实现了Spring的Cache接口。基本配置包括设置Redis连接工厂、默认过期时间和缓存名称前缀等。通过RedisCacheConfiguration可以自定义序列化方式、过期策略和键前缀等。这些配置对缓存的性能和可用性有直接影响。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;import java.time.Duration;@Configuration
public class RedisCacheConfig {@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {// 创建默认的Redis缓存配置RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()// 设置缓存有效期为1小时.entryTtl(Duration.ofHours(1))// 设置键前缀.prefixCacheNameWith("app:cache:");return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();}
}

三、自定义序列化策略

默认情况下,Spring Data Redis使用JDK序列化,这种方式存在效率低、占用空间大、可读性差等问题。自定义序列化策略可以显著改善这些问题。常用的序列化方式包括JSON、ProtoBuf和Kryo等。其中JSON序列化便于调试但性能一般,ProtoBuf和Kryo则提供更高的性能和更小的存储空间。选择合适的序列化方式需要在性能、空间效率和可读性之间做权衡。

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
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;@Configuration
public class RedisSerializerConfig {@Beanpublic RedisCacheConfiguration cacheConfiguration() {// 创建自定义的ObjectMapper,用于JSON序列化ObjectMapper mapper = new ObjectMapper();// 启用类型信息,确保反序列化时能够正确恢复对象类型mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL,JsonTypeInfo.As.PROPERTY);// 创建基于Jackson的Redis序列化器GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(mapper);// 配置Redis缓存使用String序列化器处理键,JSON序列化器处理值return RedisCacheConfiguration.defaultCacheConfig().serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer));}
}

四、实现自定义序列化器

在某些场景下,Spring提供的序列化器可能无法满足特定需求,此时需要实现自定义序列化器。自定义序列化器需要实现RedisSerializer接口,覆盖serialize和deserialize方法。通过自定义序列化器,可以实现特定对象的高效序列化,或者为序列化添加额外的安全措施,如加密解密等。实现时需注意处理序列化异常和空值情况。

import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;import java.io.ByteArrayOutputStream;public class KryoRedisSerializer<T> implements RedisSerializer<T> {private final Class<T> clazz;private static final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {Kryo kryo = new Kryo();// 配置Kryo实例kryo.setRegistrationRequired(false); // 不要求注册类return kryo;});public KryoRedisSerializer(Class<T> clazz) {this.clazz = clazz;}@Overridepublic byte[] serialize(T t) throws SerializationException {if (t == null) {return new byte[0];}Kryo kryo = kryoThreadLocal.get();try (ByteArrayOutputStream baos = new ByteArrayOutputStream();Output output = new Output(baos)) {kryo.writeObject(output, t);output.flush();return baos.toByteArray();} catch (Exception e) {throw new SerializationException("Error serializing object using Kryo", e);}}@Overridepublic T deserialize(byte[] bytes) throws SerializationException {if (bytes == null || bytes.length == 0) {return null;}Kryo kryo = kryoThreadLocal.get();try (Input input = new Input(bytes)) {return kryo.readObject(input, clazz);} catch (Exception e) {throw new SerializationException("Error deserializing object using Kryo", e);}}
}

五、多级缓存配置

在实际应用中,往往需要为不同类型的数据配置不同的缓存策略。Spring Cache支持定义多个缓存,每个缓存可以有独立的配置。通过RedisCacheManagerBuilderCustomizer可以为不同的缓存名称定制配置,如设置不同的过期时间、序列化方式和前缀策略等。多级缓存配置能够针对业务特点优化缓存性能。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder;
import org.springframework.data.redis.connection.RedisConnectionFactory;import java.time.Duration;
import java.util.HashMap;
import java.util.Map;@Configuration
public class MultiLevelCacheConfig {@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory,RedisCacheConfiguration defaultConfig) {// 创建不同缓存空间的配置映射Map<String, RedisCacheConfiguration> configMap = new HashMap<>();// 用户缓存:过期时间30分钟configMap.put("userCache", defaultConfig.entryTtl(Duration.ofMinutes(30)));// 产品缓存:过期时间2小时configMap.put("productCache", defaultConfig.entryTtl(Duration.ofHours(2)));// 热点数据缓存:过期时间5分钟configMap.put("hotDataCache", defaultConfig.entryTtl(Duration.ofMinutes(5)));// 创建并配置RedisCacheManagerreturn RedisCacheManager.builder(connectionFactory).cacheDefaults(defaultConfig).withInitialCacheConfigurations(configMap).build();}
}

六、自定义过期策略

缓存过期策略直接影响缓存的有效性和资源消耗。Spring Data Redis支持多种过期设置方式,包括全局统一过期时间、按缓存名称设置过期时间,以及根据缓存内容动态设置过期时间。合理的过期策略有助于平衡缓存命中率和数据新鲜度。对于不同更新频率的数据,应设置不同的过期时间以获得最佳效果。

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.CacheKeyPrefix;
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 org.springframework.data.redis.connection.RedisConnectionFactory;import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Objects;@Configuration
public class CustomExpirationConfig {@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {// 创建自定义的RedisCacheWriterRedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);// 默认缓存配置RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)); // 默认过期时间1小时// 创建支持动态TTL的RedisCacheManagerreturn new DynamicTtlRedisCacheManager(cacheWriter, defaultConfig);}// 自定义缓存键生成器,考虑方法名和参数@Beanpublic KeyGenerator customKeyGenerator() {return new KeyGenerator() {@Overridepublic Object generate(Object target, Method method, Object... params) {StringBuilder sb = new StringBuilder();sb.append(target.getClass().getSimpleName()).append(":").append(method.getName());for (Object param : params) {if (param != null) {sb.append(":").append(param.toString());}}return sb.toString();}};}// 自定义RedisCacheManager,支持动态TTLstatic class DynamicTtlRedisCacheManager extends RedisCacheManager {public DynamicTtlRedisCacheManager(RedisCacheWriter cacheWriter,RedisCacheConfiguration defaultConfig) {super(cacheWriter, defaultConfig);}@Overrideprotected RedisCache createRedisCache(String name, RedisCacheConfiguration config) {// 根据缓存名称动态设置TTLif (name.startsWith("userActivity")) {config = config.entryTtl(Duration.ofMinutes(15));} else if (name.startsWith("product")) {config = config.entryTtl(Duration.ofHours(4));} else if (name.startsWith("config")) {config = config.entryTtl(Duration.ofDays(1));}return super.createRedisCache(name, config);}}
}

七、缓存注解的高级应用

Spring Cache提供了丰富的注解用于管理缓存,包括@Cacheable、@CachePut、@CacheEvict和@Caching等。这些注解能够精细控制缓存行为,如何何时缓存结果、更新缓存和清除缓存。通过condition和unless属性,可以实现条件缓存,只有满足特定条件的结果才会被缓存。合理使用这些注解可以提高缓存的命中率和有效性。

import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;@Service
public class ProductService {private final ProductRepository repository;public ProductService(ProductRepository repository) {this.repository = repository;}/*** 根据ID查询产品,结果会被缓存* 条件:产品价格大于100才缓存*/@Cacheable(value = "productCache",key = "#id",condition = "#id > 0",unless = "#result != null && #result.price <= 100")public Product findById(Long id) {// 模拟从数据库查询return repository.findById(id).orElse(null);}/*** 更新产品信息并更新缓存*/@CachePut(value = "productCache", key = "#product.id")public Product updateProduct(Product product) {return repository.save(product);}/*** 删除产品并清除相关缓存* allEntries=true表示清除所有productCache的缓存项*/@CacheEvict(value = "productCache", key = "#id", allEntries = false)public void deleteProduct(Long id) {repository.deleteById(id);}/*** 复合缓存操作:同时清除多个缓存*/@Caching(evict = {@CacheEvict(value = "productCache", key = "#id"),@CacheEvict(value = "categoryProductsCache", key = "#product.categoryId")})public void deleteProductWithRelations(Long id, Product product) {repository.deleteById(id);}
}

八、实现缓存预热与更新策略

缓存预热是指在系统启动时提前加载热点数据到缓存中,以避免系统启动初期大量缓存未命中导致的性能问题。缓存更新策略则关注如何保持缓存数据与数据库数据的一致性。常见的更新策略包括失效更新、定时更新和异步更新等。合理的缓存预热与更新策略能够提高系统的响应速度和稳定性。

import org.springframework.boot.CommandLineRunner;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.concurrent.TimeUnit;@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) {this.productRepository = productRepository;this.cacheManager = cacheManager;this.redisTemplate = redisTemplate;}/*** 系统启动时执行缓存预热*/@Overridepublic void run(String... args) {System.out.println("Performing cache warming...");// 加载热门产品到缓存List<Product> hotProducts = productRepository.findTop100ByOrderByViewsDesc();for (Product product : hotProducts) {String cacheKey = "productCache::" + product.getId();redisTemplate.opsForValue().set(cacheKey, product);// 设置差异化过期时间,避免同时过期long randomTtl = 3600 + (long)(Math.random() * 1800); // 1小时到1.5小时之间的随机值redisTemplate.expire(cacheKey, randomTtl, TimeUnit.SECONDS);}System.out.println("Cache warming completed, loaded " + hotProducts.size() + " products");}/*** 定时更新热点数据缓存,每小时执行一次*/@Scheduled(fixedRate = 3600000)public void refreshHotDataCache() {System.out.println("Refreshing hot data cache...");// 获取最新的热点数据List<Product> latestHotProducts = productRepository.findTop100ByOrderByViewsDesc();// 更新缓存for (Product product : latestHotProducts) {redisTemplate.opsForValue().set("productCache::" + product.getId(), product);}}
}

九、缓存监控与统计

缓存监控是缓存管理的重要组成部分,通过监控可以了解缓存的使用情况、命中率、内存占用等关键指标。Spring Boot Actuator结合Micrometer可以收集缓存统计数据并通过Prometheus等监控系统进行可视化展示。通过监控数据可以及时发现缓存问题并进行优化,如调整缓存大小、过期时间和更新策略等。

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;@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) {this.meterRegistry = meterRegistry;}/*** 监控缓存方法的执行情况*/@Around("@annotation(org.springframework.cache.annotation.Cacheable)")public Object monitorCacheable(ProceedingJoinPoint joinPoint) throws Throwable {String methodName = joinPoint.getSignature().toShortString();Timer.Sample sample = Timer.start(meterRegistry);// 方法执行前标记,用于判断是否走了缓存boolean methodExecuted = false;try {Object result = joinPoint.proceed();methodExecuted = true;return result;} finally {// 记录方法执行时间sample.stop(meterRegistry.timer("cache.access.time", "method", methodName));// 更新缓存命中/未命中计数if (methodExecuted) {// 方法被执行,说明缓存未命中cacheMisses.computeIfAbsent(methodName, k -> {AtomicLong counter = new AtomicLong(0);meterRegistry.gauge("cache.miss.count", counter);return counter;}).incrementAndGet();} else {// 方法未执行,说明命中缓存cacheHits.computeIfAbsent(methodName, k -> {AtomicLong counter = new AtomicLong(0);meterRegistry.gauge("cache.hit.count", counter);return counter;}).incrementAndGet();}}}
}

总结

Spring Data Redis缓存通过提供灵活的配置选项,使开发者能够根据业务需求自定义序列化方式和过期策略。合理的序列化机制可显著提升缓存效率,减少网络传输和存储空间消耗。而科学的过期策略则能平衡数据一致性和缓存命中率,避免缓存穿透和雪崩等问题。在实际应用中,缓存策略应结合业务特点进行差异化配置,如对热点数据设置较短过期时间以保证数据新鲜度,对变更不频繁的配置数据设置较长过期时间以减少数据库查询。通过缓存预热、更新策略和监控体系的建立,可以构建高性能、高可靠的分布式缓存系统,有效支撑大规模并发访问的业务需求。


http://www.ppmy.cn/embedded/173747.html

相关文章

记录一次wifi版有人物联串口服务器桥接网络调试经过

目前的项目想法是将一台设备IP192.168.3.56的设备通过网口发给串口服务器&#xff0c;然后串口服务器通过桥接&#xff0c;将这个数据通过wifi路由器转发给另外一台设备IP为192.168.3.17&#xff0c;其中串口服务器的IP为192.168.3.16&#xff0c;wifi路由器组成的局域网的网管…

【操作系统安全】任务4:Windows 系统网络安全实践里常用 DOS 命令

目录 一、引言 二、网络信息收集类命令 2.1 ipconfig 命令 2.1.1 功能概述 2.1.2 实例与代码 2.2 ping 命令 2.2.1 功能概述 2.2.2 实例与代码 2.3 tracert 命令 2.3.1 功能概述 2.3.2 实例与代码 三、网络连接与端口管理类命令 3.1 netstat 命令 3.1.1 功能概述…

Webpack 和 Vite 的主要区别

Webpack 和 Vite 的主要区别&#xff0c;从构建机制、开发体验、生产优化等多个维度进行对比&#xff1a; 1. 构建机制与速度 Webpack 全量打包&#xff1a;启动时必须分析所有模块依赖关系&#xff0c;进行全量打包&#xff0c;生成 Bundle 文件。项目越大&#xff0c;冷启动时…

16、JavaEE核心技术-EL与 JSTL

EL与 JSTL 实践 一. EL&#xff08;Expression Language&#xff09; EL&#xff08;表达式语言&#xff09;是 JSP 2.0 中引入的一种简单的脚本语言&#xff0c;用于在 JSP 页面中简化数据的访问和显示。它通过一种类似于 JavaScript 的语法&#xff0c;允许开发者在 JSP 页面…

六十天前端强化训练之第二十二天之React 框架 15天深度学习总结(大师版)

欢迎来到编程星辰海的博客讲解 看完可以给一个免费的三连吗&#xff0c;谢谢大佬&#xff01; 目录 一、React 核心架构思想&#xff08;深度解析&#xff09; 1.1 组件化思维革命 1.2 虚拟DOM的智慧 二、关键技术深度剖析 2.1 JSX 本质揭秘 2.2 Hooks 设计哲学 三、企业…

vue computed 计算属性简述

Vue 的 ‌计算属性&#xff08;Computed Properties&#xff09;‌ 是 Vue 实例中一种特殊的属性&#xff0c;用于‌声明式地定义依赖其他数据动态计算得出的值‌。它的核心优势在于能够自动追踪依赖关系&#xff0c;并缓存计算结果&#xff0c;避免重复计算&#xff0c;提升性…

如何学习VBA_3.2.20:DTP与Datepicker实现日期的输入

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的劳动效率&#xff0c;而且可以提高数据处理的准确度。我推出的VBA系列教程共九套和一部VBA汉英手册&#xff0c;现在已经全部完成&#xff0c;希望大家利用、学习。 如果…

深入理解 HTML 表单与输入

在网页开发的广袤领域中&#xff0c;HTML 表单如同搭建用户与服务器沟通桥梁的基石。它是收集用户输入信息的关键渠道&#xff0c;承载着交互的重任。今天&#xff0c;就让我们一同深入探索 HTML 表单与输入的奥秘。​ HTML 表单在文档中划定出一片独特的区域&#xff0c;这片…