RabbitMQ高级特性
消息可靠性投递
在使用rabbitMQ时,作为消息发送方希望杜绝任何消息丢失或者投递失败场景,rabbitMQ为我们提供了两种方式用来控制消息的投递可靠性模式.
- confirm 确认模式
- return 追回模式
rabbitmq整个消息投递路径为:producer—>exchange—>queue—>consumer
- 消息从producer到exchange则会返回一个confirmCallback
- 消息从exchange—>queue投递失败则会返回一个returnCallback.
利用这两个callback控制消息的可靠性传递
//配置信息
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
publisher-confirms: true
publisher-returns: true
package com.qf;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.Callable;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ProviderApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void contextConfirm() {
/*
* 确认模式:
* 步骤:
* 1.确认模式开启:publisher-confirms: true
* 2.在rabbitTemplate定义confirmCallback回调函数
* */
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* @Author: Mr.Zhou
* @Date: 2021/3/2 23:31
* @Description: confirm
* @param correlationData:相关配置信息
* @param b: exchange交换机是否成功收到了消息,true代表成功,false代表失败
* @param s: 当b为false时,s会显示失败原因
* @Return: void
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
if (b){
//接收成功
System.out.println("消息接收成功");
}else{
//接收失败,处理消息再次发送
System.out.println("消息接受失败,失败原因:"+s);
}
System.out.println("confirmCallback执行了......");
}
});
//3.发送消息
rabbitTemplate.convertAndSend("test_exchange","confirm","message confirm ....");
}
@Test
public void contextReturn() {
/*
* 回退模式:当消息发送给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() {
/**
* @Author: Mr.Zhou
* @Date: 2021/3/4 11:09
* @Description: returnedMessage
* @param message: 消息对象
* @param i: 错误码
* @param s: 错误信息
* @param s1: 交换机
* @param s2: 路由键
* @Return: void
*/
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("消息对象:"+message);
System.out.println("错误信息:"+s);
System.out.println("交换机:"+s1);
System.out.println("路由键:"+s2);
System.out.println("return执行了....");
}
});
rabbitTemplate.convertAndSend("test_exchange","confirm1","message confirm ....");
}
}
Consumer ACK
ack指Acknowledge,确认,表示消费端收到消息后的确认方式.
有三种确认方式:
- 自动确认:acknowledge=“none”
- 手动确认:acknowledge=“manual”
- 根据异常情况确认:acknowledge=“auto”(这种方式使用麻烦)
自动确认是指,当消息一旦被consumer接收到,则自动确认收到,并将相应message从rabbitmq消息缓存中移除,但是实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失,如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息.
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
listener:
simple:
acknowledge-mode: manual #开启手动ack
package com.qf.controller;/*
* Description:
* User:Hello,Mr.Zhou
* Date:2021-03-04
* Time:11:36
*
* */
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/*
* Consumer ACK机制
* 1.设置手动签收.acknowledge-mode: manual
* 2.让监听器类实现ChannelAwareMessageListener接口
* 3.如果消息成功处理,则调用channel的basicAck()签收
* 4.如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新发送给consumer
*
* */
@Component
public class AckListenter implements ChannelAwareMessageListener {
@RabbitListener(queues = "test_queue")
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接受转换消息
System.out.println(new String(message.getBody()));
//2.处理业务逻辑
System.out.println("处理业务逻辑");
//3.手动签收
// int i=1/0;
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
// e.printStackTrace();
//4.拒绝签收
/*
* 第三个参数:requeue:重回队列,如果设置为true,消息重新回到队列,broker会重新发送消息给消费端
* */
channel.basicNack(deliveryTag,true,true);
}
}
}
- 在配置文件中设置acknowledge属性,设置ack方式none:自动确认,manual:手动确认
- 如果在消费端出现异常,则调用channel.basicAck(deliveryTag.false);方法确认签收消息
- 如果出现异常,则在catch中调用basicNack或basicReject,拒绝接受消息,让mq重新发送消息
消息端限流
- 配置prefetch属性设置消费端一次拉取多少条消息
- 消费端的确认模式一定为手动确认.acknowledge=“manual”
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
listener:
simple:
acknowledge-mode: manual #开启手动ack
prefetch: 1 #一次接受一条消息
package com.qf.controller;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
/*
*
* @Author: Mr.Zhou
* @Date: 2021/3/4 20:04
* @Description: consumer 限流机制
* @param null:
* @Return:
*/
/*
* 1.确保ack机制为手动确认.
* 2.listener-container配置属性
* perfectch = 1,表示消费端每次从mq拉出一条消息来消费,直到确认消费完毕后,才会继续拉下一条消息.
*
* */
@Component
public class QosListenter implements ChannelAwareMessageListener {
@RabbitListener(queues = "test_queue")
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//1.获取消息
System.out.println(new String(message.getBody()));
//2.处理业务
//3.签收
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
TTL
- TTL全称Time To Live (存活时间/过期时间)
- 当消息到达存活时间后,还没有被消费,会被自动清除
- RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间
生产者[订单系统] --------> 中间件{30分钟} ------------>消费者[支付系统]
30分钟消息未被消费自动取消
死信队列
死信队列,英文缩写:DLX.Dead Letter Exchange(死信交换机)当消息成为死信后,可以被重新发送到另一个交换机,这个交换机就是DLX.
- 死信交换机和死信队列和普通的没有区别
- 当消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列
- 消息成为死信的三种情况:
- 队列消息长度达到限制
- 消费者拒绝接受消费信息,并且不重回队列
- 原队列在消息过期设置,消息到达超时时间未被消费
延迟队列
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费.
需求:
- 下单后,30分钟未支付,取消订单,回滚库存.
- 新用户注册成功7天后,发送短信问候.
实现方式:
-
定时器(不好,定时遍历订单时间,耗费资源)
-
延迟队列
订单系统----------> 延迟队列---------(30分钟后)----------->库存系统-----(判断订单1状态)—>支付了,什么都不做,未支付,回滚库存.
很可惜,在rabbitmq中并未提供延迟队列功能
但是可以使用:TTL+死信队列组合实现延迟队列的效果.(消费者监听死信队列)
日志与监控
消息可靠性分析与追踪
在使用任何消息中间件的过程中,难免会出现某条消息异常丢失的情况,对于RabbitMQ而言,可能是因为生产者或消费者与RabbitMQ断开了连接,而它们与RabbitMQ又采用了不同的确认机制;也有可能是因为交换器与队列之间不同的转发策略;甚至是交换器并没有与任何队列进行绑定,生产者又不感知或者没有采取相应的措施;另外RabbitMQ本身的集群策略也可能导致消息的丢失.这个时候就需要一个较好的机制跟踪记录消息的投递过程.以此协助开发和运维人员进行问题的定位.
在RabbitMQ中可以使用Firehose和rabbitmq_tracing插件功能来实现消息追踪.
消息追踪-Firehose
firehose的机制是将生产者投递给rabbitmq的消息,rabbitmq投递给消费者的消息按照指定的格式发送到默认的exchange上.这个默认的exchange的名称为amq.rabbitmq.trace,它是一个topic类型的exchange.发送到这个exchange上的消息的routing key为public.exchangename和deliver.queuename.其中exchangename和queuename为实际exchange和queue的名称.分别对应生产者投递到exchange的消息,和消费者从queue上获取的消息.
注意:打开trace会影响消息写入功能,适当打开后请关闭.
rabbitmqctl trace_on:开启Firehose命令
rabbitmqctl trace_off:关闭Firehose命令
消息追踪-rabbitmqq_tracing
rabbitmq_tracing和Firehose在实现上如出一辙,只不过rabbitmq_tracing的方式比Firehose多了一层GUI的包装,更容易使用和管理.
启用插件:rabbitmq-plugins enable rabbitmq_tracing
管理
RabbitMQ应用问题### RabbitMQ高级特性
消息可靠性投递
在使用rabbitMQ时,作为消息发送方希望杜绝任何消息丢失或者投递失败场景,rabbitMQ为我们提供了两种方式用来控制消息的投递可靠性模式.
- confirm 确认模式
- return 追回模式
rabbitmq整个消息投递路径为:producer—>exchange—>queue—>consumer
- 消息从producer到exchange则会返回一个confirmCallback
- 消息从exchange—>queue投递失败则会返回一个returnCallback.
利用这两个callback控制消息的可靠性传递
//配置信息
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
publisher-confirms: true
publisher-returns: true
package com.qf;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.Callable;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ProviderApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void contextConfirm() {
/*
* 确认模式:
* 步骤:
* 1.确认模式开启:publisher-confirms: true
* 2.在rabbitTemplate定义confirmCallback回调函数
* */
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* @Author: Mr.Zhou
* @Date: 2021/3/2 23:31
* @Description: confirm
* @param correlationData:相关配置信息
* @param b: exchange交换机是否成功收到了消息,true代表成功,false代表失败
* @param s: 当b为false时,s会显示失败原因
* @Return: void
*/
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
if (b){
//接收成功
System.out.println("消息接收成功");
}else{
//接收失败,处理消息再次发送
System.out.println("消息接受失败,失败原因:"+s);
}
System.out.println("confirmCallback执行了......");
}
});
//3.发送消息
rabbitTemplate.convertAndSend("test_exchange","confirm","message confirm ....");
}
@Test
public void contextReturn() {
/*
* 回退模式:当消息发送给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() {
/**
* @Author: Mr.Zhou
* @Date: 2021/3/4 11:09
* @Description: returnedMessage
* @param message: 消息对象
* @param i: 错误码
* @param s: 错误信息
* @param s1: 交换机
* @param s2: 路由键
* @Return: void
*/
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("消息对象:"+message);
System.out.println("错误信息:"+s);
System.out.println("交换机:"+s1);
System.out.println("路由键:"+s2);
System.out.println("return执行了....");
}
});
rabbitTemplate.convertAndSend("test_exchange","confirm1","message confirm ....");
}
}
Consumer ACK
ack指Acknowledge,确认,表示消费端收到消息后的确认方式.
有三种确认方式:
- 自动确认:acknowledge=“none”
- 手动确认:acknowledge=“manual”
- 根据异常情况确认:acknowledge=“auto”(这种方式使用麻烦)
自动确认是指,当消息一旦被consumer接收到,则自动确认收到,并将相应message从rabbitmq消息缓存中移除,但是实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失,如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息.
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
listener:
simple:
acknowledge-mode: manual #开启手动ack
package com.qf.controller;/*
* Description:
* User:Hello,Mr.Zhou
* Date:2021-03-04
* Time:11:36
*
* */
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/*
* Consumer ACK机制
* 1.设置手动签收.acknowledge-mode: manual
* 2.让监听器类实现ChannelAwareMessageListener接口
* 3.如果消息成功处理,则调用channel的basicAck()签收
* 4.如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新发送给consumer
*
* */
@Component
public class AckListenter implements ChannelAwareMessageListener {
@RabbitListener(queues = "test_queue")
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接受转换消息
System.out.println(new String(message.getBody()));
//2.处理业务逻辑
System.out.println("处理业务逻辑");
//3.手动签收
// int i=1/0;
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
// e.printStackTrace();
//4.拒绝签收
/*
* 第三个参数:requeue:重回队列,如果设置为true,消息重新回到队列,broker会重新发送消息给消费端
* */
channel.basicNack(deliveryTag,true,true);
}
}
}
- 在配置文件中设置acknowledge属性,设置ack方式none:自动确认,manual:手动确认
- 如果在消费端出现异常,则调用channel.basicAck(deliveryTag.false);方法确认签收消息
- 如果出现异常,则在catch中调用basicNack或basicReject,拒绝接受消息,让mq重新发送消息
消息端限流
- 配置prefetch属性设置消费端一次拉取多少条消息
- 消费端的确认模式一定为手动确认.acknowledge=“manual”
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
listener:
simple:
acknowledge-mode: manual #开启手动ack
prefetch: 1 #一次接受一条消息
package com.qf.controller;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
/*
*
* @Author: Mr.Zhou
* @Date: 2021/3/4 20:04
* @Description: consumer 限流机制
* @param null:
* @Return:
*/
/*
* 1.确保ack机制为手动确认.
* 2.listener-container配置属性
* perfectch = 1,表示消费端每次从mq拉出一条消息来消费,直到确认消费完毕后,才会继续拉下一条消息.
*
* */
@Component
public class QosListenter implements ChannelAwareMessageListener {
@RabbitListener(queues = "test_queue")
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//1.获取消息
System.out.println(new String(message.getBody()));
//2.处理业务
//3.签收
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
TTL
- TTL全称Time To Live (存活时间/过期时间)
- 当消息到达存活时间后,还没有被消费,会被自动清除
- RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间
生产者[订单系统] --------> 中间件{30分钟} ------------>消费者[支付系统]
30分钟消息未被消费自动取消
死信队列
死信队列,英文缩写:DLX.Dead Letter Exchange(死信交换机)当消息成为死信后,可以被重新发送到另一个交换机,这个交换机就是DLX.
- 死信交换机和死信队列和普通的没有区别
- 当消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列
- 消息成为死信的三种情况:
- 队列消息长度达到限制
- 消费者拒绝接受消费信息,并且不重回队列
- 原队列在消息过期设置,消息到达超时时间未被消费
延迟队列
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费.
需求:
- 下单后,30分钟未支付,取消订单,回滚库存.
- 新用户注册成功7天后,发送短信问候.
实现方式:
-
定时器(不好,定时遍历订单时间,耗费资源)
-
延迟队列
订单系统----------> 延迟队列---------(30分钟后)----------->库存系统-----(判断订单1状态)—>支付了,什么都不做,未支付,回滚库存.
很可惜,在rabbitmq中并未提供延迟队列功能
但是可以使用:TTL+死信队列组合实现延迟队列的效果.(消费者监听死信队列)
日志与监控
消息可靠性分析与追踪
在使用任何消息中间件的过程中,难免会出现某条消息异常丢失的情况,对于RabbitMQ而言,可能是因为生产者或消费者与RabbitMQ断开了连接,而它们与RabbitMQ又采用了不同的确认机制;也有可能是因为交换器与队列之间不同的转发策略;甚至是交换器并没有与任何队列进行绑定,生产者又不感知或者没有采取相应的措施;另外RabbitMQ本身的集群策略也可能导致消息的丢失.这个时候就需要一个较好的机制跟踪记录消息的投递过程.以此协助开发和运维人员进行问题的定位.
在RabbitMQ中可以使用Firehose和rabbitmq_tracing插件功能来实现消息追踪.
消息追踪-Firehose
firehose的机制是将生产者投递给rabbitmq的消息,rabbitmq投递给消费者的消息按照指定的格式发送到默认的exchange上.这个默认的exchange的名称为amq.rabbitmq.trace,它是一个topic类型的exchange.发送到这个exchange上的消息的routing key为public.exchangename和deliver.queuename.其中exchangename和queuename为实际exchange和queue的名称.分别对应生产者投递到exchange的消息,和消费者从queue上获取的消息.
注意:打开trace会影响消息写入功能,适当打开后请关闭.
rabbitmqctl trace_on:开启Firehose命令
rabbitmqctl trace_off:关闭Firehose命令
消息追踪-rabbitmqq_tracing
rabbitmq_tracing和Firehose在实现上如出一辙,只不过rabbitmq_tracing的方式比Firehose多了一层GUI的包装,更容易使用和管理.
启用插件:rabbitmq-plugins enable rabbitmq_tracing
管理
RabbitMQ应用问题
1.消息可靠性保障
-
消息补偿机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JFthTcIS-1616037789963)(C:\Users\Hello,Mr.Zhou\Desktop\QQ截图20210317180300.jpg)]
2.消息幂等性处理
幂等性指的是一次和多次请求同一个资源,对于资源本身应该具有同样的结果,也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同.
在mq中指的是,消费多条相同的消息,得到的消费该消息一次相同的结果.
- 乐观锁解决方案
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4j2LgUm0-1616037789966)(C:\Users\Hello,Mr.Zhou\Desktop\数据库乐观锁保障幂等性.jpg)]
RabbitMQ集群搭建
RabbitMQ高可用集群
1.消息可靠性保障
-
消息补偿机制
2.消息幂等性处理
幂等性指的是一次和多次请求同一个资源,对于资源本身应该具有同样的结果,也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同.
在mq中指的是,消费多条相同的消息,得到的消费该消息一次相同的结果.
- 乐观锁解决方案