在rabbitMQ都官方文档中,高级特性又叫做Our Extensions
常用的有:
在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。
confirm
确认模式return
退回模式其中rabbitmq 整个消息投递的路径为:
producer ---> rabbitmq broker ---> exchange ---> queue ---> consumer
我们使用这两个callBack控制消息的可靠性投递
// 生产者
/**
* 确认模式:
* 1. 开启确认模式,在yml中将 publisher-confirms 设置为true
* 2. 在rabbitTemplate中定义回调函数 setConfirmCallback
*/
@Test
public void testConfirm() throws InterruptedException {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 交换机相关配置
* @param b 是否发送成功, true 为成功
* @param s 发送失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
System.out.println("发送消息....");
if (b) {
System.out.println("消息发送成功 " + s);
} else {
System.out.println("消息发送失败 " + s);
}
}
});
rabbitTemplate.convertAndSend("test_Exchange_confirm","confirm","mq confirm ~~~");
Thread.sleep(2000);
}
/**
* 回退模式: 当exchange将消息发送给 queue后,发送失败,才会执行returnCallBack
* 步骤:
* 1. 开启回退模式: publisher-returns: true
* 2. 设置 returnCallBack
* 3. 设置Exchange的处理模式
* 1. 如果消息没有路由到queue,丢弃消息 (默认)
* 2. 如果消息没有路由到queue, 返回给消息发送方 returnCallBack
*/
@Test
public void testReturn() throws InterruptedException {
// 设置交换机处理消息的模式
rabbitTemplate.setMandatory(true);
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("执行returnCallBack");
System.out.println(message);
System.out.println(replyCode);
System.out.println(replyText);
System.out.println(exchange);
System.out.println(routingKey);
}
});
rabbitTemplate.convertAndSend("test_Exchange_confirm","confirm","mq confirm ~~~");
Thread.sleep(3000);
}
步骤:
ConfirmCallback
和ReturnCallBack
两个方法,用来接收消息是否完成发送的信息小结
:
当 RabbitMQ 将消息传递给消费者时,RabbbitMQ需要知道这个消息是否到达,那么消费者共有三种模式:
其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。
但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。
开启手动确认模式
acknowledge-mode: manual
注意 :
当节点将消息传递给使用者时,它必须决定是否应将该消息视为由使用者处理(或至少接收)。由于多个事物(客户端连接、使用者应用等)可能会失败,因此此决策是一个数据安全问题。消息传递协议通常提供一种确认机制,允许使用者确认对其所连接到的节点的传递。是否使用该机制是在消费者订阅时决定的。
根据所使用的确认模式,RabbitMQ 可以认为消息在发送出去(写入 TCP 套接字)或收到显式(“手动”)客户端确认后立即成功传递。手动发送的确认可以是正数或负数,并使用以下协议方法之一:
/**
* consumer Ack步骤:
* 1. 在yml中开启 acknowledge-mode: manual
* 2. 监听器加上RabbitHandler
* 3. 定义方法参数为 (String msg, Channel channel, Message message)
* 4. 如果方法执行成功,调用 channel.basicAck
* 5. 如果方法执行失败,调用 channel.basicNAck
* @param message
*/
@RabbitHandler
public void ListenerMessage(String msg, Channel channel, Message message) throws InterruptedException {
Thread.sleep(1000);
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
System.out.println("接收到消息 :" + msg);
System.out.println("开始处理消息");
int a = 1 / 0;
/**
* basicAck(long deliveryTag, boolean multiple)
* deliveryTag 类似于TCP中的ACK号
* multiple 确认所有消息到达
*/
channel.basicAck(deliveryTag, true);
System.out.println("消息处理成功");
} catch (Exception e) {
try {
/**
* basicNack(long deliveryTag, boolean multiple, boolean requeue)
* requeue : true 表示重新丢回队列
* false 表示直接丢弃
*/
channel.basicNack(deliveryTag, true, true);
System.out.println("消息发送失败。。。");
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
在自动确认模式下,消息在发送后立即被视为已成功传递。这种模式以更高的吞吐量(只要消费者能够跟上)换取降低交付和消费者处理的安全性。这种模式通常被称为“即发即弃”。与手动确认模型不同,如果消费者的TCP连接或通道在成功传递之前关闭,则服务器发送的消息将丢失。因此,应将自动消息确认视为不安全,并且不适合所有工作负荷。
使用自动确认模式时要考虑的另一件重要事情是使用者重载。手动确认模式通常与有界通道预取一起使用,该预取限制通道上未完成(“进行中”)传递的数量。但是,使用自动确认时,根据定义没有这样的限制。因此,消费者可能会被交付速度所淹没,这可能会在内存中积累积压并耗尽堆或使其进程作系统终止。某些客户端库将应用 TCP 背压(停止从套接字读取,直到未处理的交付的积压超过一定限制)。因此,自动确认模式仅推荐给能够高效且稳定地处理交付的消费者。
根据消费端处理能力来决定消费端一次最多可以拉取的消息
属性为: prefetch
例如: prefetch = 1 表示消费端一秒只接受一条消息,直到手动确认接收完毕,才接收下一条消息
值得注意的是,如果prefetch=0表示可以接收任意数量的未确认消息
TTL 全称 Time To Live(存活时间/过期时间)。当消息到达存活时间后,还没有被消费,会被自动清除。
RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。
@Test
public void testSent() throws InterruptedException {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
System.out.println("发送消息....");
if (b) {
System.out.println("消息发送成功 " + s);
} else {
System.out.println("消息发送失败 " + s);
}
}
});
/**
* 消息后处理对象,设置消息的一些参数
*/
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
// 1. 设置过期时间
message.getMessageProperties().setExpiration("10000");
// 2. 返回消息
return message;
}
};
/**
* TTL 过期有两种设置
* 1. 队列整个消息过期 : 需要在队列参数中设置 "x-message-ttl", 10000
* 2. 单个消息设置过期:
* 如果设置了队列的过期时间和单个消息的过期时间,谁时间段谁就生效
* 队列过期后,会将所有消息都清除
* RabbitMQ并不会轮询消息,而是消息到了队列顶端,才会判断消息是否过期
*/
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("test_Exchange_TTL","ttl.hhh","mq ttl ~~~" + i, messagePostProcessor);
}
Thread.sleep(100000);
}
消息成为死信的三种方式:
配置死信队列
/** 测试死信队列
* 分别创建正常的队列(test_queue_dlx)和正常的交换机(test-exchange_dlx)
* 创建死信队列(queue_dlx)和死信交换机(exchange_dlx)
* 正常队列绑定死信交换机,并且指定
* x-dead-letter-exchange
* x-dead-letter-routing-key
*/
/**
* 创建正常队列和正常交换机
*/
@Bean("test_queue_dlx")
public Queue testQueueDlx() {
return QueueBuilder.durable("test_queue_dlx")
.withArgument("x-dead-letter-exchange", "exchange_dlx") //指定死信交换机
.withArgument("x-dead-letter-routing-key", "dlx.hhh") // 指定路由key
.withArgument("x-message-ttl", 10000) //设置队列过期时间
.withArgument("x-max-length", 10) //设置最大长度
.build();
}
@Bean("test_exchange_dlx")
public Exchange testExchangeDlx() {
return ExchangeBuilder.topicExchange("test_exchange_dlx").build();
}
@Bean
public Binding DlxBinding(@Qualifier("test_queue_dlx") Queue queue, @Qualifier("test_exchange_dlx") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("test.dlx.#").noargs();
}
/**
* 创建死信队列和死信交换机
*/
@Bean("queue_dlx")
public Queue QueueDlx() {
return QueueBuilder.durable("queue_dlx").build();
}
@Bean("exchange_dlx")
public Exchange ExchangeDlx() {
return ExchangeBuilder.topicExchange("exchange_dlx").build();
}
@Bean
public Binding DlxBindingDead(@Qualifier("queue_dlx") Queue queue, @Qualifier("exchange_dlx") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
}
测试死信队列
// 1. 队列消息过期
rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.hhh", "我那些残梦灵翼九霄");
// 2. 队列长度超出,设置队列长度为10
for (int i = 0; i < 20; i++) {
rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.hhh", "我那些残梦灵翼九霄");
}
//3. 测试消息拒收
rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.hhh", "我那些残梦灵翼九霄");
使用TTL + 死信队列,就是延迟队列
TTL用于定时,消费者从死信队列中取值