目录
在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。
rabbitmq 整个消息投递的路径为:
producer--->rabbitmq broker--->exchange--->queue--->consumer
我们将利用这两个 callback 控制消息的可靠性投递
-
-
"connectionFactory" host="${rabbitmq.host}" - port="${rabbitmq.port}"
- username="${rabbitmq.username}"
- password="${rabbitmq.password}"
- virtual-host="${rabbitmq.virtual-host}"
- publisher-confirms="true"
- publisher-returns="true"
- />
- /**
- * 确认模式:
- * 步骤:
- * 1. 确认模式开启:ConnectionFactory中开启publisher-confirms="true"
- * 2. 在rabbitTemplate定义ConfirmCallBack回调函数
- */
-
- //2. 定义回调
- rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
- /**
- *
- * @param correlationData 相关配置信息
- * @param ack exchange交换机 是否成功收到了消息。true 成功,false代表失败
- * @param cause 失败原因
- */
- @Override
- public void confirm(CorrelationData correlationData, boolean ack, String cause) {
- System.out.println("confirm方法被执行了....");
-
- if (ack) {
- //接收成功
- System.out.println("接收成功消息" + cause);
- } else {
- //接收失败
- System.out.println("接收失败消息" + cause);
- //做一些处理,让消息再次发送。
- }
- }
- });
-
- //3. 发送消息
- rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm....");
-
- /**
- * 回退模式: 当消息发送给Exchange后,Exchange路由到Queue失败是 才会执行 ReturnCallBack
- * 步骤:
- * 1. 开启回退模式:publisher-returns="true"
- * 2. 设置ReturnCallBack
- * 3. 设置Exchange处理消息的模式:
- * 1. 如果消息没有路由到Queue,则丢弃消息(默认)
- * 2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack
- */
-
-
- //设置交换机处理失败消息的模式
- rabbitTemplate.setMandatory(true);
-
- //2.设置ReturnCallBack
- rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
- /**
- *
- * @param message 消息对象
- * @param replyCode 错误码
- * @param replyText 错误信息
- * @param exchange 交换机
- * @param routingKey 路由键
- */
- @Override
- public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
- System.out.println("return 执行了....");
-
- System.out.println(message);
- System.out.println(replyCode);
- System.out.println(replyText);
- System.out.println(exchange);
- System.out.println(routingKey);
-
- //处理
- }
- });
-
-
- //3. 发送消息
- rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm....");
输出结果
return 执行了.... (Body:'message confirm....' MessageProperties [headers={}, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0]) 312 NO_ROUTE test_exchange_confirm confirm11
这里有一个坑 ConfirmCallback和returnCallback是异步的,他在执行的之后我们实际上已经关闭了rabbitmq资源 ,所以可以在最后添加上:
Thread.sleep(2000);
不然消息找不到回来的路了哈哈哈哈
在RabbitMQ中也提供了事务机制,但是性能较差,此处不做讲解。
ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
有三种确认方式:
其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。
如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。
-
- /**
- * Consumer ACK机制:
- * 1. 设置手动签收。acknowledge="manual"
- * 2. 让监听器类实现ChannelAwareMessageListener接口
- * 3. 如果消息成功处理,则调用channel的 basicAck()签收
- * 4. 如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新发送给consumer
- *
- *
- */
-
- @Component
- public class AckListener implements ChannelAwareMessageListener {
-
- @Override
- public void onMessage(Message message, Channel channel) throws Exception {
- long deliveryTag = message.getMessageProperties().getDeliveryTag();
-
- try {
- //1.接收转换消息
- System.out.println(new String(message.getBody()));
-
- //2. 处理业务逻辑
- System.out.println("处理业务逻辑...");
- int i = 3/0;//出现错误
- //3. 手动签收
- channel.basicAck(deliveryTag,true);
- } catch (Exception e) {
- //e.printStackTrace();
-
- //4.拒绝签收
- /*
- 第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
- */
- channel.basicNack(deliveryTag,true,true);
- //channel.basicReject(deliveryTag,true);
- }
- }
- }
在rabbit:listener-container标签中设置acknowledge属性,设置ack方式 none:自动确认,manual:手 动确认
-
-
"connectionFactory" acknowledge="manual" > -
"AckListener" queue-names="test_queue_confirm"> -

-
-
"connectionFactory" acknowledge="manual" prefetch="1" >
-
- /**
- * Consumer 限流机制
- * 1. 确保ack机制为手动确认。
- * 2. listener-container配置属性
- * perfetch = 1,表示消费端每次从mq拉去一条消息来消费,直到手动确认消费完毕后,才会继续拉去下一条消息。
- */
-
- @Component
- public class QosListener implements ChannelAwareMessageListener {
-
- @Override
- public void onMessage(Message message, Channel channel) throws Exception {
-
- Thread.sleep(1000);
- //1.获取消息
- System.out.println(new String(message.getBody()));
-
- //2. 处理业务逻辑
-
- //3. 签收
- channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
-
- }
- }
当消息到达存活时间后,还没有被消费,会被自动清除。
RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。


-
- <rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
-
- <rabbit:queue-arguments>
-
- <entry key="x-message-ttl" value="100000" value-type="java.lang.Integer">entry>
- rabbit:queue-arguments>
-
- rabbit:queue>
-
-
- /**
- * TTL:过期时间
- * 1. 队列统一过期
- *
- * 2. 消息单独过期
- *
- *
- * 如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。
- * 队列过期后,会将队列所有消息全部移除。
- * 消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉)
- *
- */
-
- // 消息后处理对象,设置一些消息的参数信息
- MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
-
- @Override
- public Message postProcessMessage(Message message) throws AmqpException {
- //1.设置message的信息
- message.getMessageProperties().setExpiration("5000");//消息的过期时间
- //2.返回该消息
- return message;
- }
- };
-
-
-
- for (int i = 0; i < 10; i++) {
- if(i == 5){
- //消息单独过期
- rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....",messagePostProcessor);
- }else{
- //不过期的消息
- rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....");
-
- }
-
- }
-
如果两者都进行了设置,以时间短的为准
DLX Dead Letter Exchange 当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。
- //4.拒绝签收,不重回队列 requeue=false
- channel.basicNack(deliveryTag,true,false);

给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key


-
-
- <rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
-
- <rabbit:queue-arguments>
-
- <entry key="x-dead-letter-exchange" value="exchange_dlx" />
-
-
- <entry key="x-dead-letter-routing-key" value="dlx.hehe" />
-
-
- <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" />
-
- <entry key="x-max-length" value="10" value-type="java.lang.Integer" />
- rabbit:queue-arguments>
- rabbit:queue>
- <rabbit:topic-exchange name="test_exchange_dlx">
- <rabbit:bindings>
- <rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx">rabbit:binding>
- rabbit:bindings>
- rabbit:topic-exchange>
-
-
- <rabbit:queue name="queue_dlx" id="queue_dlx">rabbit:queue>
- <rabbit:topic-exchange name="exchange_dlx">
- <rabbit:bindings>
- <rabbit:binding pattern="dlx.#" queue="queue_dlx">rabbit:binding>
- rabbit:bindings>
- rabbit:topic-exchange>
即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
很可惜,在RabbitMQ中并未提供延迟队列功能。
需求:
实现方式:

但是可以使用:TTL+死信队列 组合实现延迟队列的效果。

订单系统
-
-
- <rabbit:queue id="order_queue" name="order_queue">
-
- <rabbit:queue-arguments>
- <entry key="x-dead-letter-exchange" value="order_exchange_dlx" />
- <entry key="x-dead-letter-routing-key" value="dlx.order.cancel" />
- <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" />
-
- rabbit:queue-arguments>
-
- rabbit:queue>
- <rabbit:topic-exchange name="order_exchange">
- <rabbit:bindings>
- <rabbit:binding pattern="order.#" queue="order_queue">rabbit:binding>
- rabbit:bindings>
- rabbit:topic-exchange>
-
-
- <rabbit:queue id="order_queue_dlx" name="order_queue_dlx">rabbit:queue>
- <rabbit:topic-exchange name="order_exchange_dlx">
- <rabbit:bindings>
- <rabbit:binding pattern="dlx.order.#" queue="order_queue_dlx">rabbit:binding>
- rabbit:bindings>
- rabbit:topic-exchange>
库存系统业务逻辑
- @Component
- public class OrderListener implements ChannelAwareMessageListener {
- @Override
- public void onMessage(Message message, Channel channel) throws Exception {
- long deliveryTag = message.getMessageProperties().getDeliveryTag();
-
- try {
- //1.接收转换消息
- System.out.println(new String(message.getBody()));
-
- //2. 处理业务逻辑
- System.out.println("处理业务逻辑...");
- System.out.println("根据订单id查询其状态...");
- System.out.println("判断状态是否为支付成功");
- System.out.println("取消订单,回滚库存....");
- //3. 手动签收
- channel.basicAck(deliveryTag,true);
- } catch (Exception e) {
- //e.printStackTrace();
- System.out.println("出现异常,拒绝接受");
- //4.拒绝签收,不重回队列 requeue=false
- channel.basicNack(deliveryTag,true,false);
- }
- }
- }
-
-
"connectionFactory" acknowledge="manual" prefetch="1" > -
-
"orderListener" queue-names="order_queue_dlx"> -
这里有个坑 监听的是死信队列
RabbitMQ默认日志存放路径: /var/log/rabbitmq/rabbit@xxx.log
日志包含了RabbitMQ的版本号、Erlang的版本号、RabbitMQ服务节点名称、cookie的hash值、 RabbitMQ配置文件地址、内存限制、磁盘限制、默认账户guest的创建以及权限配置等等

查看队列
# rabbitmqctl list_queues
查看exchanges
# rabbitmqctl list_exchanges
查看用户
# rabbitmqctl list_users
查看连接
# rabbitmqctl list_connections
查看消费者信息
# rabbitmqctl list_consumers
查看环境变量
# rabbitmqctl environment
查看未被确认的队列
# rabbitmqctl list_queues name messages_unacknowledged
查看单个队列的内存使用
# rabbitmqctl list_queues name memory
查看准备就绪的队列
# rabbitmqctl list_queues name messages_ready
在RabbitMQ中可以使用Firehose和rabbitmq_tracing插件功能来实现消息追踪。
firehose的机制是将生产者投递给rabbitmq的消息,rabbitmq投递给消费者的消息按照指定的格式发送到默认的exchange上。这个默认的exchange的名称为amq.rabbitmq.trace,它是一个topic类型的exchange。发送到这个exchange上的消息的routing key为 publish.exchangename 和deliver.queuename。其中exchangename和queuename为实际exchange和queue的名称,分别对应生产者投递到exchange的消息,和消费者从queue上获取的消息。
注意:打开 trace 会影响消息写入功能,适当打开后请关闭。
rabbitmqctl trace_on:开启Firehose命令
rabbitmqctl trace_off:关闭Firehose命令

