死信队列
-
1.什么叫做死信?
死信,顾名思义就是处于死亡状态信息,在MQ中的定义就是无法被消费的消息。MQ消息的流程,一般由producer发送的broker然后由exchange转发到各个队列中,consumer从队列中获取到消息并进行消费。但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,接收死信消息的队列就叫死信队列。
-
2.什么样的消息才被称作死信?
一个消息如果满足下列条件之一,会进入到死信路由:
1)消息的TTL到了,如果消息过期了还没有被消费,那么这条消息就不会被消费了,会进入到死信队列。
2)队列消息长度达到限制。
3)消费者拒收消息,并且不将消息放入原来的队列中让他重新发送。
一旦某个队列中有消息满足了成为死信的条件,如果该队列设置了死信交换机(Dead Letter Exchange)和死信路由键,那么满足死信条件的消息就会交由死信交换机,死信交换机会根据死信路由键将死信消息投递到对应的死信队列
注意:死信交换机本质上就是一个普通的交换机,只是因为队列设置了参数指定了死信交换机,这个普通的交换机才成为了死信的接收者
-
3.设置TTL时间的方式
TTL:TTL全称Time To Live(存活时间/过期时间),是指消息在队列中最大存活的时间,如果TTL到了,还没有进行消费,那么这条消息就会过期进入到死信队列中。
RabbitMq 有两种设置TTL的方式:
1)通过expiration字段来设置每一条消息的过期时间AMQP.BasicProperties basicProperties = new AMQP.BasicProperties .Builder() .expiration("5000").build();
2)通过x-message-ttl参数来设置队列中消息的过期时间
// 设置TTL为60s map.put("x-message-ttl", 60000);
如果同时设置的expiration和x-message-ttl,那么以值较小的为准。
-
4.死信队列的绑定
死信队列的绑定主要是通过两个参数实现的
x-dead-letter-exchange: 指定死信消息发送到目的交换机
x-dead-letter-routing-key:死信消息通过该路由键进行转发// 设置死信的目的交换机 map.put("x-dead-letter-exchange", "order.event.exchange"); // 设置死信交给目的交换机时的路由键 map.put("x-dead-letter-routing-key", "order.release");
-
5.过期消息测试
private static final String EXCHANGE_NAME = "order.event.exchange"; private static final String ROUT_KEY = "order.release"; private static final String DEAD_QUEUE = "order.release.queue"; private static final String DELAY_QUEUE = "order.delay.queue"; public static void main(String[] args) throws IOException, TimeoutException { Channel channel = ChannelUtils.getChannel(); channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); // 声明死信队列 channel.queueDeclare(DEAD_QUEUE, true, false,false, null); // 声明死信队列跟交换机的关系 channel.queueBind(DEAD_QUEUE, EXCHANGE_NAME, ROUT_KEY); // 创建队列需要的参数 Map<String, Object> map = new HashMap<>(); // 设置TTL为60s map.put("x-message-ttl", 60000); // 设置死信的目的交换机 map.put("x-dead-letter-exchange", "order.event.exchange"); // 设置死信交给目的交换机时的路由键 map.put("x-dead-letter-routing-key", "order.release"); // 声明延时队列 channel.queueDeclare(DELAY_QUEUE, true, false,false, map); // 声明死信队列跟交换机的关系 channel.queueBind(DELAY_QUEUE, EXCHANGE_NAME, "order.create"); Scanner scanner = new Scanner(System.in); while (scanner.hasNext()) { String next = scanner.next(); channel.basicPublish(EXCHANGE_NAME,"order.create", null, next.getBytes()); System.out.println("发送时间为:"+ new Date(System.currentTimeMillis())); } }
生产者发送了五条消息到MQ
一开始五条消息均在消息队列中。 经过一分钟等待之后,五条消息均进入到死信队列中。 -
6.设置消息队列中的最大数量
// 设置TTL为60s // map.put("x-message-ttl", 60000); // 设置队列中最大数量为5 map.put("x-max-length", 5);
一共发送8条消息
可以看出5条处于正常队列中,3条处于死信队列中。
-
7.拒绝消息的消费
private static final String DEAD_QUEUE = "order.delay.queue"; public static void main(String[] args) throws IOException, TimeoutException { Channel channel = ChannelUtils.getChannel(); channel.basicConsume(DEAD_QUEUE, false, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("获取到消息的时间:" + new Date(System.currentTimeMillis())); System.out.println("获取到的消息内容:" + new String(body) ); // 拒绝某条消息,并且禁止重新放入队列中 channel.basicReject(envelope.getDeliveryTag(), false); } }); }
由以上三图可得知,生产者发送了三条消息,而消费者接收了三条消息但是都拒绝了消费,这三条消息就被放入到死信队列中了。
延时队列
-
1.什么是延时队列?
Mq的正常消息队列都是按顺序不定时进行消费的,而延时队列则是指期望队列中的消息在一定时间后再被进行消费处理。 -
2.业务场景
1)订单生成之后,30分钟没有进行消费,则取消订单
2)账号七天未登录进行用户通知提醒 -
3.实现方式
1)指定TTL时间参数
2)绑定死信交换机
3)绑定死信队列路由键这样最终我们只要从指定的死信队列中取出的消息就是延时消息了
考虑这样一个场景,一个订单模块生成订单后,要在1分钟后检查订单是否已经支付,未支付则取消订单,那么就可以用延时队列来实现:
1、在rabbitmq中创建一个交换机order.event.exchange,绑定两个队列:
延时队列order.delay.queue,绑定路由键为order.create
死信队列order.release.queue,绑定路由键为order.release
2、生成订单后,将消息发送给交换机order.event.exchange,指定路由键为order.create,然后交换机根据路由键将消息交给延时队列order.delay.queue
3、延时队列order.delay.queue设置了3个参数
x-message-ttl=60000,指定队列中消息的TTL为60秒
x-dead-letter-exchange=order.event.exchange,指定队列中消息成为死信后交给交换机 order.event.exchange
x-dead-letter-routing-key=order.release,指定死信交给交换机order.event.exchange时用的路由键
4、延时队列order.delay.queue中的消息成为死信后,会再次交还给交换机order.event.exchange,然后根据路由键会到达死信队列order.release.queue。从死信队列中取出的消息就是延时了30分钟的消息,这样就可以检查订单是否已支付了
生产者代码:
private static final String EXCHANGE_NAME = "order.event.exchange";
private static final String ROUT_KEY = "order.release";
private static final String DEAD_QUEUE = "order.release.queue";
private static final String DELAY_QUEUE = "order.delay.queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = ChannelUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 声明死信队列
channel.queueDeclare(DEAD_QUEUE, true, false,false, null);
// 声明死信队列跟交换机的关系
channel.queueBind(DEAD_QUEUE, EXCHANGE_NAME, ROUT_KEY);
// 创建队列需要的参数
Map<String, Object> map = new HashMap<>();
// 设置TTL为60s
map.put("x-message-ttl", 60000);
// 设置队列中最大数量为5
// map.put("x-max-length", 5);
// 设置死信的目的交换机
map.put("x-dead-letter-exchange", "order.event.exchange");
// 设置死信交给目的交换机时的路由键
map.put("x-dead-letter-routing-key", "order.release");
// 声明延时队列
channel.queueDeclare(DELAY_QUEUE, true, false,false, map);
// 声明死信队列跟交换机的关系
channel.queueBind(DELAY_QUEUE, EXCHANGE_NAME, "order.create");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String next = scanner.next();
channel.basicPublish(EXCHANGE_NAME,"order.create", null, next.getBytes());
System.out.println("发送时间为:"+ new Date(System.currentTimeMillis()));
}
}
死信消费者代码:
private static final String DEAD_QUEUE = "order.release.queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = ChannelUtils.getChannel();
channel.basicConsume(DEAD_QUEUE, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("获取到消息的时间:" + new Date(System.currentTimeMillis()));
System.out.println("获取到的消息内容:" + new String(body) );
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
启动程序,从生产者发送三条消息
死信队列的消费者获取到三条消息
对比时间可以发现,我们实现了延时1分钟消费消息的效果。这就是延时队列的实现