前记:
MQ(message queue),从字面意思上看,本质是个队列,FIFO 先入先出,只不过队列中存放的内容是
message 而已,还是一种跨进程的通信机制,用于上下游传递消息。在互联网架构中,MQ 是一种非常常
见的上下游“逻辑解耦+物理解耦”的消息通信服务。使用了 MQ 之后,消息发送上游只需要依赖 MQ,不
用依赖其他服务。
MQ – 一种通信机制。
根据四大MQ的特点来选择即可。
生产者
产生数据发送消息的程序是生产者
交换机
交换机是 RabbitMQ 非常重要的一个部件,一方面它接收来自生产者的消息,另一方面它将消息
推送到队列中。交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是推
送到多个队列,亦或者是把消息丢弃,这个得有交换机类型决定
队列
队列是 RabbitMQ 内部使用的一种数据结构,尽管消息流经 RabbitMQ 和应用程序,但它们只能存
储在队列中。队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲区。许多生产者可
以将消息发送到一个队列,许多消费者可以尝试从一个队列接收数据。这就是我们使用队列的方式
消费者
消费与接收具有相似的含义。消费者大多时候是一个等待接收消息的程序。请注意生产者,消费
者和消息中间件很多时候并不在同一机器上。同一个应用程序既可以是生产者又是可以是消费者。
**Broker:**接收和分发消息的应用,RabbitMQ Server 就是 Message Broker
**Virtual host:**出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似
于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出
多个 vhost,每个用户在自己的 vhost 创建 exchange/queue 等
**Connection:**publisher/consumer 和 broker 之间的 TCP 连接
Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP
Connection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程
序支持多线程,通常每个 thread 创建单独的 channel 进行通讯,AMQP method 包含了 channel id 帮助客
户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。 l Channel 作为轻量级的
Connection 极大减少了操作系统建立 TCP connection 的开销
Exchange : message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发
消息到 queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout
(multicast)
Queue : 消息最终被送到这里等待 consumer 取走
Binding : exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key,Binding 信息被保
存到 exchange 中的查询表中,用于 message 的分发依据
详细看文档
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>8source>
<target>8target>
configuration>
plugin>
plugins>
build>
<dependencies>
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
<version>5.8.0version>
dependency>
<dependency>
<groupId>commons-iogroupId>
<artifactId>commons-ioartifactId>
<version>2.6version>
dependency>
dependencies>
public class Producer {
public static final String QUEUE_NAME = "myQueue";
public static void main(String[] args) throws Exception{
//创建工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("xx.xxx.xxx.xxx");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("123");
//得到连接
Connection connection = connectionFactory.newConnection();
//得到信道 及 队列
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//发送 (用默认的交换机)
String message = "hello world!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("生产OK");
}
}
public class Consumer {
public static void main(String[] args) throws Exception{
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("xx.xxx.xxx.xxx");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("123");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
DeliverCallback deliverCallback = new DeliverCallback() { //接收成功回调接口
public void handle(String consumerTag, Delivery message) throws IOException {
System.out.println("consumerTag = " + consumerTag);
String msg = new String(message.getBody());
System.out.println("msg = " + msg);
}
};
CancelCallback cancelCallback = consumerTag -> {//消费失败的回调接口
System.out.println("失败啦!!consumerTag = " + consumerTag);
};
channel.basicConsume(Producer.QUEUE_NAME, true, deliverCallback, cancelCallback);
}
}
工作模式,又称简单队列
为了保证消息在发送过程中不丢失,rabbitmq 引入消息应答机制,
消息应答就是: 消费者在接收到消息并且处理该消息之后,告诉rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了
channel.basicAck(deliveryTag, true)
public class RabbitMQUtils {
public static Channel getChannel(){
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("xx.xxx.xxx.xxx");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("123");
Connection connection = null;
Channel channel = null;
try {
connection = connectionFactory.newConnection();
channel = connection.createChannel();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return channel;
}
}
public class Producer {
public static final String QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws IOException {
Channel channel = RabbitMQUtils.getChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
Scanner scanner = new Scanner(System.in);
while (true) {
String message = scanner.next();
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("发送了:" + message);
}
}
}
public class Work01 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitMQUtils.getChannel();
System.out.println("01处理消息快!");
DeliverCallback deliverCallback = new DeliverCallback() { //接收成功回调接口
public void handle(String consumerTag, Delivery message) throws IOException {
try {
Thread.sleep(1 * 1000); //模拟处理速度
} catch (InterruptedException e) {
e.printStackTrace();
}
String msg = new String(message.getBody());
System.out.println("msg = " + msg);
/**
* 手动应答代码:
* message.getEnvelope().getDeliveryTag() :消息的唯一标志
* false :不用批量应答
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
CancelCallback cancelCallback = consumerTag -> {//消费失败的回调接口
System.out.println("失败啦!!consumerTag = " + consumerTag);
};
//关闭了自动应答
channel.basicConsume(Producer.QUEUE_NAME, false, deliverCallback, cancelCallback);
}
}
public class Work02 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitMQUtils.getChannel();
System.out.println("02处理消息慢!");
DeliverCallback deliverCallback = new DeliverCallback() { //接收成功回调接口
public void handle(String consumerTag, Delivery message) throws IOException {
try {
Thread.sleep(15 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String msg = new String(message.getBody());
System.out.println("msg = " + msg);
/**
* 手动应答代码:
* message.getEnvelope().getDeliveryTag() :消息的唯一标志
* false :不用批量应答
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
CancelCallback cancelCallback = consumerTag -> {//消费失败的回调接口
System.out.println("失败啦!!consumerTag = " + consumerTag);
};
channel.basicConsume(Producer.QUEUE_NAME, false, deliverCallback, cancelCallback);
}
}
模拟的情况如下:
生产者发送消息1和2,可以见到消息以轮询的方式工作。
生产者发送消息3和4,见到3被01线程立即处理。
而消息4,正在被02线程处理。
此时,把02线程关闭。
可以见到,由于没有应答,所以把消息4重新分发给了线程01。
1
发送了:1
2
发送了:2
3
发送了:3
4
发送了:4
01处理消息快!
msg = 1
msg = 3
msg = 4
02处理消息慢!
msg = 2
如果队列和消息不进行持久化,那么队列和消息是存储内存之中的,如果断电或者RMQ重启,那么队列和消息就会消失。
要保证消息和队列不丢失,那么要将队列和消息进行持久化到磁盘中去。
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
在声明队列的时候,把第二个参数durable改成true即可。
注意:如果已经存在了同名的不持久化队列,那么再声明会报错
在管理界面可以看到相关的信息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
把第三个参数props, 传入
MessageProperties.PERSISTENT_TEXT_PLAIN
参数。
注意:持久化不能保证消息一定不丢失,如在保存在磁盘的过程中断电或重启了
channel.basicQos(0);
默认参数是0,表示是公平分发,即轮询分发channel.basicQos(int preFetch);
channel.confimSelect()
在生产者上开启。
关键代码:
public class Producer {
public static void main(String[] args) throws Exception {
publicSingle();//publicSingle花费了:16791ms
System.out.println("-------------------------------------------");
publicBatch();
System.out.println("--------------------------------------------");
publicSyn();
}
//单个确认模式
public static void publicSingle() throws Exception{
Channel channel = RabbitMQUtils.getChannel();
channel.confirmSelect(); //-----------------开启发布确认
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, false, false, false, null);
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
String message = "消息" + i;
channel.basicPublish("", queueName, null, message.getBytes());
boolean confirms = channel.waitForConfirms();//-----------------等待发布确认
if (!confirms) {
System.out.println(message + "发布失败");
}
}
long end = System.currentTimeMillis();
System.out.println("publicSingle花费了:" + (end - begin) + "ms");
}
//批量确认模式
public static void publicBatch() throws Exception {
Channel channel = RabbitMQUtils.getChannel();
channel.confirmSelect(); //-----------------开启发布确认
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, false, false, false, null);
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
String message = "消息" + i;
channel.basicPublish("", queueName, null, message.getBytes());
if ((i + 1) % 100 == 0) { //每隔100条确认
boolean confirms = channel.waitForConfirms();//-----------------等待发布确认
if (!confirms) {
System.out.println(message + "发布失败");
}
}
}
long end = System.currentTimeMillis();
System.out.println("publicBatch花费了:" + (end - begin) + "ms");
}
//异步确认模式
public static void publicSyn() throws Exception{
Channel channel = RabbitMQUtils.getChannel();
channel.confirmSelect(); //-----------------开启发布确认
ConfirmCallback ackCallback = (deliveryTag, multiple) ->{ //注意,确认和未确认回调接口,都一个类型的,即ConfirmCallback
if(!multiple) {
System.out.println("MQ未开启批量确认");
}
System.out.println(deliveryTag + "号消息成功发送了");
};
ConfirmCallback nackCallback = (deliveryTag, multiple) ->{
if(!multiple) {
System.out.println("MQ未开启批量确认");
}
System.out.println(deliveryTag + "号消息成功失败了");
};
channel.addConfirmListener(ackCallback, nackCallback);//其中一个值可以是null,代表不监听成功或失败的确认回调
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, false, false, false, null);
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
String message = "消息" + i;
channel.basicPublish("", queueName, null, message.getBytes());
if ((i + 1) % 100 == 0) { //每隔100条确认
boolean confirms = channel.waitForConfirms();//-----------------等待发布确认
if (!confirms) {
System.out.println(message + "发布失败");
}
}
}
long end = System.currentTimeMillis();
System.out.println("publicSyn花费了:" + (end - begin) + "ms");
}
}
publicSingle花费了:14840ms
-------------------------------------------
publicBatch花费了:40ms
--------------------------------------------
13号消息成功发送了
19号消息成功发送了
26号消息成功发送了
35号消息成功发送了
45号消息成功发送了
49号消息成功发送了
60号消息成功发送了
71号消息成功发送了
83号消息成功发送了
87号消息成功发送了
99号消息成功发送了
MQ未开启批量确认 //开启了多线程来进行处理。
100号消息成功发送了
.....
publicSyn花费了:310ms
步骤:
public static void publishMessageAsync() throws Exception {
try (Channel channel = RabbitMqUtils.getChannel()) {
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, false, false, false, null);
// 开启发布确认
channel.confirmSelect();
/**
* 线程安全有序的一个哈希表,适用于高并发的情况
* 1. 轻松的将序号与消息进行关联
* 2. 轻松批量删除条目 只要给到序列号
* 3. 支持并发访问
*/
ConcurrentSkipListMap<Long, String> outstandingConfirms = new
ConcurrentSkipListMap<>();
/**
* 确认收到消息的一个回调
* 1. 消息序列号
* 2.true 可以确认小于等于当前序列号的消息
* false 确认当前序列号消息
*/
ConfirmCallback ackCallback = (sequenceNumber, multiple) -> {
if (multiple) {
// 返回的是小于等于当前序列号的未确认消息 是一个 map
ConcurrentNavigableMap<Long, String> confirmed =
outstandingConfirms.headMap(sequenceNumber, true);
// 清除该部分未确认消息
confirmed.clear();
}else{
// 只清除当前序列号的消息
outstandingConfirms.remove(sequenceNumber);
}
};
ConfirmCallback nackCallback = (sequenceNumber, multiple) -> {
String message = outstandingConfirms.get(sequenceNumber);
System.out.println(" 发布的消息"+message+" 未被确认,序列号"+sequenceNumber);
};
/**
* 添加一个异步确认的监听器
* 1. 确认收到消息的回调
* 2. 未收到消息的回调
*/
channel.addConfirmListener(ackCallback, null);
long begin = System.currentTimeMillis();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String message = " 消息" + i;
/**
* channel.getNextPublishSeqNo() 获取下一个消息的序列号
* 通过序列号与消息体进行一个关联
* 全部都是未确认的消息体
*/
outstandingConfirms.put(channel.getNextPublishSeqNo(), message);
channel.basicPublish("", queueName, null, message.getBytes());
}
long end = System.currentTimeMillis();
System.out.println(" 发布" + MESSAGE_COUNT + " 个异步确认消息, 耗时" + (end - begin) +
"ms");
}
}
总共有以下类型:
直接(direct), 主题(topic) ,标题(headers) , 扇出(fanout)
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
其中的空字符串代表的是默认交换机String queueName = channel.queueDeclare().getQueue();
channel.basicPublish("", "", null, message.getBytes());
。即消息的发送到队列,只需要指定交换机名称注意点
需要注意的是,本章学习的是交换机,及交换机与队列之间的绑定,与消费者的代码其实是没有关系的。
交换机的类型声明与绑定,在生产者(消费者也行)中声明即可。
生产者要发布消息,需要指明交换机和绑定。
消费者仍然面向队列来消费消息。
关键代码:
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
channel.basicPublish(EXCHANGE_NAME,rountingKey, null,message.getBytes("UTF-8"));
/**
* 把该有的结构给定义出来,方便生产者和消费者直接调用
*/
public class Construction {
//定义各结构的名字
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static final String DEAD_EXCHANGE = "dead_exchange";
public static final String NORMAL_QUEUE = "normal_queue";
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMQUtils.getChannel();
//1、声明交换机
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
//2、声明普通队列。唯一不同的是,多了一个到死信队列的配置参数
//可以看到,到死信队列需要指明 交换机名称 和 绑定
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);//到哪个交换机
arguments.put("x-dead-letter-routing-key", "lisi");//交换机的哪个routinKey
/*
arguments.put("x-max-length", 6);//限制最大长度用的
arguments.put("x-message-ttl", 10000)//消息在队列的存活时间为10s
也可以在生产者发布消息的时候去定义:
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
*/
channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);
//2、声明死信队列
channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
//3、建立绑定
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");
System.out.println("关系确定成功");
}
}
1、消息 TTL 过期
可以声明队列是一个具有时效消息的队列:arguments.put("x-message-ttl", 10000)
可以在发布的时候,定义消息的时效:
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", properties, message.getBytes());
而后,模拟消费者宕机即可
2、队列达到最大长度
arguments.put("x-max-length", 6);//限制最大长度用的
3、消息被拒绝(basicReject 或 basicNack)并且 requeue=false(重入队参数)
首先把消费者的自动应答关闭
channel.basicReject(long deliveryTag, boolean requeue)
, 重点是把requeue 设置成falsechannel.basicReject(long deliveryTag, boolean multiple, boolean requeue)
, 重点是把requeue 设置成false rabbitTemplate.convertAndSend(exchange, queue, message, (correlationData) ->{
correlationData.getMessageProperties().setExpiration("10000");//源码中自己都写了:why not a Date or long?
return correlationData;
});
//声明QB,并指定死信队列 及 过期时间
@Bean(NORMAL_QUEUE_QB)
public Queue QBQueue() {
return QueueBuilder
.durable(NORMAL_QUEUE_QB)
.deadLetterExchange(DEAD_EXCHANGE_Y)//也可以像原生的那样加arguments参数
.deadLetterRoutingKey("YD")
.ttl(20000)
.build();
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.47version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>org.springframework.amqpgroupId>
<artifactId>spring-rabbit-testartifactId>
<scope>testscope>
dependency>
spring.rabbitmq.host=182.92.234.71
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123
@Configuration
public class TTLConfig {
//定义各结构的名字
public static final String NORMAL_EXCHANGE_X = "normal_exchange_x";
public static final String DEAD_EXCHANGE_Y = "dead_exchange_y";
public static final String NORMAL_QUEUE_QA = "normal_queue_qa";
public static final String NORMAL_QUEUE_QB = "normal_queue_qb";
public static final String DEAD_QUEUE_QD = "dead_queue_qd";
@Bean(NORMAL_EXCHANGE_X)
public DirectExchange XExchange() {
return ExchangeBuilder
.directExchange(NORMAL_EXCHANGE_X)
.build();
}
@Bean(DEAD_EXCHANGE_Y)
public DirectExchange YExchange() {
return ExchangeBuilder
.directExchange(DEAD_EXCHANGE_Y)
.build();
}
//声明QA,并指定死信队列 及 过期时间
@Bean(NORMAL_QUEUE_QA)
public Queue QAQueue() {
return QueueBuilder
.durable(NORMAL_QUEUE_QA)
.deadLetterExchange(DEAD_EXCHANGE_Y)
.deadLetterRoutingKey("YD")
.ttl(10000)
.build();
}
//声明QB,并指定死信队列 及 过期时间
@Bean(NORMAL_QUEUE_QB)
public Queue QBQueue() {
return QueueBuilder
.durable(NORMAL_QUEUE_QB)
.deadLetterExchange(DEAD_EXCHANGE_Y)//也可以像原生的那样加arguments参数
.deadLetterRoutingKey("YD")
.ttl(20000)
.build();
}
//声明QD,
@Bean(DEAD_QUEUE_QD)
public Queue QDQueue() {
return QueueBuilder.durable(DEAD_QUEUE_QD).build();
}
//以下是声明绑定
@Bean
public Binding QAToX(@Qualifier(NORMAL_QUEUE_QA) Queue QA,
@Qualifier(NORMAL_EXCHANGE_X) DirectExchange directExchange) {
return BindingBuilder
.bind(QA)
.to(directExchange)
.with("XA");
}
@Bean
public Binding QBToX(@Qualifier(NORMAL_QUEUE_QB) Queue QB,
@Qualifier(NORMAL_EXCHANGE_X) DirectExchange directExchange) {
return BindingBuilder
.bind(QB)
.to(directExchange)
.with("XB");
}
@Bean
public Binding QDToY(@Qualifier(DEAD_QUEUE_QD) Queue QD,
@Qualifier(DEAD_EXCHANGE_Y) DirectExchange directExchange) {
return BindingBuilder
.bind(QD)
.to(directExchange)
.with("YD");
}
}
@RestController
@RequestMapping("/ttl")
@Slf4j
public class TTLController {
@Autowired
RabbitTemplate rabbitTemplate;
@GetMapping("/test/{message}")
public String test(@PathVariable("message") String message) {
rabbitTemplate.convertAndSend(TTLConfig.NORMAL_EXCHANGE_X, "XA", "发送到QA:" + message);
rabbitTemplate.convertAndSend(TTLConfig.NORMAL_EXCHANGE_X, "XB", "发送到QB:" + message);
log.info("消息发送时间:{},内容:{}", new Date().toString(), message);
return message + "发送成功";
}
}
@Component
@Slf4j
public class TTLConsumer {
@RabbitListener(queues = TTLConfig.DEAD_QUEUE_QD)//监听队列QD
public void receiveQD(Message message) {
String msg = new String(message.getBody());
log.info("QD接受到的时间为:{},内容为:{}", new Date().toString(), msg);
}
}
// 自定义交换机 我们在这里定义的是一个延迟交换机
@Bean
public CustomExchange delayedExchange() {
Map<String, Object> args = new HashMap<>();
// 自定义交换机的类型
args.put("x-delayed-type", "direct");
return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false,
args);
}
(在下面的实战用到的)
spring.rabbitmq.publisher-confirm-type=correlated
有三个参数:
在仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消息生产者发送确认消息,如
果发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃这个事件的。
解决方法: rabbitTemplate.setMandatory(true);//设置队列收不到时的退回
开启SptrinBoot配置
spring.rabbitmq.publisher-confirm-type=correlated #确认
spring.rabbitmq.publisher-returns=true #退回 经过测试,效果和相同rabbitTemplate.setMandatory(true);
配置类
定义结构
@Configuration
public class AdvancedPublisherConfig {
//交换机
public static final String EXCHANGE = "abc";
//队列
public static final String QUEUE = "abcd";
//routingKey
public static final String ROUTING_KEY = "adbde";
@Bean(EXCHANGE)
public DirectExchange exchange() {
return ExchangeBuilder
.directExchange(EXCHANGE)
.build();
}
@Bean(QUEUE)
public Queue queue() {
return QueueBuilder
.durable(QUEUE)
.build();
}
@Bean
public Binding binding(@Qualifier(QUEUE) Queue queue,
@Qualifier(EXCHANGE) DirectExchange directExchange) {
return BindingBuilder
.bind(queue)
.to(directExchange)
.with(ROUTING_KEY);
}
}
高级生产者
@Component
@Slf4j
//为了更体现通用性,可以另外起一个类专门实现RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback接口
public class AdvancedPublisher implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
@Autowired
RabbitTemplate rabbitTemplate;
public void publishMsg(String exchange, String routingKey, String message) {
CorrelationData correlationData = new CorrelationData();
correlationData.setId("发布信息的id");
rabbitTemplate.convertAndSend(exchange,
routingKey,
message.getBytes(StandardCharsets.UTF_8),
correlationData);
System.out.println("已经发布消息:" + message);
}
/**
* 会在构造方法和init()方法之前执行
*/
@PostConstruct
public void setCallBackInterfaces() {
//通过源码可以看到,一个rabbitTemplate只能设置一个ConfirmCallback接口和ReturnCallback接口
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setMandatory(true);//设置队列收不到时的退回
rabbitTemplate.setReturnCallback(this);
}
/**
* 当数据不能到达交换机时,回调接口RabbitTemplate.ConfirmCallback
* @param correlationData 数据集
* @param ack 交换机是否已经进行了应答
* @param cause
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
log.info("交换机已经收到消息了,消息id={}", correlationData.getId());
} else {
log.info("消息id={},没有被接受到\n,原因为{}", correlationData.getId(), cause);
}
}
/**
* 当数据不能到达队列时,回调接口RabbitTemplate.ReturnCallback
* @param message 回退的消息
* @param replyCode 回退的代号
* @param replyText 回退的提示语
* @param exchange 回退的交换机
* @param routingKey 回退的routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("new String(message.getBody()) = " + new String(message.getBody(), StandardCharsets.UTF_8));
System.out.println("replyCode = " + replyCode);
System.out.println("replyText = " + replyText);
System.out.println("exchange = " + exchange);
System.out.println("routingKey = " + routingKey);
}
}
测试代码
@SpringBootTest
class SpringbootMqApplicationTests {
@Autowired
AdvancedPublisher advancedPublisher;
@Test
void contextLoads() {
advancedPublisher.publishMsg(AdvancedPublisherConfig.EXCHANGE, AdvancedPublisherConfig.ROUTING_KEY, "正常消息");
advancedPublisher.publishMsg("我是乱来的交换机", AdvancedPublisherConfig.ROUTING_KEY, "发送到不存在的交换机");
advancedPublisher.publishMsg(AdvancedPublisherConfig.EXCHANGE, "我是乱来的路由", "发送到不存在的队列");
}
}
结果
已经发布消息:正常消息, id = 1
交换机已经收到消息了,消息id=1
已经发布消息:发送到不存在的交换机, id = 2
消息id=2,没有被接受到,原因为channel error; protocol method: method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange '我是乱来的交换机' in vhost '/', class-id=60, method-id=40)
已经发布消息:发送到不存在的队列, id = 3
new String(message.getBody()) = 发送到不存在的队列
replyCode = 312
replyText = NO_ROUTE
exchange = abc
routingKey = 我是乱来的路由
交换机已经收到消息了,消息id=3 //注意这个,即使队列回退,也会回调ConfimCallback接口
如果是confim-type=simple
手动应答代码如下:
rabbitTemplate.invoke(operations -> {
rabbitTemplate.convertAndSend(
exchange,
routingKey,
message,
correlationData
);
return rabbitTemplate.waitForConfirms(2 * 1000);
});
.withArgument("alternate-exchange", BACKUP_EXCHANGE_NAME);// 设置该交换机的备份交换机
@Bean("confirmExchange")
public DirectExchange confirmExchange(){
ExchangeBuilder exchangeBuilder =
ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME)
.durable(true)
.withArgument("alternate-exchange", BACKUP_EXCHANGE_NAME);// 设置该交换机的备份交换机
return (DirectExchange)exchangeBuilder.build();
}
在该队列中,消息附带着优先级,MQ会优先处理优先级高的队列
和原来普通的队列数据结构是不一样的
代码:
//声明优先级队列
params.put("x-max-priority", 10);
channel.queueDeclare(QUEUE_NAME, true, false, false, params);
//发送带有优先级的消息
//如果不设置,则是使用默认优先级,即最低优先级
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().priority(5).build();
channel.basicPublish("", QUEUE_NAME, properties, message.getBytes());
概念:将消息尽可能存入到磁盘中的队列,在要消费时才把消息加载到内存
设计目标:防止消息堆积,让队列容纳更多的消息
与持久化的区别:持久化的消息,在被写入磁盘的同时也会在内存中驻留一份备份
缺点:当 RabbitMQ 需要释放内存的时候,会将内存中的消息换页至磁盘中,这个操作会耗费较长的
时间,也会阻塞队列的操作,进而无法接收新的消息
代码:
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-queue-mode", "lazy");
channel.queueDeclare("myqueue", false, false, false, args);
暂时用不到,记录下知识点: