苍穹外卖优化-续
1、spring中的redis配置
模块 | 关键配置/注解 | 作用一句话 | 必须/可选 | 快速记忆口诀 |
---|---|---|---|---|
Redis 序列化 | ObjectMapper.setVisibility(ALL,ANY) | 让 Jackson 读到 private 字段,否则只能读public字段 | 必须 | 无它 → 空 JSON{} |
| 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 | 自定义 RabbitTemplate 并 setMessageConverter(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(如SmsService 、StockMapper ),只有自身是 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 | 给 SimpleRabbitListenerContainerFactory 设 setMessageConverter(new Jackson2JsonMessageConverter()) @RabbitListener(queues = RabbitConstant.QUEUE_ORDER_DEAD,messageConverter = "jackson2JsonMessageConverter") 这个jackson2JsonMessageConverter需要自己注入,变成Bean | 配置类加 @Configuration |
步骤2:生产者配置 | 声明 Jackson2JsonMessageConverter | 给 RabbitTemplate 设 setMessageConverter(new Jackson2JsonMessageConverter()) | 发送时直接 convertAndSend(exchange, routingKey, orderPO) |
步骤3:实体类校验 | 可反序列化 | ① 实体类的可反序列化性:必须有默认无参构造函数、字段类型匹配、避免循环依赖( | Lombok 组合:@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor |
验证流程 | 清消息 → 重启 → 观察控制台 | ① purge 原队列与死信队列 | 无历史脏数据,快速验证 |