RabbitMQ入门
1、RabbitMQ介绍
1.1、简介
RabbitMQ出现后,国内大部分公司都从ActiveMQ切换到了RabbitMQ,基本代替了activeMQ的位置。它的社区还是很活跃的。
它的单机吞吐量也是万级,对于需要支持特别高的并发的情况,它是无法担当重任的。
在高可用上,它使用的是镜像集群模式,可以保证高可用。
在消息可靠性上,它是可以保证数据不丢失的,这也是它的一大优点。
同时它也支持一些消息中间件的高级功能,如:消息重试、死信队列等。
但是,它的开发语言是erlang,国内很少有人精通erlang,所以导致无法阅读源码。
对于大多数中小型公司,不需要面对技术上挑战的情况,使用它还是比较合适的。而对于一些BAT大型互联网公司,显然它就不合适了。
1.2、RabbitMQ组成
组成部分说明:
Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue
Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费者
Producer:消息生产者,即生产方客户端,生产方客户端将消息发送
Consumer:消息消费者,即消费方客户端,接收MQ转发的消息。
生产者发送消息流程:
1、生产者和Broker建立TCP连接。
2、生产者和Broker建立通道。
3、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。
4、Exchange将消息转发到指定的Queue(队列)
消费者接收消息流程:
1、消费者和Broker建立TCP连接
2、消费者和Broker建立通道
3、消费者监听指定的Queue(队列)
4、当有消息到达Queue时Broker默认将消息推送给消费者。
5、消费者接收到消息。
6、ack回复
工作原理图:
1.3、优点
1.流量消峰
举个例子,如果订单系统最多能处理一万次订单,这个处理能力应付正常时段的下单时绰绰有余,正常时段我们下单一秒后就能返回结果。但是在高峰期,如果有两万次下单操作系统是处理不了的,只能限制订单超过一万后不允许用户下单。使用消息队列做缓冲,我们可以取消这个限制,把一秒内下的订单分散成一段时间来处理,这时有些用户可能在下单十几秒后才能收到下单成功的操作,但是比不能下单的体验要好。
2.应用解耦
以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。当转变成基于消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。在这几分钟的时间里,物流系统要处理的内存被缓存在消息队列中,用户的下单操作可以正常完成。当物流系统恢复后,继续处理订单信息即可,中单用户感受不到物流系统的故障,提升系统的可用性
3.异步处理
有些服务间调用是异步的,例如 A 调用 B,B 需要花费很长时间执行,但是 A 需要知道 B 什么时候可以执行完,以前一般有两种方式,A 过一段时间去调用 B 的查询 api 查询。或者 A 提供一个 callback api,B 执行完之后调用 api 通知 A 服务。这两种方式都不是很优雅,使用消息总线,可以很方便解决这个问题,A 调用 B 服务后,只需要监听 B 处理完成的消息,当 B 处理完成后,会发送一条消息给 MQ,MQ 会将此消息转发给 A 服务。这样 A 服务既不用循环调用 B 的查询 api,也不用提供 callback api。同样 B 服务也不用做这些操作。A 服务还能及时的得到异步处理成功的消息。
2、简单队列
package com.mengyb.rabbitmq.one;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: Prodcer
* @Description: 生产者
* @Author: mengyb
* @Date: 2023/4/13 上午9:23
*/
public class Prodcer {
// 队列名称
private static final String QUEUE_NAME = "hello";
// 发送消息
public static void main(String[] args) throws IOException, TimeoutException {
// 创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 工厂连接IP 连接MQ队列
factory.setHost("IP");
factory.setPort(5672);
// 用户名
factory.setUsername("guest");
// 密码
factory.setPassword("guest");
// 创建连接
Connection connection = factory.newConnection();
// 获取信道
Channel channel = connection.createChannel();
/**
* 生成队列
* 1、队列名称
* 2、队列消息是否持久化,默认false不持久化,存在内存中, 持久化true保存在磁盘中
* 3、队列是否只给一个消费者消费,或者多个消费者消费
* 4、是否自动删除,
* 5、其他参数
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 发送消息
String mesage = "hello world";
/**
* 1、发送到哪个交换机
* 2、路由的key值是哪一个,本次是队列名称
* 3、其他参数信息
* 4、发送消息的消息体
*/
channel.basicPublish("", QUEUE_NAME, null, mesage.getBytes());
System.out.println("发送消息完成");
}
}
package com.mengyb.rabbitmq.one;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: Consumer
* @Description: 消费者
* @Author: mengyb
* @Date: 2023/4/13 上午9:40
*/
public class Consumer {
// 队列名称
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 工厂连接IP 连接MQ队列
factory.setHost("IP");
factory.setPort(5672);
// 用户名
factory.setUsername("guest");
// 密码
factory.setPassword("guest");
// 创建连接
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 声明
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("接收到的消息----" + new String(message.getBody()));
};
// 声明 取消消息的回调
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息被中断时回调----");
};
/**
* 消费者消费消息
* 1、消费的哪个队列
* 2、是否自动应答
* 3、消费者未成功接收到消息的回调 DeliverCallback
* 4、消费者取消消费的回调 CancelCallback
*/
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
}
}
3、工作队列
封装一个工具类
package com.mengyb.rabbitmq.utils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: RabbitMqUtils
* @Description: 消息队列工具类
* @Author: mengyb
* @Date: 2023/4/13 上午9:56
*/
public class RabbitMqUtils {
/**
* 连接工厂,创建信道的工具类
* @return
* @throws IOException
* @throws TimeoutException
*/
public static Channel getChannel() throws IOException, TimeoutException {
// 创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 工厂连接IP 连接MQ队列
factory.setHost("IP");
factory.setPort(5672);
// 用户名
factory.setUsername("guest");
// 密码
factory.setPassword("guest");
// 创建连接
Connection connection = factory.newConnection();
// 获取信道
Channel channel = connection.createChannel();
return channel;
}
}
package com.mengyb.rabbitmq.work;
import com.mengyb.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: Producer
* @Description: Work工作队列的生产者
* @Author: mengyb
* @Date: 2023/4/13 上午9:50
*/
public class WorkerProducer {
// 队列名称
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 绑定队列
/**
* 1、队列
* 2、 是否持久化
* 3、 是否只有一个消费者
* 4、是否删除
* 5、其他参数
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()) {
String message = scanner.nextLine();
/**
* 1、交换机
* 2、routing key 的值 或者 队列
* 3、其他参数
* 4、发送消息
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("发送消息:" + message);
}
}
}
package com.mengyb.rabbitmq.work;
import com.mengyb.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: Work01
* @Description: 工作线程(消费者)
* @Author: mengyb
* @Date: 2023/4/13 上午10:00
*/
public class Work01 {
// 队列名称
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("接收到的消息---" + new String(message.getBody()));
};
CancelCallback cancelCallback = consumerTag -> {
// 哪个消息取消了
System.out.println(consumerTag + "消息被取消掉的回调---");
};
System.out.println("worker01--consumer-");
/**
* 1、获取队列
* 2、是否自动应到
* 3、成功回调
* 4、取消消息回调
*/
channel.basicConsume(QUEUE_NAME,deliverCallback, cancelCallback, null);
}
}
package com.mengyb.rabbitmq.work;
import com.mengyb.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: Work01
* @Description: 工作线程(消费者)
* @Author: mengyb
* @Date: 2023/4/13 上午10:00
*/
public class Work02 {
// 队列名称
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("接收到的消息---" + new String(message.getBody()));
};
CancelCallback cancelCallback = consumerTag -> {
// 哪个消息取消了
System.out.println(consumerTag + "消息被取消掉的回调---");
};
System.out.println("worker02--consumer-");
/**
* 1、获取队列
* 2、是否自动应到
* 3、成功回调
* 4、取消消息回调
*/
channel.basicConsume(QUEUE_NAME,deliverCallback, cancelCallback, null);
}
}
4、消息应答
4.1、概念
消费者完成一个任务需要一段时间,但在在消费者处理一个任务过程中突然挂掉了,会发生什么情况?RabbitMQ一旦向消费者传递一个消息,便立即将该消息标记为删除.在这种情况下,突然有个消费者挂了,我们将丢失在处理的消息,以及后续发送给该消费者的消息,因为它无法接收到.
为了保证消息在发送过程中不丢失,RabbitMQ引入消息应答机制.消息应答机制就是:消费者在接收到消息并且处理该消息后,告诉RabbitMQ它已经处理完成了,RabbitMQ应该删除该消息了.
4.2、自动应答
消息发送后会立即被认为已经传送成功,这种模式需要在高吞吐和数据传输安全性方法做权衡,因为这种模式如果消息在接收到之前,消费者出现裂解或者信道关闭,那么消息就会丢失,当然另一方面这种模式消费者那边可以传递过载的消息,没有对传递的消息数量进行限制,这可能导致消费者接收到过多的消息,来不及处理,导致消息积压,最终内存耗尽,消费者线程被操作系统杀死,所以这种模式仅适用于在消费者可以高效并以某种速率能够处理这些消息的情况下使用.
4.3、 消息应答的方式
1、Channel.basicAck(用于肯定确认);
RabbitMQ已知道该消息被成功处理,可以丢弃
2、Channel.basicNack(用于否定确认);
3、Channel.basicReject(用于否定确认);与Channel.basicNack()相比缺少一个批量处理参数,不处理该消息了直接拒绝,可以丢弃.
4.4、Multiple批量应答
4.5、消息自动重新入队
如果消费者偶遇某些原因失去连接,导致消息未发送ACK消息确认,RabbitMQ将了解到消息未被处理,并重新对其进行排队.如果此时它消费者可以处理,它将会重新分发给其他的消费者.这样即使消费者偶尔死亡,也不会丢失任何的消息.
5、持久化
为保证在RabbitMQ服务停止掉后消息生产者发送过来的消息不丢失.默认情况下RabbitMQ退出或者其他原因崩溃,它忽视队列和消息,除非通知它不要这样做.确保消息不会丢失需要:将队列和消息都标记为持久化
5.1、实现持久化
// 消息队列持久化
boolean durable = true;
channel.queueDeclare(DURABLE_QUEUE_NAME, durable, false, false, null);
// 其他参数信息 MessageProperties.PERSISTENT_TEXT_PLAIN 文本持久化,消息保存到磁盘上
channel.basicPublish("", DURABLE_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"));
持久化完成后,重启RabbitMQ之后该队列也会存在.
6、分发任务
6.1、轮训分发
6.2、不公平分发
RabbitMQ默认是轮训分发,但是这种策略并不是很好,因为有些消费者处理任务速度很快,有些消费者处理任务速度很慢,这时候采用轮训分发,会导致处理任务快的消费者处于空闲状态.这种情况肯定不好,因此,为了避免这种情况使用不公平分发,能力越强责任越大.
// 消费者在声明信道的时候设置参数 不公平分发为1, 默认是轮训分发0
int perfatch = 1;
channel.basicQos(perfatch);
6.3、预取值prefetch
可以为每个队列提前取出指定数量的值,进行排序
// 在消费者获取消息前进行设置 0:为轮训分发;1:为不公平分发; >1 则为预取值
int perfatch = 5;
channel.basicQos(perfatch);
7、发布确认
生产者将信道设置为confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始).一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包括消息唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在消息写入磁盘后发出,broker回传给生产者的确认消息中delivery—tag域包含了确认消息的序列号.
confirm模式最大的好处在于它是异步的,一旦发布一条消息,生产值应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该消息,若RabbitMQ因自身原因导致消息丢失,那就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息.
7.1、开启发布确认方法
// 发布消息确认开启
channel.confirmSelect();
7.2、单个确认发布
这是一种简单的确认方式,它是一种同步确认发布的方式,也就是发布一个消息之后只有它被确认发布,后续的消息才能继续发布, waitForConfirmsOrDie(long)这个方法只有在消息被确认的时候才返回,若果在指定时间范围内这个消息没有给确认那么它将抛出异常.
这种确认方式最大的缺点就是:发布的速度特别慢,因为如果没有确认发布的消息就会阻塞所有的后续消息的发布,这种方式最多提供美妙不过几百条的发布消息.
// 单个发布消息
public static void publicMessage() throws Exception {
// 获取信道
Channel channel = RabbitMqUtils.getChannel();
// 开启确认模式
channel.confirmSelect();
String queueName = UUID.randomUUID().toString();
// 声明队列
/**
* 1、队列名称
* 2、是否持久化
* 3、是否共享
* 4、是否自动删除
* 5、其他参数
*/
channel.queueDeclare(queueName, false, false, false, null);
long startTime = System.currentTimeMillis();
for(int i = 0; i < 1000; i ++) {
String message = i + "";
// 发布消息
/**
* 1、交换机
* 2、routing key的值
* 3、其他参数
* 4、发送的消息
*/
channel.basicPublish("", queueName, null, message.getBytes());
// 单个消息马上发布确认
boolean flag = channel.waitForConfirms();
if (flag) {
System.out.println("发送消息成功!");
}
}
long endTime = System.currentTimeMillis();
System.out.println(MESSAGE_COUNT + "条发布完成时间---" + (endTime - startTime) + "ms");
}
// 1000条发布完成时间---8726ms
虽然发布消耗时间,但是可以查看到哪一条出现了问题
7.3、批量确认
// 批量发布消息
public static void publicMessageBatch() throws Exception {
Channel channel = RabbitMqUtils.getChannel();
// 开启确认模式
channel.confirmSelect();
String queueName = UUID.randomUUID().toString();
// 声明队列
/**
* 1、队列名称
* 2、是否持久化
* 3、是否共享
* 4、是否自动删除
* 5、其他参数
*/
channel.queueDeclare(queueName, false, false, false, null);
long startTime = System.currentTimeMillis();
int batchSize = 100;
for(int i = 0; i < 1000; i ++) {
String message = i + "";
// 发布消息
/**
* 1、交换机
* 2、routing key的值
* 3、其他参数
* 4、发送的消息
*/
channel.basicPublish("", queueName, null, message.getBytes());
if (i % batchSize == 0) {
// 每一百条消息马上发布确认
boolean flag = channel.waitForConfirms();
if (flag) {
System.out.println("发送消息成功!");
}
}
}
long endTime = System.currentTimeMillis();
System.out.println(MESSAGE_COUNT + "条发布完成时间---" + (endTime - startTime) + "ms");
}
// 1000条发布完成时间---186ms
批量确认发布消息,比单个确认发布消息速度快,若出现错误,无法准确的找到问题所在.
7.4、异步确认发布消息
异步确认逻辑要比前两个都复杂,但是性价比最高,无论是可靠性还是效率都是最好的.它利用回调函数来达到消息可靠性的传递,这个中间件也是通过函数回调来保证是否投递成功.
// 异步确认发布消息
public static void publicMessageAsyn() throws Exception {
Channel channel = RabbitMqUtils.getChannel();
// 开启确认模式
channel.confirmSelect();
String queueName = UUID.randomUUID().toString();
// 声明队列
/**
* 1、队列名称
* 2、是否持久化
* 3、是否共享
* 4、是否自动删除
* 5、其他参数
*/
channel.queueDeclare(queueName, false, false, false, null);
long startTime = System.currentTimeMillis();
// 异步发送核心代码
/**
* 线程安全有序的哈希表, 适用于高并发情况下的
*/
ConcurrentSkipListMap<Long, String> confirmMessageMap = new ConcurrentSkipListMap();
// 消息确认成功回调函数
/**
* deliveryTag: 消息的标记
* multiple: 是否为批量
*/
ConfirmCallback ackCallback = (deliveryTag, multiple) -> {
if (multiple) {
// 批量确认
ConcurrentNavigableMap<Long, String> confirmComplete = confirmMessageMap.headMap(deliveryTag);
confirmComplete.clear();
} else {
// 单个确认
confirmMessageMap.remove(deliveryTag);
}
System.out.println("确认的消息: " + deliveryTag);
};
// 消息确认失败回调函数
/**
* deliveryTag: 消息的标记
* multiple: 是否为批量
*/
ConfirmCallback nackCallback = (deliveryTag, multiple) -> {
String notConfirmMessage = confirmMessageMap.get(deliveryTag);
System.out.println("未确认的消息是: " + notConfirmMessage);
};
channel.addConfirmListener(ackCallback, nackCallback);
for(int i = 0; i < MESSAGE_COUNT; i ++) {
String message = "发送消息" + i ;
// 发布消息
/**
* 1、交换机
* 2、routing key的值
* 3、其他参数
* 4、发送的消息
*/
channel.basicPublish("", queueName, null, message.getBytes());
// 记录发布消息的 序号,信息
confirmMessageMap.put(channel.getNextPublishSeqNo(), message);
}
long endTime = System.currentTimeMillis();
System.out.println(MESSAGE_COUNT + "条发布完成时间---" + (endTime - startTime) + "ms");
}
// 1000条发布完成时间---101ms
如何处理未确认消息?
解决方案就是把未确认的消息放到一个机遇内存的能被发布线程访问的队列,比如说用并发链路ConcurrentLinkedQueue这个队列在confirm callbacks 与发布线程之间进行消息的传递.(以上代码已经整合)
8、Exchanges交换机模式
8.1、Exchanges概念
RabbitMQ消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列中.甚至都不知道这些消息传递到哪些队列中. 相反,生产者只能将消息发送到交换机,交换机的工作内容很简单,一边接收生产者产生的消息,一边将他们放入队列.交换机必须确切的知道如何处理收到的消息,是放到哪个队列还是遗弃,都由交换机的类型决定.
8.2、Exchanges交换机类型
direct(直连交换机)、topic(主题交换机)、fanout(扇型交换机)
8.3、无名Exchanges
以上使用的均为无名交换机
8.4、临时队列
没有持久化的队列均为临时队列,在不使用的时候均被删除.
8.5、fanout Exchange
fanout这种类型很简单,他就是将接收到的消息广播到它知道的所有队列中.
扇型交换机(funout exchange)将消息路由给绑定到它身上的所有队列。不同于直连交换机,路由键在此类型上不启任务作用。如果N个队列绑定到某个扇型交换机上,当有消息发送给此扇型交换机时,交换机会将消息的发送给这所有的N个队列.
// 生产者
package com.mengyb.rabbitmq.fanout;
import com.mengyb.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: FanoutExchange
* @Description: fanout广播交换机
* @Author: mengyb
* @Date: 2023/4/13 下午2:55
*/
public class FanoutExchange {
// 交换机名称
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String message = scanner.nextLine();
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println("消息发布成功!");
}
}
}
消费者01
package com.mengyb.rabbitmq.fanout;
import com.mengyb.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: ReciveConsumer01
* @Description: 消费者01
* @Author: mengyb
* @Date: 2023/4/13 下午2:55
*/
public class ReciveConsumer01 {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机 交换机名称 类型
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 声明一个队列
String queueName = channel.queueDeclare().getQueue();
// 将Exchange与Queue绑定
channel.queueBind(queueName, EXCHANGE_NAME, "" );
//
System.out.println("消费者01等待接收消息:");
// 确认消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("接收到消息:" + new String(message.getBody(), "UTF-8"));
};
// 消费者取消消息时回调接口
CancelCallback cancelCallback = consumerTag -> {
};
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
}
}
消费者02
package com.mengyb.rabbitmq.fanout;
import com.mengyb.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: ReciveConsumer02
* @Description: 消费者02
* @Author: mengyb
* @Date: 2023/4/13 下午2:55
*/
public class ReciveConsumer02 {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机 交换机名称 类型
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 声明一个队列
String queueName = channel.queueDeclare().getQueue();
// 将Exchange与Queue绑定
channel.queueBind(queueName, EXCHANGE_NAME, "" );
//
System.out.println("消费者02等待接收消息:");
// 确认消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("接收到消息:" + new String(message.getBody(), "UTF-8"));
};
// 消费者取消消息时回调接口
CancelCallback cancelCallback = consumerTag -> {
};
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
}
}
8.6、Direct Exchange 直接交换机(路由交换机)
直连型交换机(direct exchange)是根据消息携带的路由键(routing key)将消息投递给对应队列的,步骤如下:
1、将一个队列绑定到某个交换机上,同时赋予该绑定一个路由键(routing key)
2、当一个携带着路由值为R的消息被发送给直连交换机时,交换机会把它路由给绑定值同样为R的队列。
生产者
package com.mengyb.rabbitmq.direct;
import com.mengyb.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: DirectExchange
* @Description: Direct交换机生产者
* @Author: mengyb
* @Date: 2023/4/13 下午3:22
*/
public class DirectExchange {
// 交换机名称
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String message = scanner.nextLine();
if (message.equals("info")) {
channel.basicPublish(EXCHANGE_NAME, "info", null, message.getBytes());
}
if (message.equals("warning")) {
channel.basicPublish(EXCHANGE_NAME, "warning", null, message.getBytes());
}
if (message.equals("error")) {
channel.basicPublish(EXCHANGE_NAME, "error", null, message.getBytes());
}
System.out.println("消息发布成功!");
}
}
}
package com.mengyb.rabbitmq.direct;
import com.mengyb.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: Consumer001
* @Description: direct消费者
* @Author: mengyb
* @Date: 2023/4/13 下午3:23
*/
public class Consumer001 {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机 交换机名称 类型
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 声明一个队列
channel.queueDeclare("console", false, false, false, null);
// 将Exchange与Queue绑定
channel.queueBind("console", EXCHANGE_NAME, "info" );
//
System.out.println("消费者console --- info 等待接收消息:");
// 确认消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("消费者console接收到info消息:" + new String(message.getBody(), "UTF-8"));
};
// 消费者取消消息时回调接口
CancelCallback cancelCallback = consumerTag -> {
};
channel.basicConsume("console", true, deliverCallback, cancelCallback);
}
}
package com.mengyb.rabbitmq.direct;
import com.mengyb.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: Consumer001
* @Description: direct消费者
* @Author: mengyb
* @Date: 2023/4/13 下午3:23
*/
public class Consumer002 {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机 交换机名称 类型
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 声明一个队列
channel.queueDeclare("console", false, false, false, null);
// 将Exchange与Queue绑定
channel.queueBind("console", EXCHANGE_NAME, "warning" );
//
System.out.println("消费者console -- warning等待接收消息:");
// 确认消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("console接收到warning消息:" + new String(message.getBody(), "UTF-8"));
};
// 消费者取消消息时回调接口
CancelCallback cancelCallback = consumerTag -> {
};
channel.basicConsume("console", true, deliverCallback, cancelCallback);
}
}
package com.mengyb.rabbitmq.direct;
import com.mengyb.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: Consumer001
* @Description: direct消费者
* @Author: mengyb
* @Date: 2023/4/13 下午3:23
*/
public class Consumer003 {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机 交换机名称 类型
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 声明一个队列
channel.queueDeclare("disk", false, false, false, null);
// 将Exchange与Queue绑定
channel.queueBind("disk", EXCHANGE_NAME, "error" );
//
System.out.println("消费者disk --- error等待接收消息:");
// 确认消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("消费者disk接收到error消息:" + new String(message.getBody(), "UTF-8"));
};
// 消费者取消消息时回调接口
CancelCallback cancelCallback = consumerTag -> {
};
channel.basicConsume("disk", true, deliverCallback, cancelCallback);
}
}
8.7、topic Exchange 主题交换机
发送类型为topic交换机的消息routing_key不可随意写,需要满足一定的要求,它必须是一个单词列表,以点号分隔开.比如:“key.user”, “error.logs”, "error.message"等,单词列表最多不能超过255个字节.
*代表一个单词
#代表零个或多个单词
例如:
.orange. 中间带orange的三个单词
..rabbit 最后一个单词是rabbit的三个单词
lazy.# 第一个单词是lazy的对个单词
注意:
当一个队列绑定键是#,那么这个队列将接收所有数据,有点儿像fanout了,
若队列绑定键中没有#和*出现,那么该队列绑定类型就是direct了.
生产者
package com.mengyb.rabbitmq.topic;
import com.mengyb.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: TopicExchange
* @Description: 主题交换机生产者
* @Author: mengyb
* @Date: 2023/4/13 下午3:44
*/
public class TopicExchange {
// 交换机名称
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
Map<String,String> map = new HashMap<>();
map.put("q.orange.rabbit", "q.orange.rabbit");
map.put("lazy.orange.rabbit", "lazy.orange.rabbit");
map.put("q.orange.fox", "q.orange.fox");
map.put("lazy.o.rabbit.mm", "lazy.o.rabbit.mm");
map.put("q.orange.rabbit.asd", "q.orange.rabbit.asd");
for (Map.Entry<String, String> stringEntry : map.entrySet()) {
String routingKey = stringEntry.getKey();
String message = stringEntry.getValue();
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
System.out.println("生产者发出消息:" + message);
}
}
}
消费者
package com.mengyb.rabbitmq.topic;
import com.mengyb.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: TopicConsumer01
* @Description: 消费者01
* @Author: mengyb
* @Date: 2023/4/13 下午3:48
*/
public class TopicConsumer01 {
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机 交换机名称 类型
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 声明一个队列
String queueName = "Q1";
channel.queueDeclare(queueName, false, false, false, null);
// 将Exchange与Queue绑定
channel.queueBind(queueName, EXCHANGE_NAME, "*.orange.*" );
//
System.out.println("等待接收消息:");
// 确认消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("接收到消息:" + new String(message.getBody(), "UTF-8"));
};
// 消费者取消消息时回调接口
CancelCallback cancelCallback = consumerTag -> {
};
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
}
}
package com.mengyb.rabbitmq.topic;
import com.mengyb.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @ClassName: TopicConsumer01
* @Description: 消费者01
* @Author: mengyb
* @Date: 2023/4/13 下午3:48
*/
public class TopicConsumer02 {
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机 交换机名称 类型
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 声明一个队列
String queueName = "Q2";
channel.queueDeclare(queueName, false, false, false, null);
// 将Exchange与Queue绑定
channel.queueBind(queueName, EXCHANGE_NAME, "*.*.rabbit" );
channel.queueBind(queueName, EXCHANGE_NAME, "lazy.#" );
//
System.out.println("等待接收消息:");
// 确认消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("接收到消息:" + new String(message.getBody(), "UTF-8"));
};
// 消费者取消消息时回调接口
CancelCallback cancelCallback = consumerTag -> {
};
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
}
}