Web-RabbitMQ高级目录
一、消息可靠性投递
为了保证消息生产者 ----》队列是可靠的,RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。
- confirm 确认模式:监听从消费者 ----》交换机
- return 退回模式:监听从交换机 ----》队列
1. confirm 确认模式
confirm确认模式 是监听从消费者到交换机之间的过程的,成功/失败都会执行相应的回调方法,只是回调消息不同,执行逻辑可以自定义。
1.1 整合Springboot/Spring后中的实现
主要是通过
ConnectionFactory
开启和RabbitTemplate
设置回调逻辑
第一步:开启
spring xml文件中配置
对比springboot多了这一步:
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
在定义connectionFactory
时设置其参数publisher-confirms="true"
,并没有找到其具体实现类是哪个,只是看到ConnectionFactory接口中的默认方法可以获取这个参数,但没看到设置的API方法。
<rabbit:connection-factory id="connectionFactory"
host="${rabbitmq.host}"
publisher-confirms="true"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
springboot yaml文件中配置
spring.rabbitmq.publisher-confirms: true #老版本的写法
spring.rabbitmq.publisher-confirm-type: correlated #新版本的写法
ConnectionFactory配置类中指定
spring\springboot都可用
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
//开启确认模式
connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
//开启退回模式
connectionFactory.setPublisherReturns(true);
第二步:设置回调函数
注意:这个回调逻辑时子线程在执行,所以需要注意共享变量的安全,以及线程的顺序执行。另外每个rabbitTemplate只能设置一个回调,也就是说每个rabbitTemplate对象设置了ConfirmCallback后不能再次设置,可以考虑实现ConfirmCallback,并用单例。
spring/springboot
//spring中使用rabbitTemplate要设置相应connectionFactory属性,springboot中好像不用
@Autowired
private RabbitTemplate rabbitTemplate;
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* 发送消息后此方法就会被执行
* @param correlationData :消息相关配置信息
* @param b :交换机是否接收到消息
* @param s :失败原因,成功为null
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
if(b){
System.out.println("confirm方法被执行,消息发送成功");
}else {
System.out.println("confirm方法被执行,消息发送失败");
System.out.println("错误信息: "+s);
//可以自己定义一些逻辑,让消息再次发送
}
}
});
//rabbitTemplate也可以发送信息,至于和channel有什么区别,没研究到
rabbitTemplate.convertAndSend("test_exchange_confirm11","confirm","message_confirm");
1.2 Channel类
第一步:开启
在 channel上开启确认模式: channel.confirmSelect();
第二步:设置回调函数
channel.addConfirmListener(new ConfirmListener() {
/**
* 返回成功的回调函数
*/
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("succuss ack");
System.out.println(multiple);
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
}
/**
* 返回失败的回调函数
*/
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.printf("defeat ack");
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
}
});
1.3 总结
其实就两类方法一个是基于Channel
,一个是基于RabbitTemplate
,前者设置开启和回调函数都是Channel
,后者开启设置使用了ConnectionFactory
。开启了confirm模式后,不管成功还是失败都会执行callback,只是返回的消息不同而已。
2. Return 退回模式
当消息发送给Exchange后,Exchange路由到Queue失败时才会执行 ReturnCallBack,这里只说整合Springboot/Spring后中的实现(用RabbitTemple),和
channel.basicpublish()
中的mandatory
参数设置为true很差不多
第一步:开启
spring:
<rabbit:connection-factory id="connectionFactory"
host="${rabbitmq.host}"
publisher-confirms="true"
publisher-returns="true"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
springboot:
#开启 confirm 确认机制
spring.rabbitmq.publisher-confirms: true
#开启 return 确认机制
spring.rabbitmq.publisher-returns: true
#设置为 true 后 消费者在消息没有被路由到合适队列情况下会被return监听,而不会自动删除
spring.rabbitmq.template.mandatory: true
第二步:设置回调函数
//设置交换机处理失败消息的模式
//默认false,如果消息没有路由到Queue,则丢弃消息(默认),回调函数不会被执行。
//设置为true,如果消息没有路由到Queue,则会将消息退回给producer,并执行ReturnCallBack
//在springbootyaml参数中设置过了这里可以不设置,不知道spring中是不是可以配置文件中直接注入
rabbitTemplate.setMandatory(true);
//2.设置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_confirm", "confirm", "message confirm....");
小结
使用rabbitTemplate.setReturnCallback
设置退回函数,当消息从exchange路由到queue失败后,如果开启了退回模式且设置了rabbitTemplate.setMandatory(true)
参数,则会将消息退回给producer,并执行回调函数returnedMessage。否则会别丢弃则会丢弃消息。
疑问:
如果是这样的话,publisher-returns: true
这个参数还有什么意义,直接设置Mandatory不就得了吗?
并不清楚和channel.basicPublish()
中设置mandatory参数为true或false的区别在哪,这个只设置mandatory可以实现吗?
3. 对比
监听过程 | callback执行条件 | 失败怎么处理 | 配置位置 | |
---|---|---|---|---|
confirm模式 | publisher → Exchange | 开启confirm模式; 不管成功还是失败都会执行 | callback会带回成功和失败的信息,接下来取决于自定义的callback逻辑,一般会重新发送转发什么的 | ConnectionFactory,也就是针对整个Virtual Host的连接 |
return模式 | Exchange → Queue | 1. 开启return模式 2. 指定mandatory为true 3. 出现错误 | callback会带回失败的信息,接下来取决于自定义的callback逻辑 ,一般会重新发送,转发什么的 | 开启是在ConnectionFactory,设置mandatory是在通道中 |
二、消费端确认
消费端接收到消息后通常需要确认消息,然后队列将消息删除,确认就意味着将队列中的确认的消息删除。
在监听器标签中<rabbit:listener-container>
有三种确认模式:
- 自动确认:acknowledge=“none”:自动确认,消息发出去后直接默认消费成功直接将队列中的消息删除。
- 手动确认:acknowledge=“manual”:手动调用确认,具体逻辑子监听器类中。
- 根据异常情况确认:acknowledge=“auto”,(这种方式使用麻烦,不作讲解)
不太清楚这个和
channel.basicConsume()
中设置参数有什么联系,可能底层实现就是调用的channel.basicConsume()
。
不管是自动还是手动确认,都会执行监听类,其实与其说是监听类可以说是消费逻辑,监听器是负责接收消息对消息消费的,设置手动时会在里面自定义一些确认的逻辑。
如果设置为自动,但是监听类中逻辑里写了手动提交,貌似不会执行,未明确验证过。
如果设置为手动,失败和成功后的逻辑,以及什么时候确认消息都是自己在监听器中定义的。
设置确认方式是在监听器容器中指定的,默认为自动。
2.1 Spring
1. 创建监听容器
每个容器对应了一个Virtual Host的连接,容器指定了消息确认的方式,容器可以容纳多个监听器,每个监听器监听一个队列,消费端可以在监听器里写自己的消费逻辑和确认逻辑。
这里开启确认模式,导入监听器。相当于channel.basicConsume()
中指定autoAck和callback参数
<!--connection-factory:连接工厂;
auto-declare:
acknowledge:确认方式;
queue-names:监听的队列的名称;
ref:监听器
-->
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true" acknowledge="manual">
<rabbit:listener ref="ackListener" queue-names="test_queue_confirm"/>
</rabbit:listener-container>
2. 创建监听器
手动确认的逻辑就在这里,相当于
channel.basicConsume()
的参数(callback)DefaultConsumer类
,这里也是消费消息的逻辑和确认消息时机的逻辑所在
@Component
/**
* 这个监听器类会在接收消息后自动执行
* ChannelAwareMessageListener是MessageListener的子接口,
* 由于需要Channel去调用basicNack()方法和basicAck()方法,所以需要ChannelAwareMessageListener接口
*/
public class AckListener implements ChannelAwareMessageListener{
/**
*
* @param message
* @param channel
* @throws Exception
*/
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
System.out.println(message);
/**
* public void basicAck(long deliveryTag, boolean multiple)
* deliveryTag:消息的一个标签,表示签收哪个消息
* multiple:接收多个
*/
int i = 1/0;
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
//消费失败,拒绝签收
/**
第一个参数:指定拒绝签收的消息的参数,在发送时会自动生成
* 前两个参数同上
* 第三个参数:requeue(重回队列):如果设置为ture,则消息重新回到queue,broker会重新发送消息给消费端。
* 注:1.重新发送消息的功能并不是由开不开启手动模式决定的,开不开启只决定了是否自动提交(删除队列中的消息)
注2.channel.basicNack方法决定是否拒绝签收,第三个参数的值决定是否重回队列,如果为false相当于拒绝签收,并丢弃消息。
*/
channel.basicNack(deliveryTag,true,true);
}
}
}
2.2 springboot
1. 指定确认模式
默认自动确认,确认方式是在监听器容器中设置的,这里的监听器容器有simple、direct,不知道默认是不是simple,有什么区别不清楚。
spring.rabbitmq.listener.simple.acknowledge-mode: manual
三、消费端限流
<rabbit:listener-container>
中配置 prefetch属性设置消费端一次拉取多少消息,确认消费后才会拉取下一次的,不确认则不会拉取下一条,所以这里不能自动确认。类比channel.basicQos()
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true" acknowledge="manual" prefetch="1">
<rabbit:listener ref="qosListener" queue-names="test_queue_confirm"/>
</rabbit:listener-container>
监听器:
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
/**
* 这个监听器类会在接收消息后自动执行
* ChannelAwareMessageListener是MessageListener的子接口,
* 由于需要Channel去调用basicNack()方法和basicAck()方法,所以需要ChannelAwareMessageListener接口
*/
@Component
public class QosListener implements ChannelAwareMessageListener{
/**
*
* @param message
* @param channel
* @throws Exception
*/
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();//获取消息的id
try {
System.out.println(message);
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
channel.basicNack(deliveryTag,true,true);
}
}
}
四、TTL过期时间设置
RabbitMQ可以对消息和整个队列(Queue)设置过期时间。
1. 规则
- 如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。
- 队列过期后,会将队列所有消息全部移除。
- 消息过期后,但队列未过期,只有消息在队列顶端,才会判断其是否过期(移除掉),如果消息不在顶端(先添加进去队列的消息),则等到前面的消息消费完了,这条消息才会被移除而不是被消费。
- 如果某个设置了过期时间的队列已经建立,再去设置消息过期时,可能会报两种过期时间矛盾的错,这个时候可以删除之前的队列再去建立。
2. 设置队列过期
建立队列的属性参数,channel.queueDeclare()时也可以指定。
<!--创建queue,这里需要设置队列的属性-->
<rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
<!--设置queue的属性参数-->
<rabbit:queue-arguments>
<!--
x-message-ttl指队列的过期时间;
key为队列属性名(固定写法)在官网上有或者百度;
value:过期时间,单位ms
value-type为指定value的数据类型,默认为字符串类型
-->
<entry key="x-message-ttl" value="100000" value-type="java.lang.Integer"></entry>
</rabbit:queue-arguments>
</rabbit:queue>
<!--创建topic-exchange,并绑定队列-->
<rabbit:topic-exchange name="test_exchange_ttl" >
<rabbit:bindings>
<rabbit:binding pattern="ttl.#" queue="test_queue_ttl"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
3. 设置消息过期
@Test
public void testTtl() {
// 消息后处理对象,设置一些消息的参数信息
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//1.设置message的信息,赋予消息过期属性值
message.getMessageProperties().setExpiration("5000");//消息的过期时间
//2.返回该消息
return message;
}
};
//消息单独过期
//rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....",messagePostProcessor);
for (int i = 0; i < 10; i++) {
if(i == 5){
//消息单独过期
rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....",messagePostProcessor);
}else{
//不过期的消息
rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....");
}
}
}
五、死信队列
1. 概念
- 当消息在一个队列中变成死信 (dead message) 之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX。
- DLX也是一个正常的Exchange,和一般的Exchange没有区别
- 当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列。