《rabbitmq-3.12.14版本结合若依springboot3从安装到使用》
一. linux 安装
1 compose 安装:
-- 参考docker资料
yaml
代码解读
复制代码
name: okyun services: rabbitmq: container_name: rabbitmq hostname: rabbitmq-okyun # 固定主机名,用于生成稳定节点名,持久化数据必须设置!!! image: rabbitmq:3.12.14-management deploy: resources: limits: memory: 2G cpus: "1.5" ports: - "5672:5672" - "15672:15672" environment: - RABBITMQ_DEFAULT_VHOST:okyun - RABBITMQ_DEFAULT_USER=admin - RABBITMQ_DEFAULT_PASS=123456 volumes: - rabbitmq_plugin:/plugins # 不可以挂载本地,否则倾向原生的插件 - ./rabbitmq/data:/var/lib/rabbitmq - ./rabbitmq/log:/var/log/rabbitmq - ./rabbitmq/config/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf restart: unless-stopped networks: - okyun volumes: rabbitmq_plugin: driver: local networks: okyun: driver: bridge
2 版本:
-- rabbitmq:3.12.14-managent
3 配置:
-- 限制内存 -- 限制磁盘大小 -- cpu使用百分比限制 -- 账号/密码 -- 挂载 :持久化路径、日志路径、 配置文件
二. springBoot3 配置
1 配置信息
xml
代码解读
复制代码
<!-- rabbitmq --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
yaml
代码解读
复制代码
# Spring配置 spring: # rabbitmq配置 rabbitmq: host: 192.168.1.44 port: 5672 username: admin password: 123456 virtual-host: okyun publisher-confirm-type: none # 生产确认机制 - 开启消息确认 correlated publisher-returns: false # 生产确认机制 - 开启消息回退 true template: mandatory: true # 确保消息回退可用(必须配置) listener: simple: acknowledge-mode: manual # 手动ACK,防止丢失消息
2 处理序列化
typescript
代码解读
复制代码
// RabbitMQConfig 自定义配置项 /** * 消息转换器 - 表单JSON序列化 * @return */ @Bean public Jackson2JsonMessageConverter jackson2JsonMessageConverter() { return new Jackson2JsonMessageConverter(); } /** * 创建消息模板 * @param connectionFactory * @return */ @Bean public org.springframework.amqp.rabbit.core.RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { org.springframework.amqp.rabbit.core.RabbitTemplate template = new org.springframework.amqp.rabbit.core.RabbitTemplate(connectionFactory); template.setMessageConverter(jackson2JsonMessageConverter()); // JSON 格式转换器 return template; }
三. 可靠性
1 生产者可靠性
1.1 确认机制
php
代码解读
复制代码
// 1 Applacation.yaml 配置 开启确认机制 publisher-confirm-type: correlated # 开启消息确认 correlated publisher-returns: true # 开启消息回退 true // 2 自定义类 RabbitMqProducerConfirmConfig -- 确认消息是否到达交换机 -- 确认消息是否到达队列 -- 判断 ack ,成功/失败 -> 如果失败 重新发送消息 // 3 发送消息需要传递 UUID try { // 生成唯一消息ID CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); log.info("异步处理 库存, 订单信息 : {}",salesOrder); for (int i = 0; i < 1000000; i++) { rabbitTemplate.convertAndSend(OrderDirectMQConfig.ORDER_EXCHANGE, OrderDirectMQConfig.ORDER_ROUTER_KEY, salesOrder, correlationData); } }catch (Exception e){ log.error("异步处理 库存失败:",e); }
1.2 持久化
csharp
代码解读
复制代码
/** * direct 类型的交换机 * @return */ @Bean public DirectExchange orderExchange() { // 1 持久化交换机 true return new DirectExchange(ORDER_EXCHANGE, true, false); } /** * 持久化队列 durable * @return */ @Bean public Queue orderQueue() { System.out.println("创建队列:" + ORDER_QUEUE); // 2 持久化队列 return QueueBuilder.durable(ORDER_QUEUE) .lazy() .withArgument("x-dead-letter-exchange", "dead.letter.exchange") .withArgument("x-dead-letter-routing-key", "dead.letter.routingKey") .build(); } try { CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); log.info("异步处理 库存, 订单信息 : {}",salesOrder); for (int i = 0; i < 1000000; i++) { // 3 消息持久化 rabbitTemplate.convertAndSend( OrderDirectMQConfig.ORDER_EXCHANGE, OrderDirectMQConfig.ORDER_ROUTER_KEY, salesOrder, message -> { // 3.1 消息持久化 .setDeliveryMode(2) message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); return message; } ); }catch (Exception e){ log.error("异步处理 库存失败:",e); } // 4 持久化必须设置 固定主机名,否则每次compose重新启动都会生成新的容器ID,导致持久化文件名变化,最后持久化失败!!! compose.yaml 参数设置 -- hostname: rabbitmq-okyun # 固定主机名,用于生成稳定节点名,持久化数据必须设置!!
1.3 惰性模式
csharp
代码解读
复制代码
/** * 持久化队列 durable * @return */ @Bean public Queue orderQueue() { System.out.println("创建队列:" + ORDER_QUEUE); return QueueBuilder.durable(ORDER_QUEUE) .lazy() // 1 设置为惰性队列,已磁盘存储为主,先磁盘在缓存 .withArgument("x-dead-letter-exchange", "dead.letter.exchange") // 指定死信交换机 .withArgument("x-dead-letter-routing-key", "dead.letter.routingKey") // 指定死信路由键 .build(); }
2 消费者可靠性
2.1 消费者确认机制
yaml
代码解读
复制代码
listener: simple: acknowledge-mode: auto # manual 手动ACK,防止丢失消息; auto 自动ACK,默认为自动ACK; none 不做处理,默认为自动ACK # 使用场景: -- 支付、库存等严格业务 使用 手动确认 manual’ -- 简单业务、允许重试的业务 使用 自动确认 auto;
2.2 重试机制
yaml
代码解读
复制代码
listener: simple: acknowledge-mode: auto # manual 手动ACK,防止丢失消息; auto 自动ACK,默认为自动ACK; none 不做处理,默认为自动ACK retry: enabled: true initial-interval: 1000 # 初始重试间隔 1 秒 multiplier: 2.0 # 每次重试间隔 *2 max-attempts: 3 # 最大重试 3 次
2.3 错误队列 error
bash
代码解读
复制代码
# 绑定死信队列,处理失败直接写入死信队列!
3 幂等性
3.1 校验消息ID
csharp
代码解读
复制代码
# 1 开启消息ID 配置 RabbitMQConfig 消息转换器中启用消息ID /** * 消息转换器 - 表单JSON序列化 * @return */ @Bean public Jackson2JsonMessageConverter jackson2JsonMessageConverter() { Jackson2JsonMessageConverter jjmConverter = new Jackson2JsonMessageConverter(); // 启用消息ID UUID 生成 jjmConverter.setCreateMessageIds(true); return jjmConverter; } # 2 处理消息将消息ID写入数据库 # 3 每次处理消息检查是否有相同的消息ID,有则不处理 # 4 这种方式的缺点:植入了外部业务,增加了数据库的操作,所以不推荐
3.2 业务层处理
sql
代码解读
复制代码
# 1 处理消息时,sql判断 -- 修改支付状态举例,每次修改添加状态条件; -- 删除操作原本就是幂等;
4 兜底方案
bash
代码解读
复制代码
# 1 如果确实消息处理异常 # 2 将异常消息放到私信队列 # 3 定时任务轮训 处理异常消息 -- 查询待处理的订单数据 -- 根据支付数据、订单数据 对比,如果存在已支付的数据,修改订单状态! # 4 人工介入处理
四 延时消息
1 延时消息 - 使用场景
lua
代码解读
复制代码
# 1 使用场景 -- 抢购付款环节,限制1分钟之内支付,未支付直接取消消息发送! -- 1分钟倒计时,如果付款,直接发送消息,处理后续业务! # 2 实现方式1 -- 通过消息设置延时过期时间 30s; -- 过期后 消息发送到私信队列; -- 处理死信队列中的消息; # 3 实现方式2 -- 使用插件 rabbitmq_delayed_message_exchage -- 将插件放到 rabbitmq docker挂载的插件目录中 -- 重启容器 执行插件的命令: docker run ... rabbitmq-plugins enable rabbitmq_delayed_message_exchage -- 交换机添加 delay 配置 -- 发送消息时添加 delay配置 .setDelay(5000) # 4 延时消息不推荐,应为只要是延时消息都会有监听时钟,增加内存的使用! # 5 如果必须使用延时消息; -- 将延时设置成多个时间段,比如1分钟 拆成 10s 10s 20s 20s -- 10s 后交换机 发送到队列 -> 队列检查判断是否支付,如果未支付继续发送 一次 时间段的延时发送 -> 最后错误处理,取消订单! -- 这样如果常规下10s可以处理的业务,就不至于等到1分钟的内存占用!
2 安装 延时插件
bash
代码解读
复制代码
# 1 github 下载延时插件 rabbitmq_delayed_message_exchange 地址:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases # 2 下载对应的版本: 我的是 3.12.14 所以插件版本 3.12.0 插件文件格式:rabbitmq-delayed-message-exchange-3.12.0.ez # 3 将插件放到 rabbitmq 安装映射路径下 docker inspect rabbitmq { "Type": "volume", "Name": "okyun_rabbitmq_plugin", "Source": "/var/lib/docker/volumes/okyun_rabbitmq_plugin/_data", "Destination": "/plugins", "Driver": "local", "Mode": "z", "RW": true, "Propagation": "" }, cd /var/lib/docker/volumes/okyun_rabbitmq_plugin/_data -> 将插件放入该目录下 # 4 执行安装执行插件的命令 docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_delayed_message_exchange # 5 成功安装如下: hubiao@hubiao-ideacentre-Y700-34ISH:~$ docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_delayed_message_exchange Enabling plugins on node rabbit@5d5294e1246a: rabbitmq_delayed_message_exchange The following plugins have been configured: rabbitmq_delayed_message_exchange rabbitmq_management rabbitmq_management_agent rabbitmq_prometheus rabbitmq_web_dispatch Applying plugin configuration to rabbit@5d5294e1246a... The following plugins have been enabled: rabbitmq_delayed_message_exchange started 1 plugins. hubiao@hubiao-ideacentre-Y700-34ISH:~$ # 6 验证延时插件 docker exec -it rabbitmq rabbitmq-plugins list | grep delayed # 7 重启rabbitmq 容器 docker compose down docker compose up -d
3 使用 延时插件
3.1 配置延时交换机
java
代码解读
复制代码
package com.ruoyi.rabbitmq.config; import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /** * 订单初始化队列配置 */ @Configuration public class OrderDirectMQConfig { /** * 订单交换机 */ public static final String ORDER_EXCHANGE = "order.exchange"; /** * 订单队列 */ public static final String ORDER_QUEUE = "order.queue"; /** * 订单路由键 */ public static final String ORDER_ROUTER_KEY = "order.routingKey"; /** * 延时消息专用交换机 */ public static final String DELAYED_ORDER_EXCHANGE = "order.delayed.direct"; /** * direct 类型的交换机 * @return */ @Bean public DirectExchange orderExchange() { return new DirectExchange(ORDER_EXCHANGE, true, false); } /** * 延迟交换机 * @return */ @Bean public CustomExchange delayedOrderExchange() { Map<String, Object> args = new HashMap<>(); args.put("x-delayed-type", "direct"); // 使用 direct 类型路由 return new CustomExchange(DELAYED_ORDER_EXCHANGE, "x-delayed-message", true, false, args); } /** * 持久化队列 durable * @return */ @Bean public Queue orderQueue() { System.out.println("创建队列:" + ORDER_QUEUE); return QueueBuilder.durable(ORDER_QUEUE) .lazy() // 设置为惰性队列 (等效于 x-queue-mode=lazy) .withArgument("x-dead-letter-exchange", "dead.letter.exchange") // 指定死信交换机 .withArgument("x-dead-letter-routing-key", "dead.letter.routingKey") // 指定死信路由键 .build(); } /** * 常规绑定 * @param orderQueue * @param orderExchange * @return */ @Bean public Binding orderBinding(Queue orderQueue, DirectExchange orderExchange) { return BindingBuilder.bind(orderQueue).to(orderExchange).with(ORDER_ROUTER_KEY); } /** * 延时消息绑定 */ @Bean public Binding delayedOrderBinding() { return BindingBuilder.bind(orderQueue()).to(delayedOrderExchange()).with(ORDER_ROUTER_KEY) .noargs(); // CustomExchange需要noargs() } }
3.2 正常发送消息
c
代码解读
复制代码
/** * 通过MQ异步更新库存 * @param salesOrder */ private void updateInventoryByMQ(SalesOrder salesOrder) { // 异步处理 库存 long start = System.currentTimeMillis(); try { log.info("异步处理 库存, 订单信息 : {}",salesOrder); rabbitTemplate.convertAndSend(OrderDirectMQConfig.ORDER_EXCHANGE, OrderDirectMQConfig.ORDER_ROUTER_KEY, salesOrder); }catch (Exception e){ log.error("异步处理 库存失败:",e); } log.info("处理耗时: {}ms", System.currentTimeMillis() - start); }
3.3 发送延时消息
less
代码解读
复制代码
@Component @Slf4j public class InventoryConsumer { @Autowired private RabbitTemplate rabbitTemplate; /** * 监听订单队列,处理库存扣减 * @param salesOrder * @param channel * @param deliveryTag * @throws IOException */ @RabbitListener(queues = OrderDirectMQConfig.ORDER_QUEUE) public void processMessage(@Payload SalesOrder salesOrder , Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, @Header(name = "x-retry-count", required = false) Integer retryCount ) throws IOException { try { // 处理库存扣减 System.out.println("Received Order: " + salesOrder); // 手动 ACK 确认消息 channel.basicAck(deliveryTag, false); } catch (LockAcquisitionException e) { // 锁获取失败的特殊处理 int newRetryCount = (retryCount == null) ? 1 : retryCount + 1; if (newRetryCount > 3) { // 超过最大重试次数进入死信队列 log.error("重试第 {} 次,发送死信队列消息:{}", newRetryCount, salesOrder); channel.basicNack(deliveryTag, false, false); } else { // 1 重新通过延时交换机 将消息延时发送到队列 rabbitTemplate.convertAndSend( OrderDirectMQConfig.DELAYED_ORDER_EXCHANGE, OrderDirectMQConfig.ORDER_ROUTER_KEY, salesOrder, message -> { // 5.1 持久化 message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 5.2 设置消息头部属性:重试次数 message.getMessageProperties().setHeader("x-retry-count", newRetryCount); // 5.3 设置消息头部属性:延迟时间 message.getMessageProperties().setHeader("x-delay", calculateDelay(newRetryCount)); return message; } ); log.info("重试第 {} 次,延迟 {}ms 重新发送消息:{}", newRetryCount, calculateDelay(newRetryCount), salesOrder); // 确认原消息 channel.basicAck(deliveryTag, false); } } catch (Exception e) { // 发生异常,拒绝消息并发送到死信队列 log.error("消费异常订单数据: {}, 异常报错信息:",salesOrder, e); // 手动 NACK,失败的直接 拒绝, 不重回队列, 直接写入死信队列 channel.basicNack(deliveryTag, false, false); } } /** * 动态退避策略(示例:10s, 20s, 30s) */ private int calculateDelay(int retryCount) { return 10_000 * retryCount; } }