RabbitMQ 消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列。实际上,通常生产 者甚至都不知道这些消息传递传递到了哪些队列中。
相反,生产者只能将消息发送到交换机**(exchange)**,交换机工作的内容非常简单,一方面它接收来 自生产者的消息,另一方面将它们推入队列。交换机必须确切知道如何处理收到的消息。是应该把这些消 息放到特定队列还是说把他们到许多队列中还是说应该丢弃它们。这就的由交换机的类型来决定。
交换机一共有以下几种类型
什么是 bingding 呢,binding 其实是 exchange 和 queue 之间的桥梁,它告诉我们 exchange 和那个队 列进行了绑定关系。
通过使用以下方式来创建一个临时队列,命名随机,一旦我们断开了消费者的连接,队列将被自动删除。
String queue = channel.queueDeclare().getQueue();
Fanout 这种类型非常简单。正如从名称中猜到的那样,它是将接收到的所有消息广播到它知道的所有队列中。
案例:构建一个简单的日志系统,生产者将发出日志消息,我们启动两个消费者,第一个消费者将日志持久化到磁盘中,第二个消费者会在控制台打印在屏幕上。即生产者将消息广播给所有消费者
/**
* fanout 类型 生产者
*/
public class FanoutProducer {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
/**
* 声明一个exchange
* String exchange:exchange的名称
* String type:exchange的类型
*/
channel.exchangeDeclare(ExchangeNames.LOGS,"fanout");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String message = scanner.nextLine();
channel.basicPublish(ExchangeNames.LOGS,"",null,message.getBytes(StandardCharsets.UTF_8));
System.out.println("生产者发出消息"+ message);
}
}
}
/**
* fanout 类型 消费者1
*/
public class FanoutCustomer1 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
channel.exchangeDeclare(ExchangeNames.LOGS,"fanout");
String queue = channel.queueDeclare().getQueue();
channel.queueBind(queue,ExchangeNames.LOGS,"");
System.out.println("fanoutCustomer1 等待接收消息,把消息写到磁盘中");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String receive = new String(message.getBody());
System.out.println("接收到数据成功:"+receive+",写入磁盘中");
};
channel.basicConsume(queue,true,deliverCallback,consumerTag -> {});
}
}
/**
* fanout 类型消费者2
*/
public class FanoutCustomer2 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
channel.exchangeDeclare(ExchangeNames.LOGS,"fanout");
String queue = channel.queueDeclare().getQueue();
channel.queueBind(queue,ExchangeNames.LOGS,"");
System.out.println("fanoutCustomer2 等待接收消息,把消息打印在控制台");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String receive = new String(message.getBody());
System.out.println("接收到数据成功:"+receive+",打印到控制台。。。");
};
channel.basicConsume(queue,true,deliverCallback,consumerTag -> {});
}
}
Exchange和queue的绑定关系如下图所示
fanout类型的并不能给我们带来很大的灵活性,它只能进行无意义的广播,而direct
类型可以在队列和exchange的bingding中指定一个routingKey
,消息只去到它绑定的routingKey
中去。
exchange
绑定了两个队列,绑定类型事direct
,队列console
的绑定键为warning
和info
,而disk
的绑定键为error
exchange
上,绑定为warning
和info
的会发布到队列console
上,绑定为error
的会发布到队列disk
上,其他消息会被丢弃。exchange
的绑定类型是direct
,但是它绑定的多个队列的key
都相同,在这种情况下虽然绑定类型事direct,但是它表现的就和fanout
有点类似了,就跟广播差不多。/**
* direct 类型 生产者
*/
public class DirectProducer {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
//声明exchange类型
channel.exchangeDeclare(ExchangeNames.DIRECT_LOG,"direct");
//声明binding关系
Map<String,String> bindingKeys = new HashMap<>();
bindingKeys.put("info","普通info信息");
bindingKeys.put("error","错误error信息");
bindingKeys.put("warning","警告warning信息");
bindingKeys.put("debug","调试debug信息");
//发送消息
for (Map.Entry<String, String> entry : bindingKeys.entrySet()) {
String key = entry.getKey();
String message = entry.getValue(); channel.basicPublish(ExchangeNames.DIRECT_LOG,key,null,message.getBytes(StandardCharsets.UTF_8));
System.out.println("生产者发出消息:消息类型为--"+key+",内容为:"+message);
}
}
}
/**
* direct 类型 消费者1
*/
public class DirectCustomer1 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
channel.exchangeDeclare(ExchangeNames.DIRECT_LOG, BuiltinExchangeType.DIRECT);
String queueName = "console";
channel.queueDeclare(queueName,false,false,false,null);
channel.queueBind(queueName,ExchangeNames.DIRECT_LOG,"info");
channel.queueBind(queueName,ExchangeNames.DIRECT_LOG,"warning");
System.out.println("DirectCustomer1 等待接收消息,接收info和warning 的消息类型");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String receive = new String(message.getBody());
System.out.println("接收绑定键:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive);
};
channel.basicConsume(queueName,true,deliverCallback,consumerTag -> {});
}
}
/**
* direct 类型 消费者2
*/
public class DirectCustomer2 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
channel.exchangeDeclare(ExchangeNames.DIRECT_LOG, BuiltinExchangeType.DIRECT);
String queueName = "disk";
channel.queueDeclare(queueName,false,false,false,null);
channel.queueBind(queueName,ExchangeNames.DIRECT_LOG,"error");
System.out.println("DirectCustomer2 等待接收消息,接收error 的消息类型");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String receive = new String(message.getBody());
System.out.println("接收绑定键:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive+",保存到磁盘");
};
channel.basicConsume(queueName,true,deliverCallback,consumerTag -> {});
}
}
direct
类型的binging
关系
尽管使用 direct
交换机改进了我们的系统,但是它仍然存在局限性-比方说我们想接收的日志类型有 info.base
和 info.advantage
,某个队列只想 info.base
的消息,那这个时候 direct
就办不到了。这个时候就只能使用 topic
类型。
发送到类型是 topic
交换机的消息的 routing_key
不能随意写,必须满足一定的要求,它必须是一个单词列表,以点号分隔开。这些单词可以是任意单词,比如说:stock.usd.nyse
, nyse.vmw
, quick.orange.rabbit
这种类型的。当然这个单词列表最多不能超过 255 个字节
在这些规则列表中
*
可以代替一个单词#
可以代替零个或多个单词#
,那么这个队列将接收所有数据,就有点像fanout
了#
和*
出现,那么该队列绑定类型就是direct
了/**
* topic 类型 生产者
*/
public class TopicProducer {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
//声明exchange类型
channel.exchangeDeclare(ExchangeNames.TOPIC_LOG, BuiltinExchangeType.TOPIC);
//声明binding关系
Map<String,String> bindingKeys = new HashMap<>();
bindingKeys.put("error.bus.core","被Q1和Q2收到");
bindingKeys.put("error.common.print","被Q1和Q2收到");
bindingKeys.put("common.base.core","被Q1收到");
bindingKeys.put("error.bus","被Q2收到");
bindingKeys.put("file.common.upload","被Q1收到");
//发送消息
for (Map.Entry<String, String> entry : bindingKeys.entrySet()) {
String key = entry.getKey();
String message = entry.getValue();
channel.basicPublish(ExchangeNames.TOPIC_LOG,key,null,message.getBytes(StandardCharsets.UTF_8));
System.out.println("生产者发出消息:消息类型为--"+key+",内容为:"+message);
}
}
}
/**
* topic 类型 消费者1
*/
public class TopicCustomer1 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
channel.exchangeDeclare(ExchangeNames.TOPIC_LOG, BuiltinExchangeType.TOPIC);
String queueName = "Q1";
channel.queueDeclare(queueName,false,false,false,null);
channel.queueBind(queueName,ExchangeNames.TOPIC_LOG,"*.*.core");
channel.queueBind(queueName,ExchangeNames.TOPIC_LOG,"*.common.*");
System.out.println("TopicCustomer1 等待接收消息,接收*.*.core和*.common.* 的消息类型");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String receive = new String(message.getBody());
System.out.println("接收绑定键:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive);
};
channel.basicConsume(queueName,true,deliverCallback,consumerTag -> {});
}
}
/**
* topic 类型 消费者2
*/
public class TopicCustomer2 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
channel.exchangeDeclare(ExchangeNames.TOPIC_LOG, BuiltinExchangeType.TOPIC);
String queueName = "Q2";
channel.queueDeclare(queueName,false,false,false,null);
channel.queueBind(queueName,ExchangeNames.TOPIC_LOG,"error.#");
System.out.println("TopicCustomer1 等待接收消息,接收error.#的消息类型");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String receive = new String(message.getBody());
System.out.println("接收绑定键:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive);
};
channel.basicConsume(queueName,true,deliverCallback,consumerTag -> {});
}
}
topic
中的binding
关系