RabbitMQ的高级特性
消息可靠性投递
在使用RabbitMQ的时候,为了防止消息丢失或者投递失败,RabbitMQ为我们提供了两种方式来控制消息的投递可靠性模式
- confirm 确认模式
- return 退回模式
RabbitMQ的消息投递路径为
- 消息从Producer到exchange则会返回一个confirmCallback
- 消息从exchange到queue 投递失败会返回一个returnCallBack
这两个Callback帮助我们实现消息的可靠性投递
Confirm
以之前创建的Springboot的生产者为例
-
如果要开启confirm,那么可以先在yaml中开启配置
#配置RabbitMQ的基本信息 spring: rabbitmq: host: 123.123.1.221 port: 5672 username: hzp password: root publisher-confirm-type: correlated #配置confirm
-
编写测试类,定义ConfirmCallback函数
@SpringBootTest class ProducerApplicationTests { @Autowired private RabbitTemplate rabbitTemplate; /* 确认模式: 步骤: 1.开启确认模式:在yaml中配置publisher-confirm-type: correlated 2.在rabbitTemplate定义ConfirmCallback回调函数 */ @Test void contextLoads() { rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { /* 参数 correlationData 相关配置信息 b exchange交换机是否成功收到消息 true成功 false 失败 s 失败的原因 */ @Override public void confirm(CorrelationData correlationData, boolean b, String s) { System.out.println("Confirm方法被执行了-----------"); if(b){ //接收成功 System.out.println("消息接收成功"+s); }else { //失败,可以在这里对失败的消息进行处理 System.out.println("消息接收失败"+s); } } }); rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.hello","Message,Confirm"); } }
Return
同样以之前的Springboot生产者为例
-
配置yaml开启return
#配置RabbitMQ的基本信息 spring: rabbitmq: host: 123.123.1.221 port: 5672 username: hzp password: root publisher-confirm-type: correlated #配置confirm publisher-returns: true #配置return
-
设置ReturnCallback
/* 回退模式: 步骤: 1.开启回退模式 2.设置ReturnCallback 3.设置Exchange处理消息的模式 3.1.如果消息没有路由到Queue,则丢弃消息(默认) 3.2.如果消息没有路由到Queue,返回给消息发送方ReturnCallback */ @Test void testReturn() throws InterruptedException { //设置交换机处理失败消息的模式 rabbitTemplate.setMandatory(true);//失败后返回给生产者 //设置returnCallback rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() { @Override public void returnedMessage(ReturnedMessage returnedMessage) { System.out.println("return 执行了"); } }); rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"hello111","Message,Confirm"); }
这里的Routingkey设置错误因此无法发送到队列,会产生回退并且将消息返回
Consumer ACK
Ack表示Acknowledge,指消费端接收到消息之后的确认方式
一共有三种确认方式
- 自动确认:acknowledge=“none”
- 手动确认:acknowledge=“manual”
- 根据异常情况确认:acknowledge=“auto”
设置yaml开启手动确认
#配置RabbitMQ
spring:
rabbitmq:
host: 123.123.1.221
port: 5672
username: hzp
password: root
listener:
direct:
acknowledge-mode: manual #设置手动确认
编写监听类
/*
Consumer Ack机制
手动确认
1.设置手动签收
2.让监听器类实现ChannelAwareMessageListener
3.如果消息成功处理,就调用channel 的 basicAck()签收
4.如果消息处理失败,就调用channel 的 basicNack()拒绝签收,broker重新发送给consumer
*/
@Component
public class RabbitMQListener implements ChannelAwareMessageListener {
//@RabbitListener(queues = "boot_queue_confirm")
// public void ListenerQueue(Message message){
// System.out.println(new String(message.getBody()));
// }
@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("处理业务逻辑");
//3.手动签收
/*
参数1:消息的标签
参数2:是否批处理
*/
channel.basicAck(deliveryTag,true);
}catch (Exception e){
//e.printStackTrace();
//拒绝签收
/*
参数1:消息标签
参数2:是否批处理
参数3:是否重回队列,如果为true那么消息重新回到queue,broker会重新发送消息给消费者
*/
channel.basicNack(deliveryTag,true,true);
}
}
}
消费端限流
设置一次拉取未确认消息的最大数量
#配置RabbitMQ
spring:
rabbitmq:
host: 123.123.1.221
port: 5672
username: hzp
password: root
listener:
direct:
acknowledge-mode: manual #设置手动确认
prefetch: 1 #一次拉取一条消息
编写监听器
/*
限流机制
步骤:
1.确保ack机制为手动确认
2.listener-container 配置属性
perfetch = 1,表示消费端每次从mq拉取一条消息,直到手动确认之后才会拉取下一条
*/
@Component
public class QosListener implements ChannelAwareMessageListener {
@RabbitListener(queues = "boot_queue_confirm")
@Override
public void onMessage(Message message, Channel channel) throws Exception {
Thread.sleep(1000);
//获取消息
System.out.println(new String(message.getBody()));
//处理业务逻辑
System.out.println("处理中---");
//签收
long deliveryTag = message.getMessageProperties().getDeliveryTag();
channel.basicAck(deliveryTag,true);
}
}
TTL
Time To Live(存活时间/过期时间),单位是毫秒
当消息达到存活时间之后,还没被消费就会自动清除
队列的统一过期
直接在生产者的配置类中设置即可
//2.队列
@Bean("bootQueue")
public Queue bootQueue(){
return QueueBuilder.durable(QUEUE_NAME).ttl(10000).build();
}
消息的过期时间
MessagePostProcessor messagePostProcessor = new MessagePostProcessor(){
//设置
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//设置message的信息
message.getMessageProperties().setExpiration("1000");//消息的过期时间
//返回该消息
return null;
}
};
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.hello","Message,Confirm"+i,messagePostProcessor);//补充设置的参数
}
注意:如果设置了消息的过期时间也设置了队列的过期时间,以时间短的为主
队列过期之后所有的消息都会被清除。
死信队列
死信队列实际上指的是死信交换机,当消息成为Dead Message之后,可以被重新发送到另一个交换机,这个交换机就是死信交换机。也就是让Dead Message能够再次被利用。
消息成为死信的三种情况:
- 队列消息长度达到限制,那么后面的消息就会成为死信
- 消费者拒绝消费消息,使用了basicNack或basicReject并且不把消息重新放回原目标队列
- 原队列存在消息过期设置,消息达到过期时间并且未被消费
死信队列
1.声明正常的队列和交换机
2.声明死信队列和死信交换机
3.正常队列绑定死信交换机
设置两个参数:
死信交换机名称
死信交换机使用的routing key
配置类,进行死信队列的设置
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE_NAME = "boot_test_exchange";
public static final String DEAD_EXCHANGE_NAME = "boot_dead_exchange";
public static final String QUEUE_NAME = "boot_queue";
public static final String DEAD_QUEUE_NAME = "boot_dead_queue";
/*
死信队列
1.声明正常的队列和交换机
2.声明死信队列和死信交换机
3.正常队列绑定死信交换机
设置两个参数:
死信交换机名称
死信交换机使用的routing key
*/
//1.交换机
@Bean("bootExchange")
public Exchange bootExchange(){
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
//死信交换机
@Bean("bootDeadExchange")
public Exchange bootDeadExchange(){
return ExchangeBuilder.topicExchange(DEAD_EXCHANGE_NAME).durable(true).build();
}
//2.队列
@Bean("bootQueue")
public Queue bootQueue(){
//正常队列绑定死信交换机
return QueueBuilder.durable(QUEUE_NAME).deadLetterExchange(DEAD_EXCHANGE_NAME).deadLetterRoutingKey("dlx.haha").ttl(5000).build();
}
//死信队列
@Bean("bootDeadQueue")
public Queue bootDeadQueue(){
return QueueBuilder.durable(DEAD_QUEUE_NAME).build();
}
//绑定
@Bean
public Binding bindQueueExchange(@Qualifier("bootQueue")Queue queue,@Qualifier("bootExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("boot.dlx.#").noargs();
}
//死信队列绑定死信交换机
@Bean
public Binding bindDeadQueueExchange(@Qualifier("bootDeadQueue")Queue queue,@Qualifier("bootDeadExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
}
}
yaml
#配置RabbitMQ的基本信息
spring:
rabbitmq:
host: 123.123.1.221
port: 5672
username: hzp
password: root
publisher-confirm-type: correlated #配置confirm
publisher-returns: true #配置return
listener:
simple:
acknowledge-mode: manual
测试类
@Test
void testSent(){
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.dlx.haha","Message,Confirm"+i);
}
}
这里会发送10条消息,然后原队列消息过期之后,会自动将消息转发到私信交换机然后到死信队列
图片中死信队列有20条消息,是因为连续测试了两次
延迟队列
延迟队列是指消息进入队列之后不会立即被消费,只有达到指定时间才会被消费
比如下单后,30分钟未支付就取消订单,回滚库存;或者新用户注册七天后,发送短信问候
RabbitMQ本身没有延迟队列,但是我们可以通过TTL过期时间和死信队列结合起来实现延迟队列,比如过期时间30分钟,30分钟后成为死信后再进行处理
日志与监控
RabbitMQ默认日志存放路径为: /var/log/rabbitmq/rabbit@xxx.log
除此之外还可以通过界面查看
消息追踪
RabbitMQ可以使用Firehose和rabbitmq_tracing插件功能实现消息追踪
消息幂等性
消息的幂等性:就是即使多次收到了消息,也不会重复消费。所以保证消息的幂等性就是保证消息不会重复消费,这在开发中是很重要的。比如客户点击付款,如果点击了多次,那也只能扣一次费。