RabbitMQ入门
基本概念
AMQP协议
高级消息队列协议,基于发布/订阅模式,支持点对点、广播、路由、延迟消息、DLQ(死信队列)等
- 生产者:消息的生成者,发送消息到交换机
- 消费者:消息的消费者,从队列中获取消息并消费
- 交换机(Exchange):消息的交换者,将消息发送到对应的队列
- 队列(Queue):消息的存储容器,消息被发送到队列,然后被消费者获取并消费
- 路由(Routes):转发,就是怎么把消息从一个地方转到另一个地方(比如从生产者转发到某个队列)
安装
-
先安装 Erlang :Erlang26.2.5.9的Windows安装这个语言的性能非常高
-
安装RabbitMQ监控面板:
rabbitmq-plugins.bat enable rabbitmq_shovel rabbitmq_management进入安装位置sbin下cmd执行 -
http://localhost:15672
默认用户名/密码: guest/guest -
创建用户:
rabbitmqctl.bat add_user admin admin,远程服务器部署访问RabbitMQ需要创建用户才能访问 -
RabbitMQ端口占用:
-
4369:RabbitMQ集群管理端口
-
5672:RabbitMQ默认端口,用于客户端连接
-
5671:RabbitMQ SSL端口,用于客户端连接
-
15672:RabbitMQ管理端口,用于管理界面访问
单向发送
Hello World
Java版文档地址
-
一个生产者给一个消费者发消息
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.20.0</version> </dependency> -
发送消息send
public class SingleProducer { private final static String QUEUE_NAME = "hello"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { channel.queueDeclare(QUEUE_NAME, false, false, false, null); String message = "Hello World!"; channel.basicPublish("", QUEUE_NAME, null, message.getBytes()); System.out.println(" [x] Sent '" + message + "'"); } } }
channel.queueDeclare(可重复声明,但是必须保持一致)
- durable (boolean类型,第1个false):队列持久化标志(注意:同名称的消息队列,只能用同样的参数创建一次)
- false:队列不持久化,RabbitMQ重启后队列会丢失
- true:队列持久化,RabbitMQ重启后队列仍然存在
- exclusive (boolean类型,第2个false):独占性标志
- false:队列可以被多个连接访问
- true:队列仅对当前连接可见,连接断开时队列自动删除
- autoDelete (boolean类型,第3个false):自动删除标志
- false:即使没有消费者,队列也不会自动删除
- true:当最后一个消费者取消订阅后,队列自动删除
- arguments (Map<String, Object>类型,null):队列的其他参数配置
- null:不设置额外参数 ,可用于设置队列的TTL、最大长度等高级特性
多消费者(订阅)
场景:多个机器同时去接受并处理
-
消息持久化,指定MessageProperties.PERSISTENT_TEXT_PLAIN参数
channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"));
生产者:
-
使用Scanner持续接受用户输入,测试更方便
public class MultiProducer { private static final String TASK_QUEUE_NAME = "multi_queue"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); Scanner scanner = new Scanner(System.in); while (scanner.hasNext()) { String message = scanner.nextLine(); channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8")); System.out.println(" [x] Sent '" + message + "'"); } } } }
消费者:
-
创建多个消费者,并分别监听同一个队列,测试更方便
-
消息确认机制:channel.basicConsume()方法用于启动消费者监听队列
- 参数说明:
- TASK_QUEUE_NAME:要监听的队列名称
- false:设置为手动确认模式,处理完消息后需要手动发送确认,一般都设置为手动确认
- deliverCallback:消息处理回调函数,定义收到消息后的处理逻辑
- consumerTag -> {}:消费者取消时的回调函数(这里为空实现)
- 参数说明:
-
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
- basicAck方法向RabbitMQ服务器发送确认信号
- delivery.getEnvelope().getDeliveryTag()获取当前消息的唯一标识
- 第二个参数false表示只确认当前这一条消息
-
channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false);
- 拒绝并重新入队消息,并设置重新入队参数为 true,使消息重新放回队列等待其他消费者处理
- 第二个参数 false: 表示是否批量处理多个消息。设为false表示只处理当前这一条消息,不进行批量操作
- 第三个参数 false: 表示消息被否定后是否重新入队。设为false表示拒绝的消息不会重新放回队列,通常会被丢弃或进入死信队列
-
channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false);
-
用于拒绝当前消息
-
第一个参数是消息的唯一标识
-
第二个参数false表示不重新入队,即丢弃该消息或将其发送到死信队列(如有配置)
public class MultiConsumer { private static final String TASK_QUEUE_NAME = "multi_queue"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); final Connection connection = factory.newConnection(); for (int i = 0; i < 2; i++) { final Channel channel = connection.createChannel(); channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); // 控制单个消费者的处理任务 积压 数,每个消费者最多处理1个任务 channel.basicQos(1); int finalI = i; // 定义了如何处理消息 DeliverCallback deliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); try { // 处理工作 System.out.println(" [x] Received '" + "编号" + finalI + ": " + message + "'"); // 停20秒,模拟机器处理能力有限 Thread.sleep(20000); // 指定确认消息 channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } catch (InterruptedException e) { e.printStackTrace(); channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false); } finally { System.out.println(" [x] Done"); channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } }; // 开启消费监听 channel.basicConsume(TASK_QUEUE_NAME, false, deliverCallback, consumerTag -> { }); } } }
-
交换机
场景:多个机器同时去接受并处理任务(尤其是每个机器的处理能力有限)
-
一个生产者给多个队列发消息了,多个消费者从这个队列取消息,1个生产者对多个队列
-
交换机的作用:提供消息转发功能,类似于网络路由器
-
要解决的问题:怎么把消息转发到不同的队列上,好让消费者从不同的队列消费
-
绑定:交换机和队列关联起来,也可以叫路由,算是一个算法或者策略
-
四种类别交换机:direct, topic, headers, fanout
-
fanout: 扇出、广播
-
特点:消息会被转发到所有绑定到该交换机的队列
-
场景:很适用于发布订阅的场景,比如写日志,可以多个系统间共享
-
绑定代码:
channel1.queueBind(queueName1, EXCHANGE_NAME, "绑定规则"); -
生产者:
public class FanoutProducer { private static final String EXCHANGE_NAME = "fanout-exchange"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel() ) { // 创建一个fanout类型的交换机 channel.exchangeDeclare(EXCHANGE_NAME, "fanout"); Scanner scanner = new Scanner(System.in); while (scanner.hasNext()) { String message = scanner.nextLine(); channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8")); System.out.println(" [x] Sent '" + message + "'"); } } } } -
消费者:
public class FanoutConsumer { private static final String EXCHANGE_NAME = "fanout-exchange"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel1 = connection.createChannel(); Channel channel2 = connection.createChannel(); // 创建交换机 channel1.exchangeDeclare(EXCHANGE_NAME, "fanout"); // 创建队列,随机分配一个队列名称 // String queueName = channel.queueDeclare().getQueue(); String queueName1 = "小王的工作队列"; channel1.queueDeclare(queueName1, false, false, false, null); channel1.queueBind(queueName1, EXCHANGE_NAME, ""); String queueName2 = "小李的工作队列"; channel2.queueDeclare(queueName2, false, false, false, null); channel2.queueBind(queueName2, EXCHANGE_NAME, ""); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); DeliverCallback deliverCallback1 = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); System.out.println(" [小王] Received '" + message + "'"); }; DeliverCallback deliverCallback2 = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); System.out.println(" [小李] Received '" + message + "'"); }; channel1.basicConsume(queueName1, true, deliverCallback1, consumerTag -> { }); channel2.basicConsume(queueName2, true, deliverCallback2, consumerTag -> { }); } }
-
-
direct:可以让交换机和队列进行关联,可以指定让交换机把什么样的消息发送给那个队列
-
routingKey:路由键,绑定规则,可以理解为消息携带的key,消息根据key去发送给对应的队列
-
特点:消息会根据路由键转发给特定的队列
-
场景:特定的消息转发给特定的系统或程序
-
生产者:
public class DirectProducer { private static final String EXCHANGE_NAME = "direct_exchange"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { channel.exchangeDeclare(EXCHANGE_NAME, "direct"); Scanner scanner = new Scanner(System.in); while (scanner.hasNext()){ String userInput = scanner.nextLine(); String[] strings = userInput.split(" "); if (strings.length < 1){ continue; } String message = strings[0]; String routingKey = strings[1]; channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8")); System.out.println(" [x] Sent '" + message + " with routing" + routingKey + "'"); } } } } -
消费者:
public class DirectConsumer { private static final String EXCHANGE_NAME = "direct_exchange"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); // 创建交换机 channel.exchangeDeclare(EXCHANGE_NAME, "direct"); String queueName1 = "小网"; channel.queueDeclare(queueName1, false, false, false, null); channel.queueBind(queueName1, EXCHANGE_NAME,"xiaow"); String queueName2 = "小皮"; channel.queueDeclare(queueName2, false, false, false, null); channel.queueBind(queueName2, EXCHANGE_NAME,"xiaop"); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); DeliverCallback xiaowdeliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); System.out.println(" [小网] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); }; DeliverCallback xiaopdeliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); System.out.println(" [小皮] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); }; channel.basicConsume(queueName1, true, xiaowdeliverCallback, consumerTag -> { }); channel.basicConsume(queueName2, true, xiaopdeliverCallback, consumerTag -> { }); } }
-
-
topic:通配符模式,消息会根据一个模糊路由键转发到指定队列
-
场景:特定的一类消息可以交给特定的一类系统或程序来处理
-
绑定关系:可以模糊匹配多个绑定
-
*.:匹配一个单词,比如*.orange,可以匹配orange.orange.orange
-
#.:匹配0个或多个
-
生产者:
public class TopicProducer { private static final String EXCHANGE_NAME = "topic_exchange"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { channel.exchangeDeclare(EXCHANGE_NAME, "topic"); Scanner scanner = new Scanner(System.in); while (scanner.hasNext()) { String userInput = scanner.nextLine(); String[] strings = userInput.split(" "); if (strings.length < 1) { continue; } String message = strings[0]; String routingKey = strings[1]; channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8")); System.out.println(" [x] Sent '" + message + " with routing" + routingKey + "'"); } } } } -
消费者:
public class TopicConsumer { private static final String EXCHANGE_NAME = "topic_exchange"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, "topic"); String queueName1 = "前端"; channel.queueDeclare(queueName1, false, false, false, null); channel.queueBind(queueName1, EXCHANGE_NAME, "*.*.前端"); String queueName2 = "后端"; channel.queueDeclare(queueName2, false, false, false, null); channel.queueBind(queueName2, EXCHANGE_NAME, "*.*.后端"); String queueName3 = "产品"; channel.queueDeclare(queueName3, false, false, false, null); channel.queueBind(queueName3, EXCHANGE_NAME, "*.产品.*");System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); DeliverCallback qianduandeliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); System.out.println(" [前端] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); }; DeliverCallback houduanliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); System.out.println(" [后端] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); }; DeliverCallback chanpinliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); System.out.println(" [产品] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); }; channel.basicConsume(queueName1, true, qianduandeliverCallback, consumerTag -> { }); channel.basicConsume(queueName2, true, houduanliverCallback, consumerTag -> { }); channel.basicConsume(queueName3, true, chanpinliverCallback, consumerTag -> { }); }}
-
-
-
-
RPC: 远程过程调用,RabbitMQ也可以实现RPC,但一般没必要,直接用Dubbo、GRPC等RPC框架就好了
-
headers:消息头模式,消息会根据消息头中的键值对进行匹配转发到指定队列
- 优点:
- 灵活的路由规则:不依赖于路由键(route key),而是基于消息头(header)中的键值对进行匹配,可以实现更复杂的路由逻辑
- 多条件匹配:可以同时基于多个header属性进行匹配,支持更精细的消息分类和路由
- 解耦生产者和消费者:生产者不需要知道具体的路由键,只需要设置相应的消息头即可
- 动态路由:可以在运行时通过修改header值来改变消息路由行为
- 缺点:
- 性能相对较低:相比于direct或topic交换机,headers交换机需要检查每个消息的header属性,匹配过程更复杂,性能开销更大
- 配置复杂:需要为队列绑定时设置复杂的header匹配规则,增加了配置和维护的复杂度
- 可读性较差:相比于直观的路由键,headers的匹配规则不够直观,调试和排查问题相对困难
- 内存消耗较大:需要存储和比较更多的元数据信息,对系统内存资源消耗相对较高
- 优点:
核心机制
-
消息过期:可以给每条消息指定一个有效期,一段时间内未被消费者处理,就过期了
- 消息过期TTL
- 示例场景:消费者(库存系统挂了),一个订单15分钟还没有被库存系统处理,这个订单其实已经失效了,哪怕库存系统再恢复,其实也不用扣减库存
- 适用场景:清理过期数据,模拟延迟队列的实现(不开会员就慢速),专门让某个程序处理过期请求
- 给队列中的消息指定过期时间
- 给某条消息指定过期时间
- 注意:如果某消息处于待消费状态,并且过期时间达到后,消息将被标记为过期,但是,如果消息已经被消费者消费(消费者取了,但还没ack),并且正在处理中,即使过期时间到了,消息任然会被正常处理(ack后才消失)
// 设置消息延时 AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() .expiration("1000") .build(); channel.queueDeclare(QUEUE_NAME, false, false, false, null);// 设置队列延时 Map<String, Object> args = new HashMap<String, Object>(); args.put("x-message-ttl", 5000); // 创建队列声明 channel.queueDeclare(QUEUE_NAME, false, false, false, args); -
消息确认机制
- 一般 auto ack默认设置为false,根据实际情况,收到确认
-
死信队列
-
为了保证消息的可靠性,比如每条消息都成功消费,需要提供一个容错机制,即:失败的消息怎么处理?
-
死信:过期的消息、拒收的消息、消息队列满了、处理失败的消息的统称
-
死信队列:处理死信的队列,给处理不了的消息一个机制,处理不能处理的消息
-
死信交换机(Dead Letter Exchanges)::专门转发死信队列消息的交换机,
-
实现步骤:
- 创建死信交换机和死信队列
- 给失败之后需要容错处理的队列绑定死信交换机
- 可以给要容错的队列指定死信之后的转发规则,死信应该再转发到哪个队列
- 可以通过程序读取死信队列中的消息,进行相应的处理
- 生产者:
public class DlxDirectProducer { private static final String DEAD_EXCHANGE_NAME = "dxl-direct_exchange"; private static final String WORK_EXCHANGE_NAME = "direct2_exchange"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { // 声明死信交换机 channel.exchangeDeclare(DEAD_EXCHANGE_NAME, "direct"); // 创建死信队列 String queueName1 = "老板_死信队列"; channel.queueDeclare(queueName1, false, false, false, null); channel.queueBind(queueName1, DEAD_EXCHANGE_NAME,"laoban"); String queueName2 = "外包_死信队列"; channel.queueDeclare(queueName2, false, false, false, null); channel.queueBind(queueName2, DEAD_EXCHANGE_NAME,"waibao"); DeliverCallback laobandeliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); // 拒绝 channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false); System.out.println(" [老板] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); }; DeliverCallback waibaodeliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false); System.out.println(" [外包] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); }; channel.basicConsume(queueName1, false, laobandeliverCallback, consumerTag -> { }); channel.basicConsume(queueName2, false, waibaodeliverCallback, consumerTag -> { }); Scanner scanner = new Scanner(System.in); while (scanner.hasNext()){ String userInput = scanner.nextLine(); String[] strings = userInput.split(" "); if (strings.length < 1){ continue; } String message = strings[0]; String routingKey = strings[1]; channel.basicPublish(WORK_EXCHANGE_NAME , routingKey, null, message.getBytes("UTF-8")); System.out.println(" [x] Sent '" + message + " with routing " + routingKey + "'"); } } } }- 消费者:
public class DlxDirectConsumer { private static final String DEAD_EXCHANGE_NAME = "dxl-direct_exchange"; private static final String WORK_EXCHANGE_NAME = "direct2_exchange"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); // 创建交换机 channel.exchangeDeclare(WORK_EXCHANGE_NAME, "direct"); // 自定死信队列参数 Map<String, Object> args = new HashMap<String, Object>(); // 要绑定到那个交换机 args.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME); // 指定死信要转发到那个死信队列 args.put("x-dead-letter-routing-key", "waibao"); String queueName1 = "小猫"; channel.queueDeclare(queueName1, false, false, false, args); channel.queueBind(queueName1, WORK_EXCHANGE_NAME, "xiaom"); // 指定死信队列的参数 Map<String, Object> args2 = new HashMap<String, Object>(); args2.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME); args2.put("x-dead-letter-routing-key", "laoban"); String queueName2 = "小够"; channel.queueDeclare(queueName2, false, false, false, args2); channel.queueBind(queueName2, WORK_EXCHANGE_NAME, "xiaog");System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); DeliverCallback xiaomdeliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); // 拒绝 channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false); System.out.println(" [小猫] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); }; DeliverCallback xiaogdeliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false); System.out.println(" [小够] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); }; channel.basicConsume(queueName1, false, xiaomdeliverCallback, consumerTag -> { }); channel.basicConsume(queueName2, false, xiaogdeliverCallback, consumerTag -> { }); }}
RabbitMQ之BI项目实战
基础
-
引入依赖(要与springboot版本一致)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> <version>2.7.2</version> </dependency> -
配置
rabbitmq: host: 127.0.0.1 port: 5672 username: guest password: guest -
创建交换机、队列、绑定关系
/** * 拥有创建测试程序用到的交换机和队列(只在程序启动前执行一次) */ public class MqInitMain { public static void main(String[] args) { try { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); String EXCHANGE_NAME = "code_exchange"; channel.exchangeDeclare(EXCHANGE_NAME, "direct"); String queueName = "code_queue"; channel.queueDeclare(queueName, false, false, false, null); channel.queueBind(queueName, EXCHANGE_NAME, "my_routingKey"); } catch (Exception e) { } } } -
生产者
@Component public class MyMessageProducer { // 消息队列 @Resource private RabbitTemplate rabbitTemplate; /** * 发送消息 * @param exchange * @param routingKey * @param message */ public void sendMessage(String exchange, String routingKey, Object message) { rabbitTemplate.convertAndSend(exchange, routingKey, message); } } -
消费者
@Component @Slf4j public class MyMessageConsumer { // 消息队列 @Resource private RabbitTemplate rabbitTemplate; /** * 指定程序监听的消息队列和确认机制 * @param message * @param channel * @param deliveryTag */ @SneakyThrows @RabbitListener(queues = {"code_queue"}, ackMode = "MANUAL") public void receiveMessage(Object message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) { log.info("receiveMessage: {}", message); channel.basicAck(deliveryTag, false); } } -
测试
@Component @Slf4j public class MyMessageConsumer { // 消息队列 @Resource private RabbitTemplate rabbitTemplate; /** * 指定程序监听的消息队列和确认机制 * @param message * @param channel * @param deliveryTag */ @SneakyThrows @RabbitListener(queues = {"code_queue"}, ackMode = "MANUAL") public void receiveMessage(Object message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) { log.info("receiveMessage: {}", message); channel.basicAck(deliveryTag, false); } }
BI项目改造
以前是把任务提交到线程池,然后在线程池提交中编写处理程序的代码,线程池内排队
如果程序中断了,任务就没了,就丢了
改造后的流程:
- 把任务提交改为向队列发送消息
- 写一个专门的接受消息的程序,处理任务
- 如果程序中断了,消息未被确认,还会重发吗?
- 现在,消息全部集中发到消息队列,你可以部署多个后端,都从同一个地方取任务,从而实现了分布式负载均衡
实现:
- 创建、队列、交换机
1508

被折叠的 条评论
为什么被折叠?



