RabbitMQ_2.0
RabbitMQ高级特性
消息的可靠投递
简介:A通过消息队列给B发送一个消息,要经过以下过程(producer->rabbitmq broker->exchange->queue->consumer),RabbitMQ为了杜绝消息丢失以及投递失败,因而提供了两种方式来保证消息的投递可靠性。
- confirm 确认模式 (从producer到exchange的消息都会返回一个confirmCallback)
- return 退回模式(exchange路由到queue失败了 投递失败 才会返回returnCallback)
/*spring-rabbitmq-producer.xml*/
<!--消息可靠性投递(生产者)-->
<rabbit:queue id="test_queue_confirm" name="test_queue_confirm"/>
<rabbit:direct-exchange name="test_exchange_confirm">
<rabbit:bindings>
<rabbit:binding queue="test_queue_confirm" key="confirm"/>
</rabbit:bindings>
</rabbit:direct-exchange>
/*在ProducerTest测试类中定义回调*/
@Test
public void testConfirm(){
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("执行confirm方法......");
//ack表示发送成功或者失败(比如该交换机不存在)
if(ack){
System.out.println("接收成功"+cause);
}else {
System.out.println("接收失败"+cause);
}
}
});
//发送消息
rabbitTemplate.convertAndSend("test_exchange_confirm","confirm","message_confirm");
}
@Test
public void testReturn(){
//设置交换机处理失败消息的模式(如果消息没有路由到Queue 默认是丢弃消息)
rabbitTemplate.setMandatory(true);
//设置ReturnCallBack
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("return 执行了.....");
}
});
//发送消息(故意写错路由key)
rabbitTemplate.convertAndSend("test_exchange_confirm","confirm111","message_confirm");
}
小结
- 在connectionFactory中开启确认模式和回退模式
- 在rabbitTemplate中设置两个回调函数(注意!消息回退还要设置交换机处理失败消息的模式,rabbitTemplate.setMandatory(true),不然默认是丢弃消息)
Consumer Ack
简介:acknowledge,表示消费端确认收到消息。
三种签收方式:none(自动),manual(手动),auto(根据情况)
/*spring-rabbitmq-consumer.xml*/
<context:component-scan base-package="com.lxw.listener"/>
<!--定义监听器容器-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
<rabbit:listener ref="ackListener" queue-names="test_queue_confirm"/>
</rabbit:listener-container>
/**
* ack机制
* 1.设置手动签收 (acknowledge="manual")
* 2.让监听器类实现ChannelAwareMessageListener接口
* 3.如果消息成功处理则调用channel的basicAck签收
* 4.如果消息处理失败则调用channel的basicNack拒绝签收,让broker重新发送消息给consumer
*/
@Component
public class AckListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//接收消息
System.out.println(new String(message.getBody()));
//处理业务逻辑
System.out.println("处理业务逻辑......");
//channel手动签收
channel.basicAck(deliveryTag,true);
} catch (IOException e) {
e.printStackTrace();
//拒绝签收
channel.basicNack(deliveryTag,true,true);
}
}
}
PS:
- deliveryTag :消息的index
- multiple:是否批量处理 如果为true 就一次性签收所有小于deliveryTag的消息
- requeue:是否重回队列 如果设置为true 那么broker会重新发送消息给消费端
- broker:也是rabbitmq server 可以理解为介于生产者和消费者之间的服务器
消费端限流
@Component
public class QosListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//为了更好的看到效果
Thread.sleep(1000);
//获取消息
System.out.println(new String(message.getBody()));
//处理业务逻辑
System.out.println("处理业务逻辑");
//签收
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
小结
- 确保ack机制为手动确认
- 在监听器容器中配置属性(prefetch =n 表示每次从MQ拉取n条消息 直到确认消息消费完毕 才会继续拉去下一条消息)
TTL
简介:ttl指的是存活时间/过期时间,如果过了这个时间消息就过期。(不仅可以对消息设置过期时间,也可以对队列设置)
//声明队列、交换机并且绑定
<rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
<!--设置queue的参数 队列的过期时间 记得声明类型-->
<rabbit:queue-arguments>
<entry key="x-message-ttl" value="100000" value-type="java.lang.Integer"></entry>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="test_exchange_ttl">
<rabbit:bindings>
<rabbit:binding pattern="ttl.#" queue="test_queue_ttl"/>
</rabbit:bindings>
</rabbit:topic-exchange>
/*消息单独过期*/
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//设置message信息(设置消息过期时间)
message.getMessageProperties().setExpiration("5000");
return message;
}
};
rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.message","message ttl",messagePostProcessor);
//ps:这里有一个细节 要注意一下
for (int i = 0; i < 10; i++) {
//i等于5发送过期消息 不等于5发送不过期的消息
//发现消息并不会消失 (**证明只有消息在队列头部---要被消费时候才会单独判断是否达到过期时间**)
if(i==5){
rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.message","message ttl",messagePostProcessor);
}else {
rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.message","message ttl");
}
}
小结
- 队列统一过期(队列参数里面设置x-message-ttl)
- 消息单独过期(new一个消息后处理对象messagePostProcessor,setExpiration(“1000”)之后convertAndSend发送消息的时候,将这个后处理对象作为参数传进去)
- 消息的单独过期是当消息处于队列的顶端要被消费了才开始倒计时过期,如果不在顶端那么消息是不会过期的,所以一般设置TTL都是针对整个队列。
死信队列
简介:当消息死亡的时候,可以被重新发送到另一个交换机DLX
//普通队列里面 参数绑定死信队列的交换机
<rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
<rabbit:queue-arguments>
<entry key="x-dead-letter-exchange" value="exchange_dlx"/>
<entry key="x-dead-letter-routing-key" value="dlx.#"/>
<!--设置队列的过期时间ttl以及长度限制-->
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
<entry key="x-max-length" value="5" value-type="java.lang.Integer"/>
</rabbit:queue-arguments>
</rabbit:queue>
//整一个DlxListener消费者用来测试消息拒收(加入listener-container监听容器中)
@Component
public class DlxListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
System.out.println(new String(message.getBody()));
System.out.println("处理业务逻辑......");
int i = 4/0;//制造一个错误
channel.basicAck(deliveryTag,false);
} catch (Exception e) {
//e.printStackTrace();
System.out.println("出现错误,拒绝接受!");
//拒绝签收
channel.basicNack(deliveryTag,true,false);
}
}
}
/**
* 发送测试死信消息:
* 1.过期时间
* 2.长度限制
* 3.消息拒收
*/
@Test
public void testDlx(){
rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.hhh", "I am a message!");
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.hhh", "I am a message!");
}
rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.hhh", "I am a message!");
}
小结
-
死信交换机和死信队列和普通的队列交换机没啥区别
-
记得拒绝签收的时候设置参数requeue =false,如果重回队列的话就不会发到死信队列里面去了
-
消息成为死信的情况:
- 消息过期
- 队列消息长度达到限制(这个长度指消息一共有几条)
- 消费者拒收消息,且不返回队列
延迟队列
简介:延迟队列,消息进入队列后不会被立即消费,只有达到指定时间后才会被消费。(下单后,一定时间内没有支付,就取消订单,回滚库存。或者用户注册一段时间后,发送消息)
这里要提一嘴,RabbitMQ中并未提供延迟队列功能,所以我们要TTL+死信队列 组合实现延迟队列。
总结
RabbitMQ中不乏其他高级特性,如日志监控、消息追踪以及一些应用中的问题,如消息补偿、幂等性保障,集群搭建等等。目前仅仅是简单了解下,以后做到了具体应用再详细补充。