1、消息的可靠传递
在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了消息的确认机制,用来控制消息投递的可靠性模式。
消息的确认,是指生产者投递消息后,如果Broker收到消息,则会给我们生产这一个应答。
生产者进行接收应答,用来确定这条消息是否正常的发送到Broker,这种方式也是消息的可靠性投递的核心保障。
1.1 实现步骤
第一步:在channel上开启确认模式:channel.confirmSelect()
第二步:在channel上添加监听:addConfirmListener,监听成功和失败的返回结果,
根据具体的结果对消息进行重新发送、或者记录日志等后续处理。
rabbitmq 整个消息投递的路径为:
producer—>rabbitmq broker—>exchange—>queue—>consumer
消息从 producer 到 exchange 则会返回一个 confirmCallback 。
消息从 exchange–>queue 投递失败则会返回一个 returnCallback 。
我们将利用这两个 callback 控制消息的可靠性投递
1.1.1 Confirm确认机制-在Spring中开启
①创建项目 rabbitmq-producer-spring
②添加pom文件
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
③ 在resource
文件夹下面添加 配置文件 rabbitmq.properties
rabbitmq.host=localhost
rabbitmq.port=5672
rabbitmq.username=guest
rabbitmq.password=guest
rabbitmq.virtual-host=/
在resource
文件夹下面添加 配置文件 spring-rabbitmq-producer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://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
确认模式开启:publisher-confirms="true"
-->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
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_confirm" name="test_queue_confirm"></rabbit:queue>
<rabbit:direct-exchange name="test_exchange_confirm">
<rabbit:bindings>
<rabbit:binding queue="test_queue_confirm" key="confirm"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
</beans>
④创建测试类,添加确认模式
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class ProducerTest {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 确认模式:
* 步骤:
* 1.确认模式开启:在spring-rabbitmq-producer.xml中,使得ConnectionFactory的属性--publisher-confirms=true
* 2. 在rabbitTemplate定义ConfirmCallBack回调函数
*/
@Test
public void testConfirm(){
//2.定义回调函数
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack){
System.out.println("接收消息成功"+ cause);
}else {
System.out.println("接收消息失败" + cause);
}
}
});
//3. 发送消息
rabbitTemplate.convertAndSend("test_exchange_confirm000", "confirm", "message confirm....");
}
}
注意:发送消息时,如果rabbitmq中没有定义名为test_exchange_confirm000的交换机,消息就会发送失败。
此时需要手动打开rabbitmq的管理页面,添加此交换机
第一步
第二步
⑤运行程序
1.1.2 Return 回退机制
Return Listener用于处理一些不可路由的消息
。
我们的消息生产者,通过指定一个Exchange和Routingkey,把消息送到某一个队列中,
然后我们的消费者监听队列,进行消息处理操作。
但是在某些情况下,如果我们在发送消息的时候,当前的exchange不存在或者指定的路由key路由不到,
这个时候我们需要监听这种不可达的消息,就要使用return listener。
在基础API中有一个关键的配置项:Mandatory:
如果为true,则监听会接收到路由不可达的消息,然后进行后续处理,
如果为false,那么broker端自动删除该消息。(默认false)
回退机制测试代码
@Test
public void testReturn(){
/**
* 回退模式: 当消息发送给Exchange后,Exchange路由到Queue失败是 才会执行 ReturnCallBack
* 步骤:
* 1. 开启回退模式:publisher-returns="true"
* 2. 设置ReturnCallBack
* 3. 设置Exchange处理消息的模式:
* 1. 如果消息没有路由到Queue,则丢弃消息(默认)
* 2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack
*/
//交换机设置强制处理失败消息的模式
rabbitTemplate.setMandatory(true);
//设置ReturnCallBack
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("return 执行了....");
System.out.println(message);
System.out.println(replyCode);
System.out.println(replyText);
System.out.println(exchange);
System.out.println(routingKey);
//写一些处理代码......
}
});
//3. 发送消息
rabbitTemplate.convertAndSend("test_exchange_confirm123", "confirm123", "message confirm....");
}
1.1.3 小结
Confirm确认机制
- 在xml配置文件中设置
ConnectionFactory的publisher-confirms="true"
开启 确认模式。 - 使用
rabbitTemplate.setConfirmCallback
设置回调函数。当消息发送到exchange
后回调confirm
方法。在方法中判断ack
,如果为true
,则发送成功,如果为false
,则发送失败,需要处理。
Return回退机制
- 在xml配置文件中设置
ConnectionFactory
的publisher-returns="true"
开启 退回模式。 - 使用
rabbitTemplate.setReturnCallback
设置退回函数,当消息从exchange
路由到queue
失败后,如果设置了rabbitTemplate.setMandatory(true)
参数,则会将消息退回给 producer并执行回调函数returnedMessage
1.2、Consumer Ack(消费端确认)
1.2.1 Consumer Ack 介绍
ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
有二种确认方式:
- 自动确认:
acknowledge=“none”
- 手动确认:
acknowledge=“manual”
在spring中通过xml配置文件来配置
<!--定义监听器容器
acknowledge="manual":手动签收
-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
其中
自动确认
是指,当消息一旦被Consumer接收到
,则自动确认收到
,并将相应 message 从 RabbitMQ 的消息缓存中移除
。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。
如果设置了手动确认
方式,则需要在业务处理成功后,调用channel.basicAck()
,手动签收
,如果出现异常,则调用channel.basicNack()
方法,让其自动重新发送消息
。
1.2.2 代码演示
① 创建一个Maven项目 rabbitmq-consumer-spring
②添加pom依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
③在 resource
文件夹下面新建 rabbitmq.properties
文件 和 spring-rabbitmq-consumer.xml
文件
rabbitmq.properties
文件
rabbitmq.host=localhost
rabbitmq.port=5672
rabbitmq.username=guest
rabbitmq.password=guest
rabbitmq.virtual-host=/
spring-rabbitmq-consumer.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://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" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<context:component-scan base-package="com.codez.listener" />
<!--定义监听器容器
acknowledge="manual":手动签收
-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
<!--延迟队列效果实现: 一定要监听的是 死信队列!!!-->
<rabbit:listener ref="ackListener" queue-names="test_queue_confirm"></rabbit:listener>
</rabbit:listener-container>
</beans>
做好以上的环境搭建之后,采用哪种确认模式,只需要在配置文件中进行修改即可。但是,使用自动确认和手动确认的实现方式不太一样。
自动确认
④添加自动确认监听器,消费端一旦收到消息,就会自动确认收到
@Component
public class AckListener implements MessageListener {
@Override
public void onMessage(Message message) {
System.out.println(new String(message.getBody()));
}
}
手动确认
添加手动确认的监听器,需要确保业务正常处理之后,才会确认收到。
@Component
public class AckListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
Thread.sleep(1000);
//1.获取消息传递的标记
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//①接收消息
System.out.println(new String(message.getBody()));
//②处理业务逻辑
System.out.println("处理业务逻辑");
//设置一个错误
int i = 3/0;
//③手动签收
/**
* 第一个参数:表示收到的标签
* 第二个参数:如果为true表示可以签收所有的消息
*/
channel.basicAck(deliveryTag,true);
}catch (Exception e){
e.printStackTrace();
// ④ 拒绝签收
/**
第三个参数:requeue:重回队列。
设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
*/
channel.basicNack(deliveryTag,true,true);
}
}
}
⑤创建测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class ConsumerTest {
@Test
public void test(){
while (true){
}
}
}
运行测试类,会一直监听消息,查看后台 http://localhost:15672/#/queues
1.2.3 Consumer Ack 小结
在rabbit:listener-container
标签中设置acknowledge
属性,设置ack方式
none
:自动确认,
manual
:手动确认
如果在消费端没有出现异常
,则调用channel.basicAck(deliveryTag,true);方法确认签收消息
如果出现异常
,则在catch中调用 basicNack,拒绝消息,让MQ重新发送消息。