1、Exchange
1.1交换机概念
RabbitMQ 消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列。实际上,通常生产者甚至都不知道这些消息传递传递到了哪些队列中。
相反,生产者只能将消息发送到交换机(exchange),交换机工作的内容非常简单,一方面它接收来自生产者的消息,另一方面将它们推入队列。
交换机必须确切知道如何处理收到的消息。应该把这些消息放到特定队列还是说把他们到许多队列中还是说应该丢弃它们。这就的由交换机的类型来决定。
1.2交换机类型
主要包含以下四种类型:
- Direct exchange(直连交换机)
- Fanout exchange(扇型交换机)
- Topic exchange(主题交换机)
- Headers exchange(头交换机)
另外还有RabbitMQ的默认交换机:
- 默认交换机
- amq.* exchanges
还有一类特殊的交换机:Dead Letter Exchange(死信交换机)
1.3无名exchange
在前面部分我们对 exchange 一无所知,但仍然能够将消息发送到队列。之前能实现的原因是因为我们使用的是默认交换,我们通过空字符串(“”)进行标识。
channel.basicPublish("","123",null,message.getBytes("UTF-8"));
第一个参数就是交换机的名字,空字符串表示默认或无名交换机。消息能路由发送到队列中其实 是由 routingKey(bindingkey)绑定 key 指定的,如果它存在的话
1.4临时队列
临时队列,顾名思义就是临时的,如果没有设置持久化,这个队列旦断开连接它就会被删除。
每当我们连接到 Rabbit 时,我们都需要一个全新的空队列,为此我们可以创建一个具有随机名称 的队列,或者能让服务器为我们选择一个随机队列名称那就更好了。其次一旦我们断开了消费者的连接,队列将被自动删除。
创建临时队列的方法:
String queueName = channel.queueDeclare().getQueue();
1.5绑定(bingding)
什么是 bingding 呢,binding 其实是 exchange 和 queue 之间的桥梁,它告诉我们 exchange 和那个队 列进行了绑定关系。
2、发布订阅模式(Fanout exchange)
发布订阅模式也称广播模式fanout,这个模式即消息广播给所有订阅该消息的消费者。
使用fanout类型交换器,routingKey忽略。每个消费者定义生成一个队列并绑定到同一个Exchange,每个消费者都可以消费到完整的消息。
白话就是,消费者中的队列只要与模式fanout的交换机进行了绑定bingding,那么从这个交换机走的消息无视路由routingKey,将消息全部发放到队列里,然后消费者消费。
废话不多说看代码。
生产者
public class EmitLogs {
//交换机的名字
public static final String EXCHANGE_NAME="logs1";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtil.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
Scanner scanner=new Scanner(System.in);
while (scanner.hasNext()){
String message=scanner.next();
/**
* fanout模式下:路由键无效不需要指示
*/
channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("UTF-8"));
}
}
}
消费者:
public class ReceiveLogs1 {
//交换机的名字
public static final String EXCHANGE_NAME="logs1";
public static void main(String[] args) throws Exception{
Channel channel = RabbitmqUtil.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//声明队列 临时队列
/**
*生成一个临时队列、队列的名陈是随机的
*当消费者断开队列连接的时候,队列自动删除
*/
String queueName = channel.queueDeclare().getQueue();
//绑定交换机与队列
channel.queueBind(queueName,EXCHANGE_NAME,"");
System.out.println("等待接收消息,把接收的消息打印到屏幕上.......");
DeliverCallback deliverCallback=(consumerTag, message)->{
System.out.println("消费者1消费的消息:"+new String(message.getBody()));
};
CancelCallback cancelCallback= consumerTag->{
System.out.println("消费消息被中断");
};
//接收消息
channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
}
}
3、路由模式(Direct exchange)
路由模式,就是修改交换机为直接交换机(direct exchange)。此时使用 direct 模式Exchagne必须要 routingKey 完全匹配的情况下消息才会转发到对应的队列中被消费。
白话就是,交换机与队列绑定了后routingKey必须一样,才会将消息发送到对应的队列中被消费者消费。
生产者
public class DirectLogs {
//交换机的名字
public static final String EXCHANGE_NAME="direct_log";
public static void main(String[] args) throws Exception {
Channel channel = RabbitmqUtil.getChannel();
//声明交换机,指定交换机的类型是:直连交换机(direct)
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
Scanner scanner=new Scanner(System.in);
while (scanner.hasNext()){
String message=scanner.next();
/**
* direct模式下:指定哪个routingKey,消息就发送到哪个队列当中被消费者消费
*/
channel.basicPublish(EXCHANGE_NAME,"error",null,message.getBytes("UTF-8"));
}
}
}
消费者1:
至于为啥又在消费者里面声明交换机。防止先启动消费者然后,在进行交换机和队列绑定的时候报错,找不到对应的交换机。
public class ReceiveLogs1 {
//交换机的名字
public static final String EXCHANGE_NAME="direct_log";
public static void main(String[] args) throws Exception{
Channel channel = RabbitmqUtil.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//声明队列 临时队列
/**
*生成一个临时队列、队列的名陈是随机的
*当消费者断开队列连接的时候,队列自动删除
*/
// String queueName = channel.queueDeclare().getQueue();
channel.queueDeclare("console",false,false,false,null);
//绑定交换机与队列
channel.queueBind("console",EXCHANGE_NAME,"info");
channel.queueBind("console",EXCHANGE_NAME,"warning");
System.out.println("等待接收消息,把接收的消息打印到屏幕上.......");
DeliverCallback deliverCallback=(consumerTag, message)->{
System.out.println("消费者1消费的消息:"+new String(message.getBody()));
};
CancelCallback cancelCallback= consumerTag->{
System.out.println("消费消息被中断");
};
//接收消息
channel.basicConsume("console",true,deliverCallback,cancelCallback);
}
}
消费者2:
public class ReceiveLogs2 {
//交换机的名字
public static final String EXCHANGE_NAME="direct_log";
public static void main(String[] args) throws Exception{
Channel channel = RabbitmqUtil.getChannel();
//声明交换机
// channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//声明队列 临时队列
/**
*生成一个临时队列、队列的名陈是随机的
*当消费者断开队列连接的时候,队列自动删除
*/
// String queueName = channel.queueDeclare().getQueue();
channel.queueDeclare("disk",false,false,false,null);
//绑定交换机与队列
channel.queueBind("disk",EXCHANGE_NAME,"error");
System.out.println("等待接收消息,把接收的消息打印到屏幕上.......");
DeliverCallback deliverCallback=(consumerTag, message)->{
System.out.println("消费者2接收的消息:"+new String(message.getBody()));
};
CancelCallback cancelCallback= consumerTag->{
System.out.println("消费消息被中断");
};
//接收消息
channel.basicConsume("disk",true,deliverCallback,cancelCallback);
}
}
最终结果就是,在生产者里面指定哪个routingKey,消息就会发送到哪个对应相同的routingKey的队列当中,被消费者消费 。
路由模式下的交换机只能根据routingKey指定一个队列(前提此时不同队列的routingKey是不同的),如果我们有一条消息想要同时通过交换机发送给两个不同的队列,路由模式是不行的,它不能同时指定两个队列。下面的主题模式(Topic exchange)可以解决。
4、主题模式(Topic exchange)
发送到类型是 topic 交换机的消息的 routing_key 不能随意写,必须满足一定的要求,它必须是一个单词列表,以点号分隔开。这些单词可以是任意单词,比如说:"stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit".这种类型的。当然这个单词列表最多不能超过 255 个字节。
注意:
- *(星号)可以代替一个单词
- #(井号)可以替代零个或多个单词
当一个队列绑定键是#,那么这个队列将接收所有数据,就有点像 fanout 了
如果队列绑定键当中没有#和*出现,那么该队列绑定类型就是 direct 了
所以Topic包含了两种类型。
生产者:发送消息
//Topic主题模式,灵活运用routingKey,可以进行一个交换机发同时给多个队列发送消息
public class EmitLogsTopic {
//生产者发送消息
//交换机的名字
public static final String EXCHANGE_NAME="topic_logs";
public static void main(String[] args) throws Exception{
Channel channel = RabbitmqUtil.getChannel();
HashMap<String, String> messageMap = new HashMap<>();
messageMap.put("quick.orange.rabbit","被队列 Q1Q2 接收到");
messageMap.put("lazy.orange.elephant","被队列 Q1Q2 接收到");
messageMap.put("quick.orange.fox","被队列 Q1 接收到");
messageMap.put("lazy.brown.fox","被队列 Q2 接收到");
messageMap.put("lazy.pink.rabbit","虽然满足两个绑定但只被队列 Q2 接收一次");
messageMap.put("quick.brown.fox","不匹配任何绑定不会被任何队列接收到会被丢弃");
messageMap.put("quick.orange.male.rabbit","是四个单词不匹配任何绑定会被丢弃");
messageMap.put("lazy.orange.male.rabbit","是四个单词但匹配 Q2");
for (Map.Entry<String, String> stringStringEntry : messageMap.entrySet()) {
String key = stringStringEntry.getKey();
String mesaage = stringStringEntry.getValue();
channel.basicPublish(EXCHANGE_NAME,key,null,mesaage.getBytes("UTF-8"));
}
}
}
消费者1:
public class ReceiveLogsTopic1 {
//交换机的名字
public static final String EXCHANGE_NAME="topic_logs";
public static void main(String[] args) throws Exception{
Channel channel = RabbitmqUtil.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//声明队列 临时队列
/**
*生成一个临时队列、队列的名陈是随机的
*当消费者断开队列连接的时候,队列自动删除
*/
channel.queueDeclare("Q1",false,false,false,null);
/**
* Topic模式就是类似于正则,通过同类型的Key值交换机将消息发送给队列
* *代表一个单词
* #代表0个或者多个单词
*
*/
//绑定交换机与队列 ----这里就是如果routingKey是三个单词且orange在中间,交换机发送的消息可被这个队列接收
channel.queueBind("Q1",EXCHANGE_NAME,"*.orange.*");
System.out.println("等待接收消息,把接收的消息打印到屏幕上.......");
DeliverCallback deliverCallback=(consumerTag, message)->{
System.out.println("另外一个参数值:"+consumerTag+"消费者2消费的消息:"+new String(message.getBody())+"对应的Key值"+message.getEnvelope().getRoutingKey());
};
CancelCallback cancelCallback= consumerTag->{
System.out.println("消费消息被中断");
};
//接收消息
channel.basicConsume("Q1",true,deliverCallback,cancelCallback);
}
}
消费者2:
public class ReceiveLogsTopic2 {
//交换机的名字
public static final String EXCHANGE_NAME="topic_logs";
public static void main(String[] args) throws Exception{
Channel channel = RabbitmqUtil.getChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//声明队列 临时队列
/**
*生成一个临时队列、队列的名陈是随机的
*当消费者断开队列连接的时候,队列自动删除
*/
channel.queueDeclare("Q2",false,false,false,null);
/**
* Topic模式就是类似于正则,通过同类型的Key值交换机将消息发送给队列
* *代表一个单词
* #代表0个或者多个单词
*
*/
//绑定交换机与队列
//以rabbit结尾的三个单词的Key
channel.queueBind("Q2",EXCHANGE_NAME,"*.*.rabbit");
//以lazy开头的单词Key
channel.queueBind("Q2",EXCHANGE_NAME,"lazy.#");
System.out.println("等待接收消息,把接收的消息打印到屏幕上.......");
DeliverCallback deliverCallback=(consumerTag, message)->{
System.out.println("另外一个参数值:"+consumerTag+"消费者2消费的消息:"+new String(message.getBody())+"对应的Key值"+message.getEnvelope().getRoutingKey());
};
CancelCallback cancelCallback= consumerTag->{
System.out.println("消费消息被中断");
};
//接收消息
channel.basicConsume("Q2",true,deliverCallback,cancelCallback);
}
}
主题模式,可以灵活指定routingKey,将消息同时发送给多个队列。