视频地址
什么是交换机?
RabbitMQ 消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列。实际上,通常生产
者甚至都不知道这些消息传递传递到了哪些队列中。
情况实际上是这样的,生产者只能将消息发送到交换机(exchange),交换机工作的内容非常简单,一方面它接收来自生产者的消息,另一方面将它们推入队列。交换机必须确切知道如何处理收到的消息。是应该把这些消息放到特定队列还是说把他们到许多队列中还是说应该丢弃它们。这就的由交换机的类型来决定。
交换机的类型
临时队列
我们前面没有指定交换机的类型的时候都是使用的默认交换机,后面我们使用自定义的交换机的,我们需要将交换机和队列进行绑定(我们之前的ack_queue,hello),实际上我们还可以使用自动生成的队列,自动生成的队列在连接断开的时候会自动删除。
/*生成一个临时的队列
队列的名称是随机的
消费者断开与队列断开连接的时候就会自动删除队列
*/
String queueName = channel.queueDeclare().getQueue();
/*
将队列和交换机进行绑定
1:队列名称
2:交换机名称
3:绑定关系的名称
*/
channel.queueBind(queueName,exchangeName,"");
这样我们通过一个交换机,多个队列就可以实现将一个任务被多个消费者进行消费,只要这些消费者消费的是不同队列中的消息即可。
扇出类型
代码如下
生产者:
package com.dongmu.PublishAndSubscribe;
import com.dongmu.util.RabbitMQUtil;
import com.rabbitmq.client.Channel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Producer {
private static final String exchangeName = "logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMQUtil.getChannel();
//设置交换机的工作方式
channel.exchangeDeclare(exchangeName,"fanout");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入需要发送的消息:");
while (scanner.hasNext()){
String next = scanner.next();
/*
1:发送到哪一个交换机
2:路由的key
3:其他的参数信息
4:信息的消息体
*/
channel.basicPublish(exchangeName,"",null,next.getBytes(StandardCharsets.UTF_8));
System.out.println("发送消息'"+next+"'完成。");
}
}
}
消费者1
package com.dongmu.PublishAndSubscribe;
import com.dongmu.util.RabbitMQUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;
/**
* RabbitMQ的发布订阅模式
*/
public class Consumer1 {
private static final String exchangeName = "logs";
public static void main(String[] args) throws Exception {
//首先获取信道
Channel channel = RabbitMQUtil.getChannel();
//声明一个交换机,
/*
* 1:交换机的名称
* 2:交换机的类型(fanout表示的扇出类型,就是发布订阅模式)
*/
channel.exchangeDeclare(exchangeName,"fanout");
/*生成一个临时的队列
队列的名称是随机的
消费者断开与队列断开连接的时候就会自动删除队列
*/
String queueName = channel.queueDeclare().getQueue();
/*
将队列和交换机进行绑定
1:队列名称
2:交换机名称
3:绑定关系的名称
*/
channel.queueBind(queueName,exchangeName,"");
DeliverCallback deliverCallback = (name, delivery)->{
System.out.println("消费者接收到了消息:"+name+",消息的内容是:"+new String(delivery.getBody(), StandardCharsets.UTF_8));
};
channel.basicConsume(queueName,true,deliverCallback,consumerTag->{});
}
}
消费者2和消费者1代码相同
直接模式
上面的扇出模式中,我们使用的交换机和队列进行绑定的时候有一个绑定的routingkey,上面我们写的是一个空串。实际上我们可以进行绑定不同的routingkey,同一个队列可以绑定不同的routingkey。
我们只需要把消息发送到某一个交换机指定它的路由即可,对应的包含这个路由的队列会收到这个消息。
代码
package com.dongmu.direct;
import com.dongmu.util.RabbitMQUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Producer {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMQUtil.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String message = "北海冬木";
Scanner scanner = new Scanner(System.in);
channel.basicPublish(EXCHANGE_NAME,"error",null,message.getBytes(StandardCharsets.UTF_8));
while (scanner.hasNext()){
message = scanner.next();
channel.basicPublish(EXCHANGE_NAME,"info",null,message.getBytes(StandardCharsets.UTF_8));
System.out.println("生产者发出消息"+message);
}
}
}
package com.dongmu.direct;
import com.dongmu.util.RabbitMQUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;
public class Consumer1 {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMQUtil.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String queuename = "consule";
String routingkey1 = "info";
String routingkey2 = "warning";
channel.queueDeclare(queuename,false,false,false,null);
channel.queueBind(queuename,EXCHANGE_NAME,routingkey1);
channel.queueBind(queuename,EXCHANGE_NAME,routingkey2);
DeliverCallback callback_ack = (queueName,message)->{
System.out.println("消费了队列"+queueName+"中的消息,消息的内容是:"+new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback callback_nack = (queueName)->{
System.out.println("消费队列"+queueName+"中的消息失败。");
};
channel.basicConsume(queuename,true,callback_ack,callback_nack);
}
}
package com.dongmu.direct;
import com.dongmu.util.RabbitMQUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.nio.charset.StandardCharsets;
public class Consumer2 {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMQUtil.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String queuename = "disk";
String routingkey = "error";
channel.queueDeclare(queuename,false,false,false,null);
channel.queueBind(queuename,EXCHANGE_NAME,routingkey);
DeliverCallback callback_ack = (queueName,message)->{
System.out.println("消费了队列"+queueName+"中的消息,消息的内容是:"+new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback callback_nack = (queueName)->{
System.out.println("消费队列"+queueName+"中的消息失败。");
};
channel.basicConsume(queuename,true,callback_ack,callback_nack);
}
}
当我们先把两个消费者启动,然后启动生产者,由于生产者会首先发送一个“冬木”到error的routingKey,所以只有consumer2里面的队列能够收到。这个时候只有consumer2的控制台能够看到一下内容
如果我们在producer中发送下面的内容
由于producer把它发送到了绑定routingkey是info的队列的当中,因此这个时候只有consumer1能够消费到这个消息,并且只能消费到一条消息。
如果我们在生产者的代码写成下面这个样子
channel.basicPublish(EXCHANGE_NAME,"info",null,message.getBytes(StandardCharsets.UTF_8));
channel.basicPublish(EXCHANGE_NAME,"warning",null,message.getBytes(StandardCharsets.UTF_8));
这个时候由于consumer1里面的队列绑定了两个routingkey,也就是会路由到这个队列中两次消息,所以就会消费两次。
另外需要注意的是我们到现在为止,编写的代码都是在消费者中将队列和交换机进行绑定的,其实这样子不太好,我们必须先启动消费者然后再启动生产者。如果我们先启动的是生产者然后再启动消费者,消费者就会无法消费到启动之前的消息,因为启动时候的生产者没有将交换机和队列进行绑定,然后交换机又是不能存储消息的,这就导致了消息的丢失,所以如果在实战中是使用的时候我们还是尽量在生产者中将交换机和队列进行绑定。
另外可能你会发现你自己做实验的结果不是这样的,这是由于你可能没有在实验之前将已经存在的交换机和队列进行删除,导致生产者启动的时候,交换机和队列就已经绑定在了一起。