前言
就跟标题说的一样, 如果您项目中同一个事务出现大量远程调用, 请考虑使用消息队列, 但...
出现这种问题前, 你可以先和你团队里的老同事干一架, 微服务怎么分的?
你怎么把我品如的衣服给她穿了?
本章内容
- 什么是消息队列?
- 为什么使用消息队列?
- 有什么优点?
- 有什么缺点?
- 为什么选择
rabbitmq? rabbitmq几个模式简单入门- 待续
什么是消息队列?
本质是队列, 存放消息的队列
为什么使用消息队列?
消息队列使用在分布式系统之间, 用于发布和订阅事件使用
主要的目的有:
- 解耦
- 消峰
- 异步加速
解耦
为了了解解耦, 我们可以简单写下rabbitmq的入门代码
从图中我们可以发现, 生产者只针对交换机, 所以我们可以写下如下代码:
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = RabbitmqUtil.INSTANCE.getInstance();
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();) {
channel.exchangeDeclare(exchange_name, BuiltinExchangeType.DIRECT);
for (int i = 0; i < 10; i++) {
channel.basicPublish(exchange_name, routing_key, null,
("hello world " + i).getBytes(StandardCharsets.UTF_8));
}
}
}
上面是一段简单生产者省略代码, 会发现生产者一般只对 exchange 发送消息
问: 上面源码中的
routing_key是怎么回事?答: 上文代码的
routing_key = ""所以不用太过于关注.routing_key类似于filter过滤器, 生产者发送消息给交换机时, 可以添加routing_key路由键, 而消费者在定义exchange和queue时, 需要绑定rouing_key, 这里面的两个routing_key用于比较使用
而消费者我们只需要关注queue便可, 所以我们可以写下如下代码:
ConnectionFactory connectionFactory = RabbitmqUtil.INSTANCE.getInstance();
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange_name, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_NAME, false,
false, false, null);
channel.queueBind(QUEUE_NAME, exchange_name, routing_key);
for (int i = 0; i < 10; i++) {
channel.basicConsume(QUEUE_NAME, false, (consumerTag, message) -> {
System.err.println(new String(message.getBody(), Charset.defaultCharset()));
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}, System.err::println);
}
代码分析
这段代码核心部分分为两个:
- 定义: 定义交换机, 队列并使用路由键绑定他们
- 发送: 往
queue发送消息
可以发现消费者除了定义工作, 只需要关注队列便可
从上面这两段代码你发现了什么?
生产者只需要往交换机发送消息, 不需要关注谁收到了我的消息, 这是核心
换句话说, 生产者不关注哪个消费者订阅了我的消息, 而消费者可以根据需求选择合适自己业务的消息订阅
还不懂的话, 可以举个例子:
学生选择了PHP课程, 并发送消息给交换机
老师存在这么一个队列
老师绑定了该交换机
这样, 学生选课的信息, 老师A都会知道
问: 现在家长端APP也需要了解学生选课的信息怎么办?
简单也去绑定这个交换机便可
下次还有别的终端需要订阅该信息, 同样绑定就行
这样学生端一点代码都不需要修改, 只需要发布消息便可, 而消费端可以无限扩展
问: 你上面的代码重复定义了exchange!!!
答: 这个问题比较复杂, 需要详细讲讲
由谁定义exchange和queue?
到底是消费者定义各个组件还是生产者定义? exchange还好说, 那么queue怎么办? 是给P还是C呢?
这个问题需要分开讨论:
- 给
C端定义exchange和queue,P端只需要定义exchange就行, 这样的好处就是P端代码非常简单, 代码容易扩展, 但是exchange不保存消息, 如果P先运行, 那么消息将会被丢弃, 如果是不重要的消息还好, 那是重要消息呢? 这是你需要考虑的地方 C端定义exchange和queue,P端也定义exchange和queue. 这样如果P先启动消息也会在queue中, 不会轻易丢失, 但是带来的问题是P端定义的queue不管routing key是否匹配都会往queue里发送消息, 这是一个坑- P和C端都不定义任何
exchange和queue, 由用户去创建, PC两端都用exchange和queue的字符串(我觉得这种方式才是最好的, 但也是最麻烦的)
下面就演示演示第二种情况
P:
public static final String exchange_name = "X";
public static final String routing_key = "error";
public static final String QUEUE_NAME = "abab";
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = RabbitmqUtil.INSTANCE.getInstance();
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();) {
channel.exchangeDeclare(exchange_name, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_NAME, false,
false, false, null);
channel.queueBind(QUEUE_NAME, exchange_name, routing_key);
for (int i = 0; i < 10; i++) {
channel.basicPublish(exchange_name, routing_key, MessageProperties.PERSISTENT_TEXT_PLAIN,
("hello world " + i).getBytes(StandardCharsets.UTF_8));
}
}
}
C1:
public static final String exchange_name = "X";
public static final String routing_key = "error";
public static final String QUEUE_NAME = "amqp.gen.queue.1";
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = RabbitmqUtil.INSTANCE.getInstance();
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange_name, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_NAME, false,
false, false, null);
channel.queueBind(QUEUE_NAME, exchange_name, routing_key);
for (int i = 0; i < 10; i++) {
channel.basicConsume(QUEUE_NAME, false, (consumerTag, message) -> {
System.err.println(new String(message.getBody(), Charset.defaultCharset()));
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}, System.err::println);
}
}
C2:
public static final String exchange_name = "X";
public static final String routing_key = "info";
public static final String QUEUE_NAME = "amqp.gen.queue.2";
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = RabbitmqUtil.INSTANCE.getInstance();
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange_name, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_NAME, false,
false, false, null);
channel.queueBind(QUEUE_NAME, exchange_name, routing_key);
for (int i = 0; i < 10; i++) {
channel.basicConsume(QUEUE_NAME, false, (consumerTag, message) -> {
System.err.println(new String(message.getBody(), Charset.defaultCharset()));
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}, System.err::println);
}
}
只因为生产者做了如下定义:
channel.exchangeDeclare(exchange_name, BuiltinExchangeType.DIRECT);
channel.queueDeclare("abab", false, false, false, null);
channel.queueBind("abab", "X", "error");
channel.basicPublish("X", "error", null, ("hello world").getBytes());
对于 abab 队列来说, 这里的"error" routing_key将被忽略, 但是对于另外两个queue来说还是有效的
所以上图的情况将会是:
如果生产者发送时是这样: routing_key = "info"
还有一个问题:
最终如何选择看你怎么做, 而我推荐使用第三种方案, 消费者和生产者都不创建任何
exchange和queue, 全部交给rabbitmq用户手动去创建
异步加速
下图, AService的功能需要调用到BService和CService的函数, 那么需要 450ms的时间
然后就可以改造成发送消息给交换机的方式实现,这样时间只需要 150ms , 至于消费者的时间对于AService服务来说, 是分隔的
这样速度上去了, 即便算上完成服务的时间也只有200ms
这样速度是上去了, 但是有前提~
BService和CService并不在同一个事务中, 换句话说对于AService的业务来说, BService和CService中的业务并不是那么的重要, 即便不完成也不会影响AService的业务逻辑
对了这张图有错误哦, 应该是这样:
消峰
问: 等等, 那我们为什么不使用其他限流方案呢? 何必为了限流而使用mq, 奥卡姆剃刀原则懂不懂啊?
答: 嗯, 互联网上很少使用mq落地做微服务限流, 一般使用
redis或者sentinel还有hystrix.
hystrix应该是最好的, 可以做限流, 熔断, 降级还有超时处理等, 但是它没了sentinel是最复杂的方案, 不推荐马上使用redis是比较糙的解决方案, 可以配合redisson, 这种方式的限流主要使用分布式锁, 让某些情况只能单线线程执行, 但是这种方式真的能限流吗? 一个线程拿到了分布式锁, 其他线程拿不到那他就不执行了吗? 还不是重复尝试获取锁? 其他限流方案不会一直进行网络IO访问, 但是redis实现的方式会
使用上面redis方法还不如使用rabbitmq方案, 只要在消费端设置basicQos(0, 1, false).
prefetchSize:0,单条消息大小限制,0代表不限制prefetchCount:一次性消费的消息数量。会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,即一旦有N个消息还没有ack,则该consumer将block掉,直到有消息ack。global:true、false是否将上面设置应用于channel,简单点说,就是上面限制是channel级别的还是consumer级别。当我们设置为false的时候生效,设置为true的时候没有了限流功能,因为channel级别尚未实现。- 注意:
prefetchSize和global这两项,rabbitmq没有实现,暂且不研究。特别注意一点,prefetchCount在no_ask=false的情况下才生效,即在自动应答的情况下这两个值是不生效的。
那么加入mq的缺点是什么?
根据奥卡姆剃刀原则, 如无必要, 勿增实体, 你需要考量自己的项目是否需要加入mq
因为mq的加入, 会让整个系统变得更加复杂, 你需要去关注mq是否会出现问题, 是否存在bug, 消息是否丢失等
你还得考虑, 一致性问题, 因为消息是异步的, 它只能保证最终一致性, 但也有一些问题可能导致最终一致都做不到
需要人工介入, 所以增加mq你需要跟开发团队的人员商量
为什么选择rabbitmq?
答: 并不是选择rabbitmq, 而是所有的mq都得学!!!
但是企业中一般使用rabbitmq, 因为它文档健全, 也简单, 是几个mq中最合适最先学习的中间件
本人觉得, 如果你是
java后端开发人员, 公司业务较大, 最好选择rocketmq, 并发量比rabbitmq大一个数量级, 但是阿里的文档写的是真的烂而
kafka的话, 最合适做日志, 因为kafka会丢数据, 日志丢一两行应该没事哈哈~
怎么安装rabbitmq?
官方文档打开, 第一时间就能看到使用docker的方案, 可以考虑, 但本文使用的是 rabbitmq window版本, 主要是为了图个方便
可以去Release RabbitMQ 3.11.13 · rabbitmq/rabbitmq-server (github.com) 下载
但是安装window版本的rabbitmq需要安装网页功能, 否则无法打开web功能
rabbitmq-plugins.bat enable rabbitmq_management
这样才能打开: http://localhost:15672
那么rabbitmq怎么使用呢?
建议: 在初期编写消息队列的代码比较困难, 可以考虑一边看着图片一边写代码
hello world
生产者 P, 消费者 C, 红色是队列
意思是说, 生产者直接向队列发送消息, 消费者直接从队列中读取消息
public class Sender {
public static final String QUEUE_NAME = "HELLO_WORLD_QUEUE";
public static final String host = "127.0.0.1";
public static final int port = 5672;
public static final String username = "zhazha";
public static final String password = "123456";
public static final ConnectionFactory connectionFactory = new ConnectionFactory();
static {
// rabbitmq ip
connectionFactory.setHost(host);
// rabbitmq 端口
connectionFactory.setPort(port);
// rabbitmq 虚拟系统, 也就是逻辑系统
connectionFactory.setVirtualHost("/");
// rabbitmq 用户名
connectionFactory.setUsername(username);
// rabbitmq 密码
connectionFactory.setPassword(password);
}
public static void main(String[] args) throws Exception {
// 拿到rabbitmq连接
// 创建 exchange 交换器
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();) {
// 定义 queue
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i = 0; i < 10; i++) {
// 发送消息
channel.basicPublish("", QUEUE_NAME, null,
("hello world" + i).getBytes(StandardCharsets.UTF_8));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
P端往queue发送消息
public static final String QUEUE_NAME = "HELLO_WORLD_QUEUE";
private static final ConnectionFactory connectionFactory = new ConnectionFactory();
public static final String host = "127.0.0.1";
public static final int port = 5672;
public static final String username = "zhazha";
public static final String password = "123456";
static {
connectionFactory.setHost(host);
connectionFactory.setPort(port);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
}
public static void main(String[] args) throws Exception {
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicConsume(QUEUE_NAME,(consumerTag, message) -> {
System.err.println(new String(message.getBody(), Charset.defaultCharset()));
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
}, consumerTag -> { });
}
C端往queue读取消息
问: rabbitmq不是有自动
ack参数吗? 为什么还需要调用channel.basicAck? 答: 如果使用auto ack,rabbitmq可能在我们的消息没有消费完毕的时候直接响应了ack消息, 告知broker可以再发送消息给我消费了,ack对应的消息并没有处理完毕, 如果消费者在崩溃前ack告知了broker, 那么在崩溃后那个消息将丢失
work queue模式
这个模式的特点是生产者只有一个。消费者有两个。而消费者会从一条队列中读取多个消息。
那么这也就是要关注C1和C2的竞争了
public static final String WORK_2_QUEUE = "work2_queue";
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = RabbitmqUtil.INSTANCE.getInstance();
try(Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();) {
for (int i = 0; i < 10; i++) {
channel.basicPublish("", WORK_2_QUEUE,
null, ("hello world" + i).getBytes(StandardCharsets.UTF_8));
}
}
}
生产者的代码总是那么好写。
public static final String WORK_2_QUEUE = "work2_queue";
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = RabbitmqUtil.INSTANCE.getInstance();
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
// 接受者每次只能收到一个消息之后才能ack
channel.basicQos(1);
channel.queueDeclare(WORK_2_QUEUE, true, false,
true, null);
channel.basicConsume(WORK_2_QUEUE, false, (consumerTag, message) -> {
System.err.println(new String(message.getBody(), Charset.defaultCharset()));
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}, System.err::println);
}
消费者也是从队列中读取消息,但是有个前提需要事先设置好qos
这里的
qos意味着消费者一次读取一个消息就立即ack, 在消费端如果不配置Qos, 那么消息的读取和ack不是一对一的, 可能是多个消息一起使用同一个ack
public static final String WORK_2_QUEUE = "work2_queue";
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = RabbitmqUtil.INSTANCE.getInstance();
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
// 接受者每次只能收到一个消息之后才能ack
channel.basicQos(1);
channel.queueDeclare(WORK_2_QUEUE, true, false,
true, null);
channel.basicConsume(WORK_2_QUEUE, false,(consumerTag, message) -> {
System.err.println(new String(message.getBody(), Charset.defaultCharset()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}, System.err::println);
}
消费者二和消费者一差不多,但是消费者二多了等待的时间。这里的消费者二是在模拟消费者的性能比消费者一慢。
上面这两个模式都是生产者和消费者直接跟队列交流的模式。所以一个消息只能发送给一个消费者。不能像广播模式一样的一个消息同时发送给多少个消费者。
为什么要basicQos(1)?
换句话说: rabbitmq不支持消息窃取功能, 所谓消息窃取就是从另一个消费者中偷取消息进行处理
rabbitmq默认支持轮询分发, 也就是说, 不管多少个消息, 都会根据消费者数量进行平均分, 比如 100 个消息, 有 10 个消费者, 那么rabbitmq就会将 100 / 10 = 10 个消息, 平均分给这 10 个消费者
如果其中一个消费者是一台超级计算机, 那么它也就消费了10个消息, 还有一台是 8086微型计算机, 也处理 10个消息
对于超级计算机来说可以一下子处理完毕所有消息, 但是如果使用 8086 那么需要花费 10小时的时间才能处理完毕 10条消息
这可相当的浪费资源
应该是能者多劳, 超级计算机应该处理更多的消息, 而非只处理10条消息, 然后一直空等着
8086 运行效率较慢, 所以因该给他安排较少的消息, 比如 1条消息给他慢慢处理
basicQos(1)存在的问题
一次消息, 一次ack, 效率慢
前面我们知道能者多劳才不会浪费资源, 但是 basicQos(1) 是存在问题的
加入这段代码意味着一个消息一次ack, 这本身是一个非常耗费性能的问题
因该计算每台计算机的效率, 提前按照一定的比例分配 Qos 的数值
比如超级计算机效率高 Qos 可以是 10000 , 而 8086 效率低可以是 1
发布订阅模式
这个模式下, 生产者就可以向交换机发送消息了
public static final String EXCHANGE_NAME = "pub_sub_exchange_name";
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = RabbitmqUtil.INSTANCE.getInstance();
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
// 生产者只需要将生产出来的消息发送给 fanout exchange 就行了, 生产者甚至不清楚这个消息将发送给哪个queue
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT, true);
for (int i = 0; i < 10; i++) {
channel.basicPublish(EXCHANGE_NAME, "", null,
("hello world" + i).getBytes(StandardCharsets.UTF_8));
}
}
}
生产者直接向exchange发送消息可能会丢失消息, 需要注意, 因为消息只能存储在queue中, exchange并没有存储消息的能力
生产者只需要将生产出来的消息发送给 fanout exchange 就行了, 生产者甚至不清楚这个消息将发送给哪个queue
public static void main(String[] args) throws Exception {
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
// 这里创建一个临时的queue, 不持久, 不独占, 使用完自动删除的queue
String queueName = channel.queueDeclare().getQueue();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT, true);
// routing key 是忽略的
channel.queueBind(queueName, EXCHANGE_NAME, "");
channel.basicConsume(queueName, false,
(consumerTag, message) -> {
System.err.println(new String(message.getBody(), Charset.defaultCharset()));
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
},
consumerTag -> {
System.err.println("consumerTag: " + consumerTag);
});
}
上面这段代码, 创建了一个临时queue然后将其和交换机绑定, 这样生产者发送给交换机的消息都会被exchange发送给临时queue
public static void main(String[] args) throws Exception {
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
// 这里创建一个临时的queue, 不持久, 不独占, 使用完自动删除的queue
String queueName = channel.queueDeclare().getQueue();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT, true);
// routing key 是忽略的
channel.queueBind(queueName, EXCHANGE_NAME, "");
channel.basicConsume(queueName, false,
(consumerTag, message) -> {
System.err.println(new String(message.getBody(), Charset.defaultCharset()));
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
},
consumerTag -> {
System.err.println("consumerTag: " + consumerTag);
});
}
这个模式的交换机类型是FANOUT, 表示一个消息将会被交换机同时广播给多个队列
注意上面使用的临时queue, 并不意味着只能使用临时queue
public static final String EXCHANGE = "pub_sub_exchange";
public static void main(String[] args) {
ConnectionFactory connectionFactory = RabbitmqUtil.INSTANCE.getInstance();
Scanner scanner = new Scanner(System.in);
try (
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
) {
while (true) {
String context = scanner.next();
if ("-1".equalsIgnoreCase(context.trim())) {
break;
}
channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.FANOUT, true, true, null); // 这段代码是有问题的, 在 while 循环中重复定义exchange
channel.basicPublish(EXCHANGE,
"", null, context.getBytes(Charset.defaultCharset()));
}
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
public static final String EXCHANGE = "pub_sub_exchange";
public static final String QUEUE_CONSUMER1 = "pub_sub_queue1";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = RabbitmqUtil.INSTANCE.getInstance();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.FANOUT, true, true, null);
channel.queueDeclare(QUEUE_CONSUMER1, true, false, true, null);
channel.queueBind(QUEUE_CONSUMER1, EXCHANGE, "");
channel.basicConsume(QUEUE_CONSUMER1, false, (consumerTag, message) -> {
String content = new String(message.getBody());
System.err.println("Consumer1接收到消息: " + content);
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}, consumerTag -> {
});
}
public static final String EXCHANGE = "pub_sub_exchange";
public static final String QUEUE_CONSUMER2 = "pub_sub_queue2";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = RabbitmqUtil.INSTANCE.getInstance();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.FANOUT, true, true, null);
channel.queueDeclare(QUEUE_CONSUMER2, true, false, true, null);
channel.queueBind(QUEUE_CONSUMER2, EXCHANGE, "");
channel.basicConsume(QUEUE_CONSUMER2, false, (consumerTag, message) -> {
String content = new String(message.getBody());
System.err.println("Consumer2接收到消息: " + content);
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}, consumerTag -> {
});
}
上面这几段代码只能证明, 消息发送给exchange, exchange只要有队列就发送消息, 如果是fanout, 那么就发送给所有queue
注意queue只是临时队列, 不持久, 不独占, 使用完毕立即删除
路由键模式
根据routing key比较再进行消息传输, 是精确比较
public static final String exchange_name = "X";
public static final String routing_key = "info";
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = RabbitmqUtil.INSTANCE.getInstance();
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();) {
channel.exchangeDeclare(exchange_name, BuiltinExchangeType.DIRECT);
for (int i = 0; i < 10; i++) {
// 这里配合使用 message 持久化功能
// 虽然这么做了, 但是还是无法保证消息不回丢失, 比如rabbitmq会先将消息保存到缓存中, 然后在慢慢写入磁盘这个过程 rabbitmq 废了
// 那么我们的消息也就消失了
channel.basicPublish(exchange_name, routing_key, MessageProperties.PERSISTENT_TEXT_PLAIN,
("hello world " + i).getBytes(StandardCharsets.UTF_8));
}
}
}
public static final String exchange_name = "X";
public static final String routing_key = "error";
public static final String QUEUE_NAME = "amqp.gen.queue.1";
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = RabbitmqUtil.INSTANCE.getInstance();
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange_name, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_NAME, false,
false, false, null);
channel.queueBind(QUEUE_NAME, exchange_name, routing_key);
for (int i = 0; i < 10; i++) {
channel.basicConsume(QUEUE_NAME, false, (consumerTag, message) -> {
System.err.println(new String(message.getBody(), Charset.defaultCharset()));
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}, System.err::println);
}
}
public static final String exchange_name = "X";
public static final String routing_key = "info";
public static final String QUEUE_NAME = "amqp.gen.queue.2";
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = RabbitmqUtil.INSTANCE.getInstance();
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange_name, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_NAME, false,
false, false, null);
channel.queueBind(QUEUE_NAME, exchange_name, routing_key);
for (int i = 0; i < 10; i++) {
channel.basicConsume(QUEUE_NAME, false, (consumerTag, message) -> {
System.err.println(new String(message.getBody(), Charset.defaultCharset()));
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}, System.err::println);
}
}
不知道你发现问题了没? 这两种模式的交换机是不同的, 一个是
direct另一个是fanout这两个的区别在于:
direct根据routing key发送消息, 只要queue绑定了routing key那么就可以根据绑定的routing key和消费者发送的routing key进行比较匹配, 如果匹配则发送到queue中
fanout则根据exchange和queue的绑定情况进行群发, 这里忽略routing key
主题模式
routing key模糊匹配模式
topic模式和routing key模式的区别在于topic模式是模糊匹配
- *: 表示匹配单个单词, 比如
a.b和a.b.c就可以被a.b.*匹配, 但是a.b.c.d就不可以被匹配到 - #: 匹配多级单词, 比如
a.b,a.b.c和a.b.c.d就可以被a.b.*匹配
topic模式是模糊匹配模式,direct模式是精确匹配模式
问:
rabbitmq不是还有个header exchange吗? 我们不讲么?答:
header exchange网上很多人都说存在性能问题, 既然都存在性能问题了, 那我就不讲了(主要是懒
public static final String EXCHANGE = "X";
public static final String ROUTING_KEY = "zhazha.orange.xixi.hehe";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = RabbitmqUtil.INSTANCE.getInstance();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.confirmSelect();
channel.addConfirmListener((deliveryTag, multiple) -> {
System.err.println(deliveryTag + "消息: 确认发送" + " 是否批量: " + multiple);
},(deliveryTag, multiple) -> {
System.err.println(deliveryTag + "消息: 发送失败" + " 是否批量: " + multiple);
});
channel.addReturnListener(returnMessage -> {
String str = JSONUtil.toJsonStr(returnMessage);
System.err.println("消息被退回了: " + str);
});
channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.TOPIC, true, true, null);
for (int i = 0; i < 1000; i++) {
channel.basicPublish(EXCHANGE, ROUTING_KEY, null, ("message" + i).getBytes(Charset.defaultCharset()));
}
}
public static final String EXCHANGE = "X";
public static final String QUEUE1 = "Q1";
public static final String ROUTING_KEY = "*.orange.*";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = RabbitmqUtil.INSTANCE.getInstance();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE,
BuiltinExchangeType.TOPIC, true, true, null);
channel.queueDeclare(QUEUE1, true, false,
true, null);
channel.queueBind(QUEUE1, EXCHANGE, ROUTING_KEY);
channel.basicConsume(QUEUE1, false, (consumerTag, message) -> {
System.err.println("消费1 消息: " + new String(message.getBody()));
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}, consumerTag -> {
});
}
public static final String EXCHANGE = "X";
public static final String QUEUE1 = "Q2";
public static final String ROUTING_KEY = "*.orange.#";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = RabbitmqUtil.INSTANCE.getInstance();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE,
BuiltinExchangeType.TOPIC, true, true, null);
channel.queueDeclare(QUEUE1, true, false,
true, null);
channel.queueBind(QUEUE1, EXCHANGE, ROUTING_KEY);
channel.basicConsume(QUEUE1, false, (consumerTag, message) -> {
System.err.println("消费2 消息: " + new String(message.getBody()));
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}, consumerTag -> {
});
}
rabbitmq丢数据?
生产者发送消息未被保存到queue这段时间会导致丢失数据
rabbitmq会丢数据, 虽然exchange queue message 都可以持久化, 但是 rabbitmq在发送消息的时候, 第一时间写入cache, 但未必第一时间写入队列写入交换机中
这短时间内可能导致数据丢失, 因为机器是有可能宕机的
相同的情况出现在
mysql上,mysql也将数据写入cache中,mysql进程可能崩溃, 但系统可以将数据写入redo log, 但是如果系统崩了呢?