1、交换机
假设工作队列背后,每个任务都恰好交付给一个消费者(工作进程)。在这一部分,我们将消息传达给多个消费者。这种模式称为“发布/订阅模式”。
1.1、Exchanges
1.1.1、Exchanges概念
RabbitMQ 消息传递模型的核心思想是:生产者生产的消息从不会直接发送到队列。实际上,通常生产者甚至都不知道这些消息传递到了哪些队列中。
相反,生产者只能将消息发送到交换机(exchange),交换机工作的内容非常简单,一方面它接收来自生产者的消息,另一方面将他们推入队列。交换机必须确切知道如何处理收到的消息。是应该把这些消息放到特定队列还是把他们放到许多队列还是说应该丢弃它们。这就由交换机的类型来决定。
1.1.2、Exchanges的类型
直接(direct)、主题(topic)、标题(headers)、扇出(fanout)
1.1.3、默认Exchanges
在之前的案例仍然能够将消息发送到队列,这是因为我们使用的是默认交换机,通过空字符串(" ")进行标识。
channel.basicPublish("",queueName,null,message.getBytes());
第一个参数是交换机的名字。空字符串表示默认或无名称交换机:消息能路由发送到队列中其实是由routingKey(bindingkey) 绑定 key 指定的,如果它存在的话。
1.2、临时队列
每当我们连接到 RabbitMQ 时,我们都需要一个全新的空队列,为此我们可以创建一个具有随机名称的队列,或者能让服务器为我们选择一个随机队列名称那就更好了。其次一旦我们断开了消费者的连接,队列将被自动删除。
创建临时队列的方法:
String queue = channel.queueDeclare().getQueue(); //得到的是队列名字
创建出来的队列:
1.3、绑定(bindings)
binding其实是exchange和queue之间的桥梁,它告诉我们exchange和那个队列进行了绑定关系。比如说下面这张图告诉我们的就是X与Q1和Q2进行了绑定

绑定过程:




1.4、Fanout
1.4.1、Fanout介绍
Fanout这种类型非常简单,它是将接收到的所有消息广播到它知道的所有队列中。
1.4.2、Fanout实战

一个生产者发送消息,全部消费者都能接收到消息。
生产者代码
package com.hxh.rabbitmq.five;
import com.hxh.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.util.Scanner;
//发送消息
public class EmitLog {
//交换机名称
private static final String EXCHANGE_NAME="logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String message = scanner.next();
channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes());
System.out.println("成功发送:"+message);
}
}
}
消费者代码:
package com.hxh.rabbitmq.five;
import com.hxh.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消息接收
public class ReceiveLogs01 {
//交换机名称
private static final String EXCHANGE_NAME="logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//声明一个交换机
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//声明一个临时队列
String queueName = channel.queueDeclare().getQueue();
//绑定交换机与队列
channel.queueBind(queueName,EXCHANGE_NAME,"");
System.out.println("01等待接收消息");
DeliverCallback deliverCallback=(var1,var2)->{
System.out.println("01接收消息:"+new String(var2.getBody()));
};
CancelCallback cancelCallback=(var1)->{
System.out.println("01取消");
};
channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
}
}
package com.hxh.rabbitmq.five;
import com.hxh.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消息接收
public class ReceiveLogs02 {
//交换机名称
private static final String EXCHANGE_NAME="logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//声明一个交换机
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//声明一个临时队列
String queueName = channel.queueDeclare().getQueue();
//绑定交换机与队列
channel.queueBind(queueName,EXCHANGE_NAME,"");
System.out.println("02等待接收消息");
DeliverCallback deliverCallback=(var1,var2)->{
System.out.println("02接收消息:"+new String(var2.getBody()));
};
CancelCallback cancelCallback=(var1)->{
System.out.println("02取消");
};
channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
}
}
1.5、Direct exchange
1.5.1、介绍
Fanout这种交换类型并不能给我们带来很大的灵活性,它只能进行无意识的广播,在这里我们将使用direct这种类型来进行替换,这种类型的工作方式是,消息只去到它绑定的routingKey队列中去。
在上面这张图中,我们可以看到X绑定了两个队列,绑定类型是direct。队列Q1绑定键为orange队列Q2绑定键有两个:一个绑定键为black,另一个绑定键为green.
在这种绑定情况下,生产者发布消息到exchange上,绑定键为orange的消息会被发布到队列Q1。绑定键为black和green的消息会被发布到队列Q2,其他消息类型的消息将被丢弃。
1.5.2、多重绑定
当然如果exchange的绑定类型是direct,但是它绑定的多个队列的key如果都相同,在这种情况下虽然绑定类型是direct,但是它表现的就和fanout有点类似了,就跟广播差不多,如下图所示。
1.5.3、实战


生产者代码:
package com.hxh.rabbitmq.six;
import com.hxh.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.util.Scanner;
public class DirectLogs {
//交换机名称
private static final String EXCHANGE_NAME="direct_logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String message = scanner.next();
channel.basicPublish(EXCHANGE_NAME,"error",null,message.getBytes()); //修改routingKey 使消息被不同的消费者接受
System.out.println("成功发送:"+message);
}
}
}
消费者代码:
package com.hxh.rabbitmq.six;
import com.hxh.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
public class ReceiveLogsDirect01 {
//交换机名称
private static final String EXCHANGE_NAME="direct_logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//声明一个交换机
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
//声明一个队列
channel.queueDeclare("console", false, true, true, null);
//绑定交换机与队列
channel.queueBind("console",EXCHANGE_NAME,"info");
channel.queueBind("console",EXCHANGE_NAME,"warning");
System.out.println("01等待接收消息");
DeliverCallback deliverCallback=(var1, var2)->{
System.out.println("01接收消息:"+new String(var2.getBody()));
};
CancelCallback cancelCallback=(var1)->{
System.out.println("01取消");
};
channel.basicConsume("console",true,deliverCallback,cancelCallback);
}
}
package com.hxh.rabbitmq.six;
import com.hxh.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
public class ReceiveLogsDirect02 {
//交换机名称
private static final String EXCHANGE_NAME="direct_logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//声明一个交换机
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
//声明一个队列
channel.queueDeclare("disk", false, true, true, null);
//绑定交换机与队列
channel.queueBind("disk",EXCHANGE_NAME,"error");
System.out.println("02等待接收消息");
DeliverCallback deliverCallback=(var1, var2)->{
System.out.println("02接收消息:"+new String(var2.getBody()));
};
CancelCallback cancelCallback=(var1)->{
System.out.println("02取消");
};
channel.basicConsume("disk",true,deliverCallback,cancelCallback);
}
}
1.6、Topics
1.6.1、Topic的要求
发送到类型是topic交换机的消息的 routingkey 不能随意写,必须满足一定的要求,它必须是一个单词列表,以点号分隔开。这些单词可以是任意单词,比如说:"stock.usd.nyse". "nyse.vmw","quick.orange.rabbit".这种类型的。当然这个单词列表最多不能超过255个字节。
在这个规则列表中,其中有两个替换符是大家需要注意的:
*(星号)可以代替一个单词
#(并号)可以替代零个或多个单词
1.6.2、Topic匹配案例
下图
Q1绑定的是:中间带 orange 带3个单词的字符串(*.orange.*)
Q2绑定的是:最后一个单词是 rabbit 的3个单词(*.*.rabbit)、第一个单词是 lazy 的多个单词(lazy.#)

例如:
quick.orange.rabbit——被队列Q1Q2接收到
lazy.orange.elephant——被队列Q1Q2接收到
quick.orange.fox——被队列Q1接收到
lazy.brown.fox——被队列Q2接收到
lazy.pink.rabbit——虽然满足两个绑定但只被队列Q2接收一次
quick.brown.fox——不匹配任何绑定不会被任何队列接收到会被丢弃
quick.orange.male.rabbit——是四个单词不匹配任何绑定会被丢弃
lazy.orange.male.rabbit——是四个单词但匹配Q2
当队列绑定关系是下列这种情况时需要引起注意:
当一个队列绑定键是#,那么这个队列将接收所有数据,就有点像fanout了
如果队列绑定键当中没有#和*出现,那么该队列绑定类型就是direct 了
1.6.3、实战
生产者代码:
package com.hxh.rabbitmq.seven;
import com.hxh.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.util.HashMap;
import java.util.Map;
//生产者
public class EmitLogTopic {
private static final String EXCHANGE_NAME="topic_logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
HashMap<String, String> map = new HashMap<>();
map.put("quick.orange.rabbit","被队列Q1Q2接收到");
map.put("lazy.orange.elephant","被队列Q1Q2接收到");
map.put("quick.orange.fox","被队列Q1接收到");
map.put("lazy.brown.fox","被队列Q2接收到");
map.put("quick.brown.fox","虽然满足两个绑定但只被队列Q2接收一次");
map.put("lazy.pink.rabbit","不匹配任何绑定不会被任何队列接收到会被丢弃");
map.put("quick.orange.male.rabbit","是四个单词不匹配任何绑定会被丢弃");
map.put("lazy.orange.male.rabbit","是四个单词但匹配Q2");
for (Map.Entry<String, String> bindingKey : map.entrySet()) {
String key = bindingKey.getKey();
String message = bindingKey.getValue();
channel.basicPublish(EXCHANGE_NAME,key,null,message.getBytes());
System.out.println("生产者发出:"+message);
}
}
}
消费者代码:
package com.hxh.rabbitmq.seven;
import com.hxh.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消费者
public class ReceiveLogsTopic01 {
private static final String EXCHANGE_NAME="topic_logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
String queueName="Q1";
channel.queueDeclare(queueName,false,false,false,null);
channel.queueBind(queueName,EXCHANGE_NAME,"*.orange.*");
System.out.println("01等待接收...");
DeliverCallback deliverCallback=(var1, var2)->{
System.out.println("01接收消息:"+new String(var2.getBody()));
System.out.println("绑定键:"+var2.getEnvelope().getRoutingKey());
};
CancelCallback cancelCallback=(var1)->{
System.out.println("01取消");
};
channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
}
}
package com.hxh.rabbitmq.seven;
import com.hxh.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
//消费者
public class ReceiveLogsTopic02 {
private static final String EXCHANGE_NAME="topic_logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
String queueName="Q2";
channel.queueDeclare(queueName,false,false,false,null);
channel.queueBind(queueName,EXCHANGE_NAME,"*.*.rabbit");
channel.queueBind(queueName,EXCHANGE_NAME,"lazy.#");
System.out.println("02等待接收...");
DeliverCallback deliverCallback=(var1, var2)->{
System.out.println("02接收消息:"+new String(var2.getBody()));
System.out.println("绑定键:"+var2.getEnvelope().getRoutingKey());
};
CancelCallback cancelCallback=(var1)->{
System.out.println("02取消");
};
channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
}
}
2、死信队列
2.1、死信的概念
死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer将消息投递到broker或者直接到queue里了,consumer 从queue取出消息进行消费,但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。
应用场景:为了保证订单业务的消息数据不丢失,需要使用到RabbitMQ的死信队列机制,当消息消费发生异常时,将消息投入死信队列中,还有比如说:用户在商城下单成功并点击去支付后在指定时间未支付时自动失效
2.2、死信的来源
消息TTL过期
队列达到最大长度(队列满了,无法在添加数据到mq中)
消息被拒绝(basic.reject或basic.nack)并且 requeue=false。
2.3、死信实战
2.3.1、代码架构图

2.3.2、消息TTL过期
生产者
package com.hxh.rabbitmq.eight;
import com.hxh.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
//生产者
public class Producer {
private static final String NORMAL_EXCHANGE="normal_exchange";
private static final String NORMAL_QUEUE="normal_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//死信消息 设置TTL时间 单位是ms
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
for (int i = 1; i < 11; i++) {
String message=i+"";
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
System.out.println("发送消息:"+i);
}
}
}
消费者01
package com.hxh.rabbitmq.eight;
import com.hxh.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.util.HashMap;
import java.util.Map;
//普通队列 消费者01
public class Consumer01 {
//声明普通和死信的交换机、队列
private static final String NORMAL_EXCHANGE="normal_exchange";
private static final String DEAD_EXCHANGE="dead_exchange";
private static final String NORMAL_QUEUE="normal_queue";
private static final String DEAD_QUEUE="dead_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//声明普通和死信的交换机
channel.exchangeDeclare(NORMAL_EXCHANGE,"direct");
channel.exchangeDeclare(DEAD_EXCHANGE,"direct");
//声明普通队列
Map<String, Object> arguments = new HashMap<>();
//设置死信交换机
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//设置死信RoutingKey
arguments.put("x-dead-letter-routing-key","lisi");
channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);
//声明死信队列
channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
//绑定队列与交换机
channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");
channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");
System.out.println("等待消息...");
DeliverCallback deliverCallback=(var1,var2)->{
System.out.println("01接收消息:"+new String(var2.getBody(),"utf-8"));
};
CancelCallback cancelCallback=var1->{
System.out.println("01取消了....");
};
channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,cancelCallback);
}
}
消费者02
package com.hxh.rabbitmq.eight;
import com.hxh.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.util.HashMap;
import java.util.Map;
//死信队列 消费者02
public class Consumer02 {
//声明死信队列
private static final String DEAD_QUEUE="dead_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
System.out.println("等待消息...");
DeliverCallback deliverCallback=(var1,var2)->{
System.out.println("02接收消息:"+new String(var2.getBody(),"utf-8"));
};
CancelCallback cancelCallback=var1->{
System.out.println("02取消了....");
};
channel.basicConsume(DEAD_QUEUE,true,deliverCallback,cancelCallback);
}
}
2.3.3、队列达到最大长度
生产者

消费者01

消费者02不变
2.3.4、消息被拒绝
生产者

消费者01

消费者02不变
本文详细介绍了RabbitMQ的交换机机制,包括Exchanges的概念、类型如Direct、Fanout、Topic,并探讨了临时队列的创建和删除。此外,还详细阐述了死信队列的原理,如消息成为死信的来源及实战应用。

4万+

被折叠的 条评论
为什么被折叠?



