1.消息可靠性投递
存在的问题
在使用RabbitMQ的时候,我们可能会由于种种原因发送消息丢失或投递失败的场景
就像我们生活中送快递一样,快递员送快递后,需要我们确认签收,才能证明这个快递到我们手上了。如果快递在运输的途中丢了,我们就需要向快递公司反馈,这里面就有一套处理机制。RabbitMQ也是如此
解决方案
在使用RabbitMQ的时候,作为消息发送方希望杜绝任何消息丢失或投递失败场景,RabbitMQ为我们提供了两种方式用来控制消息的投递可靠性模式。
1.confirm 确认模式
2.return 退回模式
rabbitMQ整个消息的投递路径为:
producer–>rabbitmq broker -->exchange–>queue–>consumer
消息从producer 到exchange 则会返回一个confirmCallback
消息从exchange–>queue投递失败则会返回一个returnCallback
我们将利这两个callback控制消息的可靠性投递
两种模式介绍
1.publiser-confirm模式可以确保生产者到交换器exchange消息有没有发送成功
使用步骤
1.开启确认模式 publisher-confirms=“true”
2.在rabbitTemplate中定义confirm callBack回调函数
#设置此属性配置可以确保消息成功发送到交换器
spring.rabbitmq.publisher-confirms=true
2.publisher-return模式可以在消息没有被路由到指定的queue时将消息返回,而不是丢弃
#可以确保消息在未被队列接收时返回
spring.rabbitmq.publisher-returns=true
使用步骤
1.开启回退模式:publisher-returns=“true”
2.设置return Callback
3.设置exchange处理消息模式
1. 如果消息没有路由到Queue,则丢弃消息(默认)
2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack
案例演示
演示步骤
1.创建工程
2.添加对应的依赖
3.编写配置文件
4.测试代码
1.创建工程
2.添加对应的依赖
<dependencies>
<!--测试相关-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!--RabbitMQ相关-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
<!--spring集成rabbitmq-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<!--spring相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.2</version>
</dependency>
</dependencies>
3.编写配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!--定义rabbitmq ConnectionFactory-->
<rabbit:connection-factory id="connectionFactory"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
host="${rabbitmq.host}"
virtual-host="${rabbitmq.virtual-host}"
publisher-confirms="true"
publisher-returns="true"
/>
<!--定义管理交换机队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
<!--队列-->
<rabbit:queue id="test_queue_high" name="test_queue_high"/>
<!--绑定交换机-->
<rabbit:direct-exchange name="test_exchange_high" >
<rabbit:bindings>
<rabbit:binding queue="test_queue_high" key="confirm"/>
</rabbit:bindings>
</rabbit:direct-exchange>
</beans>
4.测试代码
public-confirm
/*
* 确认模式
* 步骤
* 1.开启确认模式
* 2.在rabbitTemplate中定义confirm callBack回调函数
* */
@Test
public void test1() throws InterruptedException {
/*定义回调*/
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/*
* 参数介绍从左至右
* 1.相关配置信息
* 2.为true则为成功发送到了交换机
* 3.错误原因
* */
@Override
public void confirm(CorrelationData correlationData, boolean b, String cause) {
System.out.println("confirm方法被执行了....");
System.out.println(b);
if (b) {
//接收成功
System.out.println("接收成功消息" + cause);
} else {
//接收失败
System.out.println("接收失败消息" + cause);
//做一些处理,让消息再次发送。
}
}
});
/*发送消息*/
/*
* 参数介绍
* 1.交换机名称
* 2.key名称
* 3.所发送的消息
* */
rabbitTemplate.convertAndSend("test_exchange_high","confirm","This is message!!");
Thread.sleep(2000);
}
public-return
/*
* 回退模式:当消息发送给Exchange之后,Exchange路由到Queue失败才会执行ReturnCallBack
*步骤 :
* 1.开启回退模式:publisher-returns="true"
* 2.设置return Callback
* 3.设置exchange处理消息模式
* 1. 如果消息没有路由到Queue,则丢弃消息(默认)
* 2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack
* */
@Test
public void test2() throws InterruptedException {
/*当把 Mandatory 参数设置为 true 时,如果交换机无法将消息进行
路由时,会将该消息返回给生产者,而如果该参数设置为false,如果发现
消息无法进行路由,则直接丢弃。*/
/*设置交换机处理消息失败模式*/
rabbitTemplate.setMandatory(true);
/*设置returnCallBack*/
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/*
* 参数介绍,从左到右
* message:消息对象
* error code:错误码
* errorMessage:错误信息
* exchange:交换机
* routeKey:路由键
* */
@Override
public void returnedMessage(Message message, int errorCode, String errorMessage, String exchange, String routeKey) {
System.out.println("returnMessage方法执行了");
System.out.println("所发送的信息:"+message);
System.out.println("路由键:"+routeKey);
System.out.println("错误码:"+errorCode);
}
});
/*发送消息*/
/*
* 参数介绍
* 1.交换机名称
* 2.key名称
* 3.所发送的消息
* */
rabbitTemplate.convertAndSend("test_exchange_high","confirm2","This is message!!");
Thread.sleep(200);
}
小总结
设置ConnectionFactory的publish-confirms=“true”开启确认模式
使用rabbitTemplate.setConfirmCallBack设置回调函数,当消息发送到exchange后回调confirm方法,在方法中判断ack,如果为true,则发送成功,如果为false则发送失败,需要处理
设置ConnectionFactory的publisher-returns=“true” 开启回退模式
使用rabbitTemplate.setReturnCallBack设置退回函数,当消息从exchange路由到queue失败如果设置了rabbitTemplate.setMandatory(true)函数,则会将消息回退给producer,并执行回调函数returnedMessage()
2.Consumer ACK
三种确认方式介绍
ACK指Acknowledge,确认,表示消费端接受到 消息后的确认方式
有三种确认方式:
自动确认:acknowledge="none’
手动确认: acknowledge=“manual”
根据异常情况确认:acknowledge=“auto”(这种方式麻烦,不做讲解)
其中自动确认是指,当消息一旦被consumer接收到,则自动确认收到,并将相应message从queue中取出,但是在实际的业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失,如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicACK(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息
小案例
测试步骤概述
1.创建工程
2.导入相应的依赖坐标
3.编写继承ChannelAwareMessageListener的监听类
4.测试
1.创建工程
2.导入相应的依赖坐标
<dependencies>
<!--测试相关-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!--RabbitMQ相关-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
<!--spring集成rabbitmq-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<!--spring相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.2</version>
</dependency>
</dependencies>
3.编写继承ChannelAwareMessageListener的监听类
package com.pjh.listen;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
@Component
public class ACKListenerTwo implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
Thread.sleep(1000);
try{
/*接受装换消息*/
System.out.println(new String(message.getBody()));
/*处理业务逻辑*/
System.out.println("处理业务逻辑");
int i=3/0;
/*手动签收*/
channel.basicAck(deliveryTag,true);
}catch (Exception e){
/*拒绝签收*/
/*
* 第三个参数 requeue:重回队列 如果设置为true则重回消费端
* broker会重新发送该消息给消费端
* */
System.out.println("出错了!!");
channel.basicNack(deliveryTag,true,true);
}
}
}
手动编写一个错误
4.测试
package com.pjh;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbit-consumer.xml")
public class test {
@Test
public void test(){
while(true){
}
}
}
测试结果
主要方法及其参数介绍:
1. channel.basicAck()
参数:
deliveryTag:该消息的index
multiple:是否批量处理.true:将一次性ack所有小于deliveryTag的消息
void basicAck(long deliveryTag, boolean multiple) throws IOException;
2. channel.basicNack()
参数:
deliveryTag:该消息的index
multiple:是否批量.true:将一次性拒绝所有小于deliveryTag的消息
requeue:被拒绝的是否重新入队列 注意:如果设置为true ,则会添加在队列的末端
void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;
小结
在rabbit:listener-container标签中设置acknowledge属性,设置ack方式none:自动确认,manual:手动确认
如果在消费端没有出现异常,则调用channel.basic(delivery Tag,false)方法确认签收消息
如果出现异常,则在catch中调用basicNack或basicReject,拒绝消息,让MQ重新发送消息