中间件MQ的学习
前言:上一篇已经介绍了RabbitMQ的基础知识,以及简单应用。这一篇介绍下RabbitMQ的几个高级特性
一、消息的可靠投递
1.从名字可以猜出这个特性主要是和生产者相关,因为投递很容易就联想到是消息被生产之后发送到交换机中进而推送到队列的过程。可以看如下RabbitMQ的消息投递路径
Producer----->Brocker----->Exchange---->Queue---->Consumer
RabbitMQ为我们提供了两种方式用来控制消息的可靠投递:confirm模式、return模式
消息从 producer 到 exchange 则会返回一个 confirmCallback 。
消息从 exchange–>queue 投递失败则会返回一个 returnCallback 。我们将利用这两个 callback 控制消息的可靠性投递
2.接下来将通过代码及文字说明解释它是怎么利用这两个特性的
(1)开启confirm模式:
1.设置ConnectionFactory的publisher-confirms=“true” 开启 确认模式。
2. 使用rabbitTemplate.setConfirmCallback设置回调函数。当消息发送到exchange后回调confirm方法。在方法中判断ack,如果为true,则发送成功,如果为false,则发送失败,需要处理。
3.代码:主要涉及 生产者的测试代码及配置文件
//测试 Confirm 模式
@Test
public void testConfirm() {
//定义回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 相关配置信息
* @param ack exchange交换机 是否成功收到了消息。true 成功,false代表失败
* @param cause 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("confirm方法被执行了....");
//ack 为 true表示 消息已经到达交换机
if (ack) {
//接收成功
System.out.println("接收成功消息" + cause);
} else {
//接收失败
System.out.println("接收失败消息" + cause);
//做一些处理,让消息再次发送。
}
}
});
//进行消息发送
rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message Confirm...");
//进行睡眠操作
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
}
<!-- 定义rabbitmq connectionFactory -->
<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" auto-declare="true"></rabbit:queue>
<!--定义交换机以及交换机与队列绑定-->
<rabbit:direct-exchange name="test_exchange_confirm" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="test_queue_confirm" key="confirm"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
(2)开启returnCallback
1.设置ConnectionFactory的publisher-returns=“true” 开启 退回模式。
2. 使用rabbitTemplate.setReturnCallback设置退回函数,当消息从exchange路由到queue失败后,如果设置了rabbitTemplate.setMandatory(true)参数,则会将消息退回给producer。并执行回调函数returnedMessage
3.代码:主要涉及 生产者的测试代码及配置文件
//测试 return模式
@Test
public void testReturn() {
//设置交换机处理失败消息的模式 为true的时候,消息达到不了 队列时,会将消息重新返回给生产者
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("return 执行了....");
System.out.println("message:" + message);
System.out.println("replyCode:" + replyCode);
System.out.println("replyText:" + replyText);
System.out.println("exchange:" + exchange);
System.out.println("routingKey:" + routingKey);
//处理
}
});
//进行消息发送
rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message return...");
//进行睡眠操作
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
}
xml配置和上面的配置一样,主要是在配置connectionFactory时设置的属性
二、Consumer Ack
1.根据名称不难猜出来,这个主要涉及消费者。用来确保队列中的消息被消费者接受后成功消费了。ack表示消息收到消息后的确认方式有三种确认方式:
自动确认:acknowledge=“none”
手动确认:acknowledge=“manual”
根据异常情况确认:acknowledge=“auto”
2.加下来通过代码及文字说明它的作用
<!--定义监听器容器
acknowledge="manual":手动签收
prefetch="1":每次抓取多少条消息
-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="2"/>
@Component
public class AckListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//1、获取消息的id
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//2、获取消息
System.out.println("message:" + new String(message.getBody()));
//3、进行业务处理
System.out.println("=====进行业务处理====");
//模拟出现异常
int i = 5/0;
//4、进行消息签收
channel.basicAck(deliveryTag, true);
} catch (Exception e) {
//拒绝签收
/*
第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
*/
channel.basicNack(deliveryTag, true, true);
}
}
}
如上代码:当xml中配置ack为手动时,消息消费完成之后需要手动确认消费。如果过程中出现异常,可以使用channel.basicNack(deliveryTag, true, true)拒绝签收消息返回队列,这样监听器可以再次监听消息进行消费。如果xml中配置的ack为null,则消息被消费者接收之后就默认确认了,此时如果出现异常,导致实际上消息尚未处理,那么这条消息就丢失了。
3.总结:其实为了达到消息的可靠性,就需要生产者和消费者都配置,保证生产者消息进入队列,保证消费者消息成功消费。同时保证持久化、broker的高可用,不会因为挂掉一个MQ导致消息丢失。
三、消费端限流
这个根据名称不能理解,一般来说就是担心消费者的消费能力不足,一次拉去太多消息,将消费端撑爆。上面Consumer Ack的xml配置中属性prefetch就是用来控制每次拉去消息数量的。
四、TTL
TTL 全称 Time To Live(存活时间/过期时间),即可以设置过期时间。这个过期可以设置队列过期,也可以设置消息过期。也就是达到过期时间之后,消息会过期,需要注意的是队列过期和设置消息过期有一点区别。队列过期的话,队列中的消息都会过期,消息过期的话只有当消息处于队列中头时才会识别出来。如果两个都设置了过期时间,以短的为准。
五、死信队列
1.初听死信队列感觉好像很高大上,也不知道干啥的,后来面试被问到生产消息过多时怎么防止消息丢失,才知道可以用死信队列来解决,那么死信队列是什么呢?其实说白了它也是一个队列,只是它里面存储的消息和正常队列的消息不太一样,可以理解为被抛弃的消息,具体条件看如下解释。
2.消息成为死信的三种情况
- 队列消息长度到达限制;
- 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
- 原队列存在消息过期设置,消息到达超时时间未被消费;
3.下面将通过图片、代码、文字说明死信队列的工作方式
1.正常交换机和正常对列绑定(这就是正常流程,生产者发送消息到交换机,再到队列)
2.正常队列和死信交换机绑定(消息满足成为死信的三种情况之一就进入死信交换机,再进入死信队列)
3.死信交换机和死信队列绑定(用监听器监听死信队列,就能消费死信队列的消息)
<!--
死信队列:
1. 声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx)
2. 声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
3. 正常队列绑定死信交换机
设置两个参数:
* x-dead-letter-exchange:死信交换机名称
* x-dead-letter-routing-key:发送给死信交换机的routingkey
-->
<!--
1. 声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx)
-->
<rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
<!--3. 正常队列绑定死信交换机-->
<rabbit:queue-arguments>
<!--3.1 x-dead-letter-exchange:死信交换机名称-->
<entry key="x-dead-letter-exchange" value="exchange_dlx" />
<!--3.2 x-dead-letter-routing-key:发送给死信交换机的routingkey-->
<entry key="x-dead-letter-routing-key" value="dlx.hehe" /> <!--这里的value为什么必须是这个?-->
<!--4.1 设置队列的过期时间 ttl-->
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" />
<!--4.2 设置队列的长度限制 max-length -->
<entry key="x-max-length" value="10" value-type="java.lang.Integer" />
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="test_exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--
2. 声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
-->
<rabbit:queue name="queue_dlx" id="queue_dlx"></rabbit:queue>
<rabbit:topic-exchange name="exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="dlx.#" queue="queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
// 消费端
<!--定义监听器,死信队列-->
<rabbit:listener ref="dlxListener" queue-names="queue_dlx"></rabbit:listener>
六、延迟队列
延迟队列就是进入队列的消息不会马上被消息,会有一个延迟。但是RabbitMQ没有延迟队列,但是它可以通过TTL+死信队列 来实现。
如图
消息在进入TTL(过期)队列后,我们实际上不监听这个队列的消息,等到消息过期后,进入死信队列,我们对死信队列进行监听。此时接受到的消息就经过了一个过期时间的延期。
七、消息幂等性
消息幂等性就是不论消息被消费多少次得到的结果总是相同的。例如,每个消息都有携带一个全局唯一id,接受到之后,将消息存入本地数据库,每次接受之后都用这个唯一id从数据库查询一次看是否存在,存在则返回之前的结果,或者不处理。如果不存在则是新消息在处理。
到这里RabbitMQ的高级特性就说完了,整理的东西是方便日后自己查看,所以有些话过于口语,而且有些点也不是特别详细,如果有看官发现遗漏或者错误欢迎指正。