2026山东大学软件学院项目实训个人blog(八)
Redis缓存优化方案的设计与落地,针对高频访问、低频更新的精选应用列表场景实现缓存能力
目录
一、本期个人核心任务
负责Redis缓存优化方案的设计与落地,针对高频访问、低频更新的精选应用列表场景实现缓存能力,解决数据库查询压力大、接口响应慢的问题,提升平台首页访问的流畅度与用户体验。
二、缓存策略设计
当前平台首页精选应用列表接口直接查询数据库,随着访问量增加,数据库查询压力持续增大,接口响应耗时变长,用户浏览首页时易出现加载延迟的问题。
因此本次开发的核心目标是:构建基于Redis的旁路缓存体系,针对精选应用列表场景定制缓存策略,通过SpringCache注解简化缓存开发,实现“缓存优先、数据库兜底”的查询逻辑,降低数据库访问频次,提升接口响应速度。
(一)需求分析
平台首页精选应用列表是用户访问频次最高的核心接口之一,现有实现存在明显痛点:
-
精选应用列表无缓存层,每次请求均查询数据库,高并发下数据库压力大;
-
精选应用由管理员手动维护,更新频率低,但每次查询仍需走数据库链路,资源利用率低;
为解决上述问题,本次开发需实现:
-
设计旁路缓存模式,针对精选应用列表场景定制“查询先查缓存、未命中查库并回写缓存”的逻辑;
-
开发缓存工具类,实现缓存key的统一生成与管理,避免key重复或过长问题;
-
基于Spring Data Redis配置缓存管理器,适配不同缓存区域的过期时间需求;
-
通过缓存注解实现接口缓存能力,仅缓存前10页高频访问数据,兼顾缓存效率与内存占用。
通过本次需求落地,平台将新增“精选应用列表缓存”能力,形成“缓存层→数据库层”的双层查询体系,降低数据库压力,提升首页访问体验。
(二)方案设计
针对Redis缓存优化的需求,梳理核心设计思路并明确技术方案:
1.缓存模式选择
对比常见缓存模式(旁路缓存、读写穿透、写回缓存),结合精选应用“读多写少”的特点,选择旁路缓存模式:
-
查询逻辑:缓存命中→直接返回;缓存未命中→查数据库→回写缓存→返回结果;
-
更新逻辑:无需主动删除缓存,通过设置合理过期时间,让缓存自然失效,避免缓存与数据库数据不一致;
-
优势:实现简单,无需侵入数据库操作逻辑,适配现有代码架构。
2.缓存key生成方案
为避免key重复、过长或格式不统一问题,设计“参数序列化+MD5哈希”的生成逻辑:
-
第一步:将接口参数(如页码、页大小)通过JSON序列化转为字符串,保证相同参数生成相同字符串;
-
第二步:对序列化后的字符串做MD5哈希,生成固定长度(32位)的唯一key,避免key过长占用Redis内存;
-
第三步:增加null值/空参数边界处理,保证异常参数也能生成合法key。
3.缓存管理器配置方案
基于SpringCache设计缓存管理器,兼顾通用性与定制化:
-
基础配置:key序列化使用StringRedisSerializer(保证key可读),value序列化先简化为字符串(规避反序列化类信息丢失问题);
-
差异化配置:默认缓存过期时间+精选应用缓存区域专属过期时间,适配不同场景需求;
4.最终方案
综合场景特点与技术适配性,确定最终方案:
-
核心模式:旁路缓存+前10页数据缓存;
-
key生成:JSON序列化+MD5哈希+边界处理;
-
配置体系:自定义CacheManager+注解式缓存;
-
过期策略:设置合理过期时间,无需主动删除缓存,自然失效保证数据一致性。
三、开发实现
1)环境与依赖确认
依赖检查:因前期引入spring-session-data-redis,已自动依赖spring-data-redis,无需额外添加Maven/Gradle依赖:
spring:
data:
redis:
host: localhost
port: 6379
database: 0
password:
ttl: 3600 # 缓存过期时间(秒)
2)缓存工具类开发
为保证缓存key生成的一致性与唯一性,定制缓存工具类(CacheKeyUtils),核心设计要点:
-
静态方法封装:提供
generateKey(Object...params)方法,支持多参数传入生成唯一key; -
序列化处理:使用Hutool的JSONUtil序列化参数,处理null值为空字符串,保证序列化结果统一;
-
哈希处理:通过MD5算法对序列化字符串哈希,生成固定长度key;
-
异常处理:捕获序列化过程中的异常,返回默认key前缀+异常标识,避免工具类抛出异常导致接口降级。
/**
* 缓存 key 生成工具类
*
* @author yupi
*/
public class CacheKeyUtils {
/**
* 根据对象生成缓存key (JSON + MD5)
*
* @param obj 要生成key的对象
* @return MD5哈希后的缓存key
*/
public static String generateKey(Object obj) {
if (obj == null) {
return DigestUtil.md5Hex("null");
}
// 先转JSON,再MD5
String jsonStr = JSONUtil.toJsonStr(obj);
return DigestUtil.md5Hex(jsonStr);
}
}
3)启用缓存
启用缓存功能:在SpringBoot启动类添加@EnableCaching注解,开启Spring Cache支持:
@EnableCaching
@SpringBootApplication
public class YuAiCodeMotherApplication {
public static void main(String[] args) {
SpringApplication.run(YuAiCodeMotherApplication.class, args);
}
}
4)配置缓存管理器
必须配置Redis缓存管理器CacheManager,这是Spring Cache的核心组件。
如果不配置的话,使用缓存注解时可能会报错:

在config包下新增代码,定制CacheManager:
@Configuration
public class RedisCacheManagerConfig {
@Resource
private RedisConnectionFactory redisConnectionFactory;
@Bean
public CacheManager cacheManager() {
// 配置 ObjectMapper 支持 Java8 时间类型
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
// 默认配置
RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) // 默认 30 分钟过期
.disableCachingNullValues() // 禁用 null 值缓存
// key 使用 String 序列化器
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
// value 使用 JSON 序列化器(支持复杂对象)
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)));
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaultConfig)
// 针对 good_app_page 配置5分钟过期
.withCacheConfiguration("good_app_page",
defaultConfig.entryTtl(Duration.ofMinutes(5)))
.build();
}
}
这个配置的几个关键点:
1.序列化器选择:StringRedisSerializer用于序列化key,确保Redis中的key是可读的字符串;GenericJackson2JsonRedisSerializer用于序列化value,支持复杂对象的序列化和反序列化。
2.时间类型支持:注册JavaTimeModule来支持Java8时间类型LocalDateTime
3.差异化配置:既提供了默认配置,又为特定的缓存区域设置不同的过期时间
但是要注意,如果对value进行JSON序列化,可能会出现无法反序列化的情况,因为Redis中并没有存储Java类的信息,不知道要反序列化成哪个类,就会报错。所以我们可以先注释掉这些代码:
// value 使用 JSON 序列化器(支持复杂对象)
// .serializeValuesWith(RedisSerializationContext.SerializationPair
// .fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)));
5)接口缓存注解应用
在AppController中为精选应用列表接口添加缓存注解:
@PostMapping("/good/list/page/vo")
@Cacheable(
value = "good_app_page",
key = "T(com.yupi.yuaicodemother.utils.CacheKeyUtils).generateKey(#appQueryRequest)",
condition = "#appQueryRequest.pageNum <= 10"
)
public BaseResponse<Page<AppVO>> listGoodAppVOByPage(@RequestBody AppQueryRequest appQueryRequest) {
// 方法实现保持不变...
}
核心注解说明:
-
value:指定缓存区域,对应缓存管理器中定制的过期时间; -
key:通过SpEL调用静态方法生成唯一缓存key; -
condition:缓存条件,仅前10页数据缓存,符合用户浏览习惯,减少无效缓存。
四、测试验证
通过Redis客户端(Redis Desktop Manager)查看缓存数据的效果。
每个页面的缓存都有独立的key:

缓存的内容以JSON格式存储,便于阅读和调试

五、开发过程中的问题与解决方案
问题:Redis反序列化失败
现象:配置GenericJackson2JsonRedisSerializer序列化value后,接口返回时出现“无法反序列化为指定类”异常。
原因:JSON序列化未存储Java类的全限定名,Redis中仅保存纯JSON字符串,反序列化时无法确定目标类类型。
解决方案:
临时方案:简化value序列化方式为StringRedisSerializer,直接存储JSON字符串,接口返回时自行反序列化;
长期方案:定制GenericJackson2JsonRedisSerializer,注册Jackson2ObjectMapperBuilder并开启enableDefaultTyping,让序列化结果包含类信息,解决反序列化类匹配问题。
六、实训收获与技术成长
本次Redis缓存优化开发,核心收获体现在:
-
掌握缓存架构设计思路,理解不同缓存模式的适用场景,能结合业务特点选择最优方案(如旁路缓存适配读多写少场景);
-
深入理解Spring Cache注解与Redis缓存管理器的协同机制,解决了序列化、key生成、过期策略等核心问题;
-
具备缓存问题排查能力,能定位反序列化、缓存穿透/失效等常见问题,并设计临时/长期解决方案;
七、后续开发计划
增加缓存监控:接入Spring Boot Actuator + Prometheus + Grafana,监控缓存命中率、过期率、内存占用等指标,及时发现缓存异常;
扩展缓存场景:将缓存策略复用至其他读多写少接口(如应用详情、分类列表),统一缓存工具类与配置体系;
优化缓存一致性:针对精选应用更新场景,实现“更新数据库+主动删除缓存”逻辑,避免缓存过期前的数据不一致问题。
更多推荐


所有评论(0)