6. RabbitMQ 死信队列的详细操作编写
文章目录
- 6. RabbitMQ 死信队列的详细操作编写
- 1. 死信的概念
- 2. 消息 TTL 过期(触发死信队列)
- 3. 队列超过队列的最大长度(触发死信队列)
- 4. 消息被拒(触发死信队列)
- 5. 最后:
1. 死信的概念
先从概念上解释上搞清楚这个定义,死信,顾名思义就是无法被消费者读取/消费的消息 。字面意思可以这样理解,一般来说,producer 将消息投递到 broker 或者直接到 queue 里了,consumer 从 queue 取出消息进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费 ,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有与之对应的 ——> 死信队列 。
应用场景: 为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息消费发生异常时,将消息投入死信队列中,还有比如说:用户在商城下单成功并点击去支付后,在指定时候未支付时,自动失效。
死信的来源: (死信的常见方式,大概时如下三种方式:)
- 消息的
TTL
过期。 - 队列达到最大长度(队列满了,无法再添加数据到 RabbitMQ 当中了)
- 当消息被拒绝(
basic.reject 或 basic.nack
) 并且requeue = fasle
。
死信的实战案例:
2. 消息 TTL 过期(触发死信队列)
生产者代码
package com.rainbowsea.rabbitmq.eight;import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;import java.io.IOException;
import java.util.concurrent.TimeoutException;/*** 死信队列——> 生产者代码*/
public class Producer {// 普通交换机的名称public static final String NORMAL_EXCHANGE = "normal_exchange";public static void main(String[] args) throws IOException, TimeoutException {Channel channel = RabbitMQUtils.getChannel();// 死信消息,设置 TTL时间AMQP.BasicProperties properties =new AMQP.BasicProperties().builder().expiration("10000").build();for (int i = 0; i < 11; i++) {String message = "info" + i;channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());//channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", null, message.getBytes());}}}
消费者 C1 代码(启动之后关闭该消费者 模拟其接收不到消息)
特别说明: 配置正常队列,无法处理的队列信息,转发给死信队列的,配置,只需在该正常队列当中配置映射死信队列的信息配置即可,而死信队列就是作为一个正常的队列处理读取/消费死信队列当中的内容即可。
package com.rainbowsea.rabbitmq.eight;import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;/*** 死信队列-消费者1(正常队列)*/
public class Consumer01 {// 普通交换机的名称public static final String NORMAL_EXCHANGE = "normal_exchange";// 死信交换机的名称public static final String DEAD_EXCHANGE = "dead_exchange";// 普通队列的名称public static final String NORMAL_QUEUE = "normal_queue";// 死信队列的名称public static final String DEAD_QUEUE = "dead_queue";public static void main(String[] args) throws IOException, TimeoutException {Channel channel = RabbitMQUtils.getChannel();// 声明死信和普通交换机类型为 directchannel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);// 声明普通队列Map<String, Object> arguments = new HashMap<>();// 过期时间 10s = 10000ms 参数 key 是固定值的不可以随便写//arguments.put("x-message-ttl",100000);//正常队列设置死信交换机 参数 key 是固定值arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);// 设置死信RoutingKeyarguments.put("x-dead-letter-routing-key", "lisi");// 声明的是一个正常的队列channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);//// 声明死信队列channel.queueDeclare(DEAD_QUEUE, false, false, false, null);// 绑定普通的交换机与普通的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");System.out.println("等待接收消息");// 接收消息DeliverCallback deliverCallback = (consumerTag, message) -> {System.out.println("Consumer01 正常队列接收到的消息" + new String(message.getBody(), "UTF-8"));};channel.basicConsume(NORMAL_QUEUE, true, deliverCallback, consumerTag -> {});}
}
消费者 C2 代码(以上步骤完成后 启动 C2 消费者 它消费死信队列里面的消息)
package com.rainbowsea.rabbitmq.eight;import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;/*** 死信队列-消费者1(死信队列)*/
public class Consumer02 {;// 死信交换机的名称public static final String DEAD_EXCHANGE = "dead_exchange";// 死信队列的名称public static final String DEAD_QUEUE = "dead_queue";public static void main(String[] args) throws IOException, TimeoutException {Channel channel = RabbitMQUtils.getChannel();// 声明死信和普通交换机类型为 directchannel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);// 绑定死信的交换机与死信的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");// 接收消息DeliverCallback deliverCallback = (consumerTag, message) -> {System.out.println("Consumer02 死信接收到的消息" + new String(message.getBody(), "UTF-8"));};channel.basicConsume(DEAD_QUEUE, true, deliverCallback, consumerTag -> {});}
}
3. 队列超过队列的最大长度(触发死信队列)
- 基于上面代码:将生产者代码去掉 TTL 属性
package com.rainbowsea.rabbitmq.eight;import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;import java.io.IOException;
import java.util.concurrent.TimeoutException;/*** 死信队列——> 生产者代码*/
public class Producer {// 普通交换机的名称public static final String NORMAL_EXCHANGE = "normal_exchange";public static void main(String[] args) throws IOException, TimeoutException {Channel channel = RabbitMQUtils.getChannel();// 死信消息,设置 TTL时间//AMQP.BasicProperties properties =// new AMQP.BasicProperties()// .builder().expiration("10000").build();for (int i = 0; i < 11; i++) {String message = "info" + i;//channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", null, message.getBytes());}}}
- C1 消费者修改以下代码(启动之后关闭该消费者 模拟其接收不到消息)
注意此时需要把原先队列删除 因为参数改变了,因为 RabbitMQ 不可以将一个一开始形态的队列,修改成另外一个形态的队列。所以需要删除后在创建。
package com.rainbowsea.rabbitmq.eight;import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;/*** 死信队列-消费者1(正常队列)*/
public class Consumer01 {// 普通交换机的名称public static final String NORMAL_EXCHANGE = "normal_exchange";// 死信交换机的名称public static final String DEAD_EXCHANGE = "dead_exchange";// 普通队列的名称public static final String NORMAL_QUEUE = "normal_queue";// 死信队列的名称public static final String DEAD_QUEUE = "dead_queue";public static void main(String[] args) throws IOException, TimeoutException {Channel channel = RabbitMQUtils.getChannel();// 声明死信和普通交换机类型为 directchannel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);// 声明普通队列Map<String, Object> arguments = new HashMap<>();// 过期时间 10s = 10000ms 参数 key 是固定值的不可以随便写//arguments.put("x-message-ttl",100000);//正常队列设置死信交换机 参数 key 是固定值arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);// 设置死信RoutingKeyarguments.put("x-dead-letter-routing-key", "lisi");// 设置正常队列的长度的限制,x-max-length参数 key 是固定值的,不可以随便写arguments.put("x-max-length",6);// 声明的是一个正常的队列channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);//// 声明死信队列channel.queueDeclare(DEAD_QUEUE, false, false, false, null);// 绑定普通的交换机与普通的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");System.out.println("等待接收消息");// 接收消息DeliverCallback deliverCallback = (consumerTag, message) -> {System.out.println("Consumer01 正常队列接收到的消息" + new String(message.getBody(), "UTF-8"));};channel.basicConsume(NORMAL_QUEUE, true, deliverCallback, consumerTag -> {});}
}
同时 C1 正常队列也是读取到信息了
然后,启动之后关闭该消费者C1, 模拟其接收不到消息
- C2 消费者代码不变(启动 C2 消费者)
启动消费者 1 然后再启动消费者 2
package com.rainbowsea.rabbitmq.eight;import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;/*** 死信队列-消费者1(死信队列)*/
public class Consumer02 {;// 死信交换机的名称public static final String DEAD_EXCHANGE = "dead_exchange";// 死信队列的名称public static final String DEAD_QUEUE = "dead_queue";public static void main(String[] args) throws IOException, TimeoutException {Channel channel = RabbitMQUtils.getChannel();// 声明死信和普通交换机类型为 directchannel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);// 绑定死信的交换机与死信的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");// 接收消息DeliverCallback deliverCallback = (consumerTag, message) -> {System.out.println("Consumer02 死信接收到的消息" + new String(message.getBody(), "UTF-8"));};channel.basicConsume(DEAD_QUEUE, true, deliverCallback, consumerTag -> {});}
}
4. 消息被拒(触发死信队列)
- .消息生产者代码同上生产者一致,不做修改
package com.rainbowsea.rabbitmq.eight;import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;import java.io.IOException;
import java.util.concurrent.TimeoutException;/*** 死信队列——> 生产者代码*/
public class Producer {// 普通交换机的名称public static final String NORMAL_EXCHANGE = "normal_exchange";public static void main(String[] args) throws IOException, TimeoutException {Channel channel = RabbitMQUtils.getChannel();// 死信消息,设置 TTL时间//AMQP.BasicProperties properties =// new AMQP.BasicProperties()// .builder().expiration("10000").build();for (int i = 0; i < 11; i++) {String message = "info" + i;//channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", null, message.getBytes());}}}
- C1 消费者代码(启动之后关闭该消费者 模拟其接收不到消息) 添加上拒绝消息的操作。
注意此时需要把原先队列删除 因为参数改变了,因为 RabbitMQ 不可以将一个一开始形态的队列,修改成另外一个形态的队列。所以需要删除后在创建。
// 接收消息DeliverCallback deliverCallback = (consumerTag, message) -> {// 获取到消息信息String msg = new String(message.getBody(), "UTF-8");if (msg.equals("info5")) {System.out.println("Consumer01 接收的消息是: " + msg + "此消息被 Consumer01 拒绝了");} else {System.out.println("Consumer01 接收的消息: " + msg);}};
package com.rainbowsea.rabbitmq.eight;import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;/*** 死信队列-消费者1(正常队列)*/
public class Consumer01 {// 普通交换机的名称public static final String NORMAL_EXCHANGE = "normal_exchange";// 死信交换机的名称public static final String DEAD_EXCHANGE = "dead_exchange";// 普通队列的名称public static final String NORMAL_QUEUE = "normal_queue";// 死信队列的名称public static final String DEAD_QUEUE = "dead_queue";public static void main(String[] args) throws IOException, TimeoutException {Channel channel = RabbitMQUtils.getChannel();// 声明死信和普通交换机类型为 directchannel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);// 声明普通队列Map<String, Object> arguments = new HashMap<>();// 过期时间 10s = 10000ms 参数 key 是固定值的不可以随便写//arguments.put("x-message-ttl",100000);//正常队列设置死信交换机 参数 key 是固定值arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);// 设置死信RoutingKeyarguments.put("x-dead-letter-routing-key", "lisi");// 设置正常队列的长度的限制,x-max-length参数 key 是固定值的,不可以随便写//arguments.put("x-max-length", 6);// 声明的是一个正常的队列channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);//// 声明死信队列channel.queueDeclare(DEAD_QUEUE, false, false, false, null);// 绑定普通的交换机与普通的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");System.out.println("等待接收消息");// 接收消息DeliverCallback deliverCallback = (consumerTag, message) -> {// 获取到消息信息String msg = new String(message.getBody(), "UTF-8");if (msg.equals("info5")) {System.out.println("Consumer01 接收的消息是: " + msg + "此消息被 Consumer01 拒绝了");// 拒绝对应 message.getEnvelope().getDeliveryTag() 的信息channel.basicReject(message.getEnvelope().getDeliveryTag(), false);} else {System.out.println("Consumer01 接收的消息: " + msg);// 手动应答,接收消息channel.basicAck(message.getEnvelope().getDeliveryTag(), false);}};channel.basicConsume(NORMAL_QUEUE, false, deliverCallback, consumerTag -> {});}
}
- C2 消费者代码不变;启动消费者 1 然后再启动消费者 2
package com.rainbowsea.rabbitmq.eight;import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;/*** 死信队列-消费者1(死信队列)*/
public class Consumer02 {;// 死信交换机的名称public static final String DEAD_EXCHANGE = "dead_exchange";// 死信队列的名称public static final String DEAD_QUEUE = "dead_queue";public static void main(String[] args) throws IOException, TimeoutException {Channel channel = RabbitMQUtils.getChannel();// 声明死信和普通交换机类型为 directchannel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);// 绑定死信的交换机与死信的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");// 接收消息DeliverCallback deliverCallback = (consumerTag, message) -> {System.out.println("Consumer02 死信接收到的消息" + new String(message.getBody(), "UTF-8"));};channel.basicConsume(DEAD_QUEUE, true, deliverCallback, consumerTag -> {});}
}
总结: 上述的,消息TTL过期,队列超过最大长度,消息被拒等等这三种方式,都是让消息无法被正常消费者读取掉,由于消费者没有读取到该消息,有为了防止,该消息被丢失,所以就将其消息放入到一个死信交换机——>死信队列中 ,最后让一个死信消费者读取到保存起来,这样队列当中的消息就没有被丢失了。
特别说明: 配置正常队列,无法处理的队列信息,转发给死信队列的,配置,只需在该正常队列当中配置映射死信队列的信息配置即可,而死信队列就是作为一个正常的队列处理读取/消费死信队列当中的内容即可。
// 普通交换机的名称public static final String NORMAL_EXCHANGE = "normal_exchange";// 死信交换机的名称public static final String DEAD_EXCHANGE = "dead_exchange";// 普通队列的名称public static final String NORMAL_QUEUE = "normal_queue";// 死信队列的名称public static final String DEAD_QUEUE = "dead_queue"; Channel channel = RabbitMQUtils.getChannel();// 声明死信和普通交换机类型为 directchannel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);// 声明普通队列Map<String, Object> arguments = new HashMap<>();// 过期时间 10s = 10000ms 参数 key 是固定值的不可以随便写//arguments.put("x-message-ttl",100000);//正常队列设置死信交换机 参数 key 是固定值arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);// 设置死信RoutingKeyarguments.put("x-dead-letter-routing-key", "lisi");// 设置正常队列的长度的限制,x-max-length参数 key 是固定值的,不可以随便写//arguments.put("x-max-length", 6);// 声明的是一个正常的队列channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);//// 声明死信队列channel.queueDeclare(DEAD_QUEUE, false, false, false, null);// 绑定普通的交换机与普通的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");System.out.println("等待接收消息");
5. 最后:
“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”