苍穹外卖优化-续

1、spring中的redis配置

模块 关键配置/注解 作用一句话 必须/可选 快速记忆口诀
Redis 序列化 ObjectMapper.setVisibility(ALL,ANY) 让 Jackson 读到 private 字段,否则只能读public字段 必须 无它 → 空 JSON{}
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); registerModule(JavaTimeModule) LocalDateTime 不报错、不转时间戳 必须 无它 → 时间乱码
activateDefaultTyping(...) 支持"父类变量存子类对象" 可选 用不到就注释
RedisTemplate<String, Object> 用法,value用的JSON的序列化器 redisTemplate.opsForValue().set(key,obj,ttl) 手动存任意 Java 对象,自动转为JSON格式的 ------ 复杂/定时缓存用它
redisTemplate.opsForValue().get(key) 手动读对象,读出来是Object类型,强转指定类型即可 ------ 序列化器已帮你转好
Spring Cache 开关 @EnableCaching 打在启动类 让 @Cacheable 生效 必须 漏加 → 注解全失效
CacheManager 配置,SpringCache的核心接口 GenericJackson2JsonRedisSerializer 注解缓存以 JSON 存 Redis 必须 无它 → 二进制乱码
.entryTtl(Duration.ofMinutes(30)) 默认 30 min 过期 推荐 防数据永驻
SpringCache 注解放置层 仅 Service 层 Controller/Mapper 都不放 铁律 放错 → 重复缓存 or 缓存失效
查询缓存 @Cacheable(value="biz:func",key="#p0+'-'+#p1") 有缓存直接返回 常用 value 即前缀,key 拼参数
修改清缓存 @CacheEvict(value="biz:func",allEntries=true) 改库后整组清 常用 只改一条时改 key 精准清除
缓存键命名规则 业务:功能:唯一标识 例:dish:id:1 全局不冲突、可排查 推荐 冒号分层,IDE 全局搜
redis写入基础类型,value是JSON序列化器 直接 set(key,Integer) Jackson 会转 JSON 值 "1" ------ 看起来没转,实际已转
redis写入复杂对象,value是JSON序列化器 直接 set(key,DishVO) Jackson 转完整 JSON ------ 可读、可调试
序列化器对比 RedisTemplate vs. CacheManager 手动 API vs. 注解 根据应用场景选择

2、常见缓存问题及其解决方案

问题 典型场景 解决方案 方法逻辑(一句话) 具体实现要点(直接抄代码) Redis 里真实形态 坑点提示
缓存穿透 数据库&缓存都无数据,每次打DB 空值占位 "" 写回 Redis,TTL 短 if(db==null) redis.set(key,"",2min) "" 别忘了给空值加过期时间,否则永远进不来
缓存击穿 热点 key 瞬时过期,高并发全部打到 DB ① 互斥锁 ① 只让 1 个线程重建 setnx 抢锁 → 查 DB → 写缓存 → del ① 正常 JSON ① 锁一定加过期时间防止死锁
缓存雪崩 大量 key 同时失效,请求雪崩 随机 TTL + 集群 + 限流 + 多级缓存 让过期时间 分散 TTL = base + Random(0~300)s 正常 JSON 随机值范围别太小,否则仍可能批量失效

缓存穿透: 查询→DB无→Redis写""→下次直接返回空

缓存击穿: 查询→Redis无/过期→抢锁→只有1线程重建→其余线程短暂等待 or 先返回旧数据

缓存雪崩: 批量查询→Redis同时过期→随机TTL打散→数据库负载平滑

建议封装成Redis的工具类,传参调用相应的功能类中的方法:

3、RabbitMQ使用前的配置

模块 关键要点 配置/代码片段 备注
1. 依赖 必引包 spring-boot-starter-amqp 后者支持 LocalDateTime
2. 连接 yml 最简配置 host/port/username/password/virtual-host 生产环境换账号
3. 队列/交换机 配置类一次性声明 @Bean public Queue + DirectExchange + Binding durable=true 持久化
4. 生产者 发消息入口 rabbitTemplate.convertAndSend(exchange, routingKey, dto) dto 为 POJO
5. 消费者 监听入口 @RabbitListener(queues = "xxx") 方法参数直接写 dto 类型
6. 序列化 必换 JSON 自定义 RabbitTemplatesetMessageConverter(new Jackson2JsonMessageConverter(objectMapper)) 解决可读性/兼容性/安全
7. 对象兼容性 字段一致即可 无需 Serializable 但建议加上防意外
8. 持久化三件套 队列+交换机+消息 创建时 durable=true 重启不丢
9. 生产者确认 确保"发到 Rabbit" yml: publisher-confirm-type: correlated 失败记录日志/重发
10. 消费者确认 确保"处理完" yml: acknowledge-mode: manual 异常 basicNack(tag, false, false) 进死信
11. 死信队列 失败消息兜底 普通队列加 x-dead-letter-exchange 等参数 人工后续处理
12. 常见消息类型 字符串/基本类型/POJO/List/Map 统一走 JSON 转换器 控制台可直接查看 JSON

4、消费者类加 @Component 注解的原因

心作用 具体说明 不添加的后果
让 Spring 识别并管理 Bean @Component 是 Spring 基础组件注解,标记后 Spring 会扫描并将消费者纳入容器管理,成为 Spring Bean。 消费者类不被 Spring 管理,@RabbitListener 注解无法被 Spring 的 RabbitMQ 监听容器解析,监听逻辑失效。
支持依赖注入 消费者通常依赖其他 Spring Bean(如SmsServiceStockMapper),只有自身是 Spring Bean,才能通过@Autowired注入依赖。 无法注入依赖,业务逻辑(如发送短信、扣减库存)无法执行,抛出NullPointerException

5、DirectRabbitConfig 配置类工作机制,能生效的原因

第一步:Spring 启动时,自动创建核心组件

Spring 启动时会扫描 @Configuration 标记的 DirectRabbitConfig 类,执行其中所有 @Bean 注解的方法,将返回的「队列」「交换机」「绑定关系」注册到 RabbitMQ 服务器:

配置模块 核心职责 工作流程(启动时 + 运行时) 关键规则(直连交换机)
队列(Queue) 定义消息的「存储容器」,设置是否持久化、排他、自动删除等属性(如TestDirectQueue)。 启动时:Spring 调用 RabbitMQ API,在服务器创建指定队列;运行时:接收交换机转发的消息,供消费者获取。 持久化(durable=true)确保 RabbitMQ 重启后队列不丢失。
交换机(DirectExchange) 定义消息的「路由中转站」(如TestDirectExchange),接收生产者发送的消息。 启动时:Spring 在 RabbitMQ 创建直连交换机;运行时:接收消息,根据路由键匹配绑定关系。 直连交换机仅转发「路由键完全匹配」的消息。
绑定(Binding) 关联「交换机 - 队列 - 路由键」(如TestDirectRouting),定义消息转发规则。 启动时:Spring 在 RabbitMQ 建立绑定关系;运行时:交换机通过绑定关系找到目标队列,完成消息转发。 路由键是绑定的核心,生产者需指定相同路由键才能匹配。
整体转发逻辑 串联「生产者 - 交换机 - 队列 - 消费者」,实现消息路由。 1. 生产者发送消息(指定交换机 + 路由键)→ 2. 交换机匹配绑定关系 → 3. 消息转发到目标队列 → 4. 消费者监听队列获取消息。 路由键不匹配时,消息会丢失(需配置死信交换机避免)。

6、自定义rabbitTemplate属性, 类型的自动转换,以及和生产者/消费者 的 写入/读取 时出现的问题

模块 关键点 具体做法 备注
异常摘要 Cannot convert from byte[] to com.sky.entity.OrderPO Spring 缺省转换器无法把字节流变实体; 我的问题是,rabbitTemplate配置了自定义的序列化器(JSON格式的),即生产者写入的时候value是JSON格式的,但是消费者读取的时候没有设置序列化器,导致字节流无法转换为我的实体类 死信队列二次失败同理
根因 消息转换器未配置或两端不一致 生产者/消费者至少一方无 JSON 转换器 消息头 contentType 虽对,但体不是合法 JSON
步骤1:消费者配置 声明 Jackson2JsonMessageConverter SimpleRabbitListenerContainerFactorysetMessageConverter(new Jackson2JsonMessageConverter()) 或者直接(见下图) @RabbitListener( queues = RabbitConstant.QUEUE_ORDER_DEAD, messageConverter = "jackson2JsonMessageConverter") 这个jackson2JsonMessageConverter需要自己注入,变成Bean 配置类加 @Configuration
步骤2:生产者配置 声明 Jackson2JsonMessageConverter RabbitTemplatesetMessageConverter(new Jackson2JsonMessageConverter()) 发送时直接 convertAndSend(exchange, routingKey, orderPO)
步骤3:实体类校验 可反序列化 @NoArgsConstructor 必备 实体类的可反序列化性:必须有默认无参构造函数、字段类型匹配、避免循环依赖(OrderPO 中若有其他实体引用(如 UserPO),需确保无循环依赖,或用 @JsonIgnore 排除不需要序列化的字段。) Lombok 组合:@Data+@Builder+@AllArgsConstructor+@NoArgsConstructor
验证流程 清消息 → 重启 → 观察控制台 ① purge 原队列与死信队列 无历史脏数据,快速验证

7、初次尝试Springboot引入RabbitMQ后的,遇到的问题

# 一句话总结 立刻止血方案
1 Channel 被扔进线程池,多线程并发写同一个 TCP 帧 → 帧错乱、连接闪断、ACK 失败。(同一个 Channel 引用 传给了多个任务 (多段 Runnable)) 当多个线程池线程同时操作同一个Channel时,会导致通道数据错乱、连接中断、ACK/NACK 失败,表现为消息重复消费、死信队列异常堆积、Broker 通信超时等 禁止Channel 当参数传进线程池;改为手动确认模式 + 消费线程自己 ACK/NACK 通过@RabbitListener(concurrency="20")配置消费者并发数,与生产者 20 线程匹配,避免任务堆积。 Channel必须在当前消费线程 中操作,不能传递到异步线程。需将ACK/NACK逻辑移回主线程,仅业务逻辑异步执行(或直接去掉线程池,RabbitMQ 本身支持多消费者并发)。
2 @Transactional 只罩住主线程,pool.execute(...) 里的数据库操作已脱离事务上下文 → 脏数据/消息丢失。 1、去掉线程池,让@Transactional生效(推荐,RabbitMQ 消费者本身可通过concurrency配置多线程) 2、若必须用线程池,需使用SpringAsyncTransactionManager或手动管理事务(复杂度高)
3 所有异常一律 basicNack(..., false, false) 进死信,可恢复异常也被判死刑。 捕获异常后先分类:可重试异常 / 不可重试异常
4 20 并发生产者 vs 10 线程固定线程池,任务堆积 + TTL 超时 → 消息"秒进"死信。 消费者线程池改为 弹性线程池new ThreadPoolExecutor(core, max, 60s, ArrayBlockingQueue+拒绝策略))并与 TTL 配合
5 消息队列的重试问题 1、优先用 Spring-AMQP 原生重试机制(80% 的业务场景够用); 2、复杂场景用 RabbitMQ 延迟队列 + 死信队列(解决 "重试间隔可控" 需求); 3、超大规模场景用中间件(如 RocketMQ)(自带重试 + 死信,无需额外配置)。
相关推荐
金銀銅鐵3 小时前
[Java] 枚举常量的精确类型一定是当前枚举类型吗?
java·后端
邂逅星河浪漫4 小时前
Spring Boot常用注解-详细解析+示例
java·spring boot·后端·注解
青鱼入云4 小时前
java面试中经常会问到的mysql问题有哪些(基础版)
java·mysql·面试
Darenm1114 小时前
python进程,线程与协程
java·开发语言
凯哥Java4 小时前
适应新环境:Trae编辑器下的IDEA快捷键定制
java·编辑器·intellij-idea
從南走到北4 小时前
JAVA同城打车小程序APP打车顺风车滴滴车跑腿源码微信小程序打车源码
java·开发语言·微信·微信小程序·小程序
落日漫游4 小时前
K8s资源管理:高效管控CPU与内存
java·开发语言·kubernetes
weixin_456904274 小时前
基于Spring Boot + MyBatis的用户管理系统配置
spring boot·后端·mybatis