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 | 自定义 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:实体类校验 | 可反序列化 | ① @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、若必须用线程池,需使用Spring 的AsyncTransactionManager 或手动管理事务(复杂度高) |
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)(自带重试 + 死信,无需额外配置)。 |