RabbitMQ(二):交换机
前言
- 学习自bili尚硅谷-RabbitMQ
发布确认
之前的消息应答,队列持久化是为了保证 -> 消息从rabbitmq队列到消费者的过程中不会丢失;消息持久化则是为了保证 -> 消息从生产者到队列的过程中不会丢失,但也不能完全保证,因此有了发布确认策略,即当消息到达队列后,会通知生产者以确认
单个发布确认:发布一个确认一个,效率低
批量发布确认:批量发布,批量确认,若消息丢失,则不知道是那条消息丢失了
异步发布确认业务逻辑:
- 生产者向队列发送消息,生产者只管发送消息,发送消息前会有一个监听器,监听发布确认的消息,和未发布确认的消息、
- 发布消息前会将所有消息记录到一个并发链路队列,当监听器监听到消息发布确认后,就将并发链路队列中的消息删除,剩余则时未发布确认的消息
点击查看详细代码
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();
// 新建一个并发链路队列
ConcurrentSkipListMap<Long, String> outstandingConfirms = new
ConcurrentSkipListMap<>();
// 确认收到消息的一个回调
ConfirmCallback ackCallback = (sequenceNumber, multiple) -> {
/**
* 返回批量确认消息或单个确认消息时,清除
*/
if (multiple) {
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);
};
// 添加一个异步确认的监听器,参数是两个回调函数,队列收到消息的回调以及未收到消息的回调
channel.addConfirmListener(ackCallback, nackCallback);
long begin = System.currentTimeMillis(); // 获取发送消息的开始时间
for (int i = 0; i < MESSAGE_COUNT; i++){ // 发送消息
String message = "消息" + i;
// 发送消息前,将所有发送的消息记录到一个并发链路队列
outstandingConfirms.put(channel.getNextPublishSeqNo(), message);
channel.basicPublish("", queueName, null, message.getBytes()); // 发送消息
}
long end = System.currentTimeMillis();
System.out.println("发布" + MESSAGE_COUNT + "个异步确认消息,耗时" + (end - begin) +"ms");
}
}
交换机
生产者发消息给交换机,交换机通过routingKey绑定不同的队列,队列再将消息发送给不同的消费者
交换机类型:设置为空,则表示使用默认交换机;直接(direct);主题(topic);标题(headers);扇出(fanout)
扇出
即像广播一样,同时向多个消费者发送同样的消息
- 编写两个类作为消费者,队列名称是随机生成的
点击查看详细代码
public class ReceiveLogs01 {
private static final String EXCHANGE_NAME = "logs"; // 交换机名称
public static void main(String[] argv) throws Exception {
Channel channel = RabbitUtils.getChannel(); // 获取信道
channel.exchangeDeclare(EXCHANGE_NAME, "fanout"); // 声明一个交换机
// 生成一个临时的队列 队列的名称是随机的,当消费者断开和该队列的连接时,队列自动删除
String queueName = channel.queueDeclare().getQueue();
// 队列绑定到交换机,参数:队列、交换机、routingKey可为null
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println("等待接收消息,把接收到的消息打印在屏幕........... ");
DeliverCallback deliverCallback = (consumerTag, delivery) -> { // 接收成功的方法回调
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("控制台打印接收到的消息"+message);
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { }); // 接收消息
}
}
- 编写生产者发送消息:
点击查看详细代码
public class EmitLog {
private static final String EXCHANGE_NAME = "logs"; // 交换机名称
public static void main(String[] argv) throws Exception {
try (Channel channel = RabbitUtils.getChannel()) {
// 声明一个交换机:名称、类型
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
Scanner sc = new Scanner(System.in);
System.out.println("请输入信息");
while (sc.hasNext()) {
String message = sc.nextLine();
// 交换机、路由key、消息持久化、消息体
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8")); // 发送消息
System.out.println("生产者发出消息" + message);
}
}
}
}
- 测试:生产者发送消息,两个消费者都能收到消息
- 生产者到交换机、交换机到两个消费者,绑定的路由key是相同的,所以生产者发送的消息,两个消费者都能收到
直接
- 新建一个类作为消费者:交换机 -> 路由key(error) -> 队列disk -> 消费者1
点击查看详细代码
public class ReceiveLogsDirect01 {
private static final String EXCHANGE_NAME = "direct_logs"; // 交换机名称
public static void main(String[] argv) throws Exception {
Channel channel = RabbitUtils.getChannel(); // 获取信道
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); // 声明交换机
String queueName = "disk"; // 声明队列
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, EXCHANGE_NAME, "error"); // 绑定路由key为error
System.out.println("等待接收消息........... ");
DeliverCallback deliverCallback = (consumerTag, delivery) -> { // 接收成功的回调
String message = new String(delivery.getBody(), "UTF-8");
message="接收绑定键:"+delivery.getEnvelope().getRoutingKey()+",消息:"+message;
File file = new File("C:\\work\\rabbitmq_info.txt");
FileUtils.writeStringToFile(file,message,"UTF-8");
System.out.println("错误日志已经接收");
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
}
再新建一个类作为消费者,交换机和队列间绑定了两条路由key,即交换机可通过任意一条路由将消息传递给队列:交换机 -> 路由key(info、warning) -> 队列console -> 消费者2
新建类作为生产者,使用不同的路由key,则进入不同的队列:生产者 -> 路由key -> 队列 -> 消费者
点击查看详细代码
public class EmitLogDirect {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] argv) throws Exception {
try (Channel channel = RabbitUtils.getChannel()) {
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); // 声明一个交换机,并指定类型
//创建多个 bindingKey
Map<String, String> bindingKeyMap = new HashMap<>();
bindingKeyMap.put("info","普通 info 信息");
bindingKeyMap.put("warning","警告 warning 信息");
bindingKeyMap.put("error","错误 error 信息");
bindingKeyMap.put("debug","调试 debug 信息"); // 该条路由key不存在,模拟消息丢失
for (Map.Entry<String, String> bindingKeyEntry: bindingKeyMap.entrySet()){
String bindingKey = bindingKeyEntry.getKey();
String message = bindingKeyEntry.getValue();
// 发送消息:交换机、路由key、消息是否持久化、消息体
channel.basicPublish(EXCHANGE_NAME,bindingKey, null,message.getBytes("UTF-8"));
System.out.println("生产者发出消息:" + message);
}
}
}
主题
- 扇出、直接模式的交换机都存在缺陷,扇出无法像指定的单个队列发送消息,直接模式无法同时向所有队列发送消息
主题模式则可以向指定的单个队列发送消息,也可以向所有队列发送消息
- 新建一个类作为消费者:交换机 -> 路由key -> 队列Q1 -> 消费者1
点击查看详细代码
public class ReceiveLogsTopic01 {
private static final String EXCHANGE_NAME = "topic_logs"; // 交换机名称
public static void main(String[] argv) throws Exception {
Channel channel = RabbitUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic"); // 声明一个交换机,指定类型为:主题
String queueName="Q1"; // 队列Q1
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, EXCHANGE_NAME, "*.orange.*"); // 绑定的留有key:*.orange.*
System.out.println("等待接收消息........... ");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" 接 收 队 列 :"+queueName+" 绑 定 键:"+delivery.getEnvelope().getRoutingKey()+",消息:"+message);
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
}
- 再新建一个类作为消费者:交换机 -> 路由key -> 队列Q1 -> 消费者1
点击查看详细代码
public class ReceiveLogsTopic02 {
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] argv) throws Exception {
Channel channel = RabbitUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String queueName="Q2";
channel.queueDeclare(queueName, false, false, false, null);
// 交换机和队列Q2之间绑定了两条路由key
channel.queueBind(queueName, EXCHANGE_NAME, "*.*.rabbit");
channel.queueBind(queueName, EXCHANGE_NAME, "lazy.#");
System.out.println("等待接收消息........... ");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" 接 收 队 列 :"+queueName+" 绑 定 键:"+delivery.getEnvelope().getRoutingKey()+",消息:"+message);
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
}
- 新建类作为生产者,使用不同的路由key,则进入不同的队列:生产者 -> 路由key -> 队列 -> 消费者
点击查看详细代码
public class EmitLogTopic {
private static final String EXCHANGE_NAME = "topic_logs"; // 交换机名称
public static void main(String[] argv) throws Exception {
try (Channel channel = RabbitUtils.getChannel()) {
channel.exchangeDeclare(EXCHANGE_NAME, "topic"); // 声明交换机,指定类型
Map<String, String> bindingKeyMap = new HashMap<>();
bindingKeyMap.put("quick.orange.rabbit","被队列 Q1Q2 接收到");
bindingKeyMap.put("lazy.orange.elephant","被队列 Q1Q2 接收到");
bindingKeyMap.put("quick.orange.fox","被队列 Q1 接收到");
bindingKeyMap.put("lazy.brown.fox","被队列 Q2 接收到");
bindingKeyMap.put("lazy.pink.rabbit","虽然满足两个绑定但只被队列 Q2 接收一次");
bindingKeyMap.put("quick.brown.fox","不匹配任何绑定不会被任何队列接收到会被丢弃");
bindingKeyMap.put("quick.orange.male.rabbit","是四个单词不匹配任何绑定会被丢弃");
bindingKeyMap.put("lazy.orange.male.rabbit","是四个单词但匹配 Q2");
for (Map.Entry<String, String> bindingKeyEntry: bindingKeyMap.entrySet()){
String bindingKey = bindingKeyEntry.getKey();
String message = bindingKeyEntry.getValue();
channel.basicPublish(EXCHANGE_NAME,bindingKey, null,
message.getBytes("UTF-8"));
System.out.println("生产者发出消息" + message);
}
}
}
}
星号表示一个单词,井号表示零个或多个单词;因此使用类似模糊查询的方式,生产者便可匹配需要的消费者
RabbitMQ(二):交换机的更多相关文章
- 中间件系列三 RabbitMQ之交换机的四种类型和属性
概述本文介绍RabbitMQ中交换机类型和属性,主要内容如下: 交换机的作用交换机的类型:Direct exchange(直连交换机).Fanout exchange(扇型交换机).Topic exc ...
- RabbitMQ(二):理解消息通信RabbitMQ
原文:RabbitMQ(二):理解消息通信RabbitMQ 一.消费者.生产者和信道 生产者(producer):生产者创建消息,然后发布(发送)到代理服务器(RabbitMQ),可以说发送消息的程序 ...
- 快速掌握RabbitMQ(二)——四种Exchange介绍及代码演示
在上一篇的最后,编写了一个C#驱动RabbitMQ的简单栗子,了解了C#驱动RabbitMQ的基本用法.本章介绍RabbitMQ的四种Exchange及各种Exchange的使用场景. 1 direc ...
- RabbitMQ的交换机类型(三)
RabbitMQ的交换机类型共有四种,是根据其路由过程的不同而划分成的 分别是Direct Exchange(直连交换机), Fanout Exchange(扇型交换机), Topic Excha ...
- RabbitMQ之交换机及spring整合
交换机 交换机属性: Name:交换机名称 Type:交换机类型 direct.topic.fanout.headers Durability:是否需要持久化,true为持久化 Auto Delete ...
- RabbitMQ各种交换机类型Exchange Types介绍
最新版本的RabbitMQ有四种交换机类型,分别是Direct exchange.Fanout exchange.Topic exchange.Headers exchange. 一.Direct E ...
- RabbitMQ中交换机的消息分发机制
RabbitMQ是一个消息代理,它接受和转发消息,是一个由 Erlang 语言开发的遵循AMQP协议的开源实现.在RabbitMQ中生产者不会将消息直接发送到队列当中,而是将消息直接发送到交换机(ex ...
- RabbitMQ(二):mandatory标志的作用
本文转自:http://m.blog.csdn.net/article/details?id=54311277 在生产者通过channel的basicPublish方法发布消息时,通常有几个参数需要设 ...
- RabbitMQ (二) 简单队列
参考:https://blog.csdn.net/vbirdbest/article/details/78583480 简单队列的模型: P : 生产者,即 Producer C : 消费者,即 Co ...
- RabbitMQ二----' helllo world '
RabbitMQ实现了AMQP定义的消息队列.它实现的功能”非常简单“:从Producer接收数据然后传递到Consumer.它能保证多并发,数据安全传递,可扩展. 我们将会设计两个程序,一个发送He ...
随机推荐
- 【保姆级】利用Github搭建自己的个人博客,看完就会
大家好,我是辰哥~ 作为一名喜欢技术的爱好者,平时喜欢把自己学习技术的心得或者一些踩坑.易错的过程记录下来,首选的是技术平台(博客),今天辰哥来教大家如何利用Github来搭建一个自己的个人博客平台. ...
- 第十六篇 -- QListWidget与QToolButton(功能)
效果图: 添加的部分,就是对几个action绑定了槽函数,完成相应的功能. listWidget操作的都是item,添加一个item,删除一个item,插入一个item等等.那么只需要知道item的几 ...
- 什么是SpringBoot,微服务
Spring是如何简化Java开发的 为了降低Java开发的复杂性,Spring采用了以下4种关键策略: 1.基于pojo的轻量级和最小侵入性编程: 2.通过IOC,依赖注入(DI)和面向接口实现 ...
- jvm源码解读--02 Array<u1>* tags = MetadataFactory::new_writeable_array<u1>(loader_data, length, 0, CHECK_NULL); 函数引入的jvm内存分配解析
current路径: #0 Array<unsigned char>::operator new (size=8, loader_data=0x7fd4c802e868, length=8 ...
- vscode安装ESlint配置
先安装插件ESLint,后面在设置setting.json中配置加入代码: { "files.autoSave": "afterDelay", "ed ...
- 数据结构——图的深度优先遍历(邻接矩阵表示+java版本)
1.深度优先遍历(DFS) 图的深度优先遍历本质上是一棵树的前序遍历(即先遍历自身,然后遍历其左子树,再遍历右子树),总之图的深度优先遍历是一个递归的过程. 如下图所示,左图是一个图,右图是图的深度 ...
- python代码下载m3u8视频
代码如下: # -*- coding: utf-8 -*- import requests import re import os import base64 from Crypto.Cipher i ...
- Linux命令(七)之上传/共享/挂载文件至Linux系统中
.personSunflowerP { background: rgba(51, 153, 0, 0.66); border-bottom: 1px solid rgba(0, 102, 0, 1); ...
- Ubuntu Server安装telnet服务时"Unable to locate package telnetd"解决方法
装好Ubuntu Server 12.04后,用apt-get安装telnetd报"E: Unable to locate package telnetd",解决方法如下: 虚拟机 ...
- 授予mysql的其他用户数据库的使用权限
场景:不同的开发人员有不同的数据库的权限:也可适用于外包公司不同的开发权限. root用户登录数据库,命令行执行下面语句即可. grant select,delete,update,create,dr ...