RabbitMQ学习笔记
一、消息队列概述
1、简介:
消息队列是用来发送消息的消息中间件,本质上是队列,有先进先出的特点。
2、功能:
服务削峰
程序解耦
异步消息
3、MQ分类:
ActiveMQ
RocketMQ
Kafka
RabbitMQ
4、RabbitMQ安装
(一)安装并运行
(1)、在docker hub 中查找rabbitmq镜像
docker search rabbitmq:3-management
(2)、从docker hub 中拉取rabbitmq镜像
docker pull rabbitmq:3-management
(3)、查看拉取的rabbitmq镜像
docker images
(4)、运行 rabbitmq服务端
docker run -d \
-v /opt/rabbitmq/data:/var/lib/rabbitmq \
-p 5672:5672 -p 15672:15672 --name rabbitmq --restart=always \
--hostname myRabbit rabbitmq:3-management
参数解释:
docker run :启动命令
--name :给容器起名字
-p :★映射端口号,主机端口:容器端口
-v : 将主机中指定目录的挂载到容器的目录
-i : 以交互模式运行。
-t : 进入终端。
-d : 以守护模式后台运行。
-e XXX_XXX="xxxxxxxxxxx" : 指定环境变量
(5)、查看正在运行的容器
docker ps
(6)、容器运行成功之后,在浏览器访问:
http://192.168.1.100:15672
账号 guest , 密码 guest
(二)其他操作:
(1)、重新启动 rabbitmq 容器
docker restart <容器id>
(2)、结束正在运行的容器
docker stop <容器id> 容器优雅退出
docker kill <容器id> 容器直接退出
(3)、删除 docker 容器 (容器在删除前要先结束)
docker rm <容器id> [ <容器id> ...]
(4)、删除 docker 镜像
docker rmi <镜像id> [ <镜像id> ...]
(5)、查看正在运行的 rabbitmq 进程
ps -ef | grep rabbitmq
(6)、进入容器内部
docker exec -it <容器id> /bin/bash
(7)、查看容器内网ip地址
docker inspect <容器id>
(8)、查看docker 镜像的版本
docker image inspect <镜像名称>:latest|grep -i version
5、RabbitMQ的工作原理
(一)下图是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、简介:
channel.basicPublish()方法第一个参数为空字符串,消息生产者绑定默认交换机,第二个参数填入队列名称作为routingKey,就是简易队列模式。
2、代码:
(1)生产者:
public class Producer {
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception {
ConnectionFactory fact = new ConnectionFactory();
fact.setHost("192.168.1.100");
fact.setUsername("guest");
fact.setPassword("guest");
Channel channel = fact.newConnection().createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String mess = "hello world";
channel.basicPublish("", QUEUE_NAME, null, mess.getBytes());
System.out.println("消息发送完毕");
}
}
// 第一个参数是交消息换机名称。如果为空字符串则队列模式为简易队列模式,绑定默认交换机,第二个参数填入队列名称作为routingKey。
// 第二个参数是路由键routingKey。
// 第三个参数是消息的属性.
// 第四个参数是消息的内容.
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
(2)消费者:
public class Consumer {
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception {
ConnectionFactory fact = new ConnectionFactory();
fact.setHost("192.168.1.100");
fact.setUsername("guest");
fact.setPassword("guest");
Connection connection = fact.newConnection();
Channel channel = connection.createChannel();
DeliverCallback d = (consumerTag, message) -> {
System.out.println(new String(message.getBody()));
};
CancelCallback c = consumerTag -> {
System.out.println("消息被中断");
};
channel.basicConsume(QUEUE_NAME, true, d, c);
}
}
(3)编写工具类:
public class ConnectUtils {
public static Channel connect() throws Exception {
ConnectionFactory fact = new ConnectionFactory();
fact.setHost("192.168.1.100");
fact.setUsername("guest");
fact.setPassword("guest");
Connection connection = fact.newConnection();
Channel channel = connection.createChannel();
return channel;
}
}
public class SleepUtils {
public static void sleep(int second) {
try {
Thread.sleep(second * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
三、相关设置
3、消息应答:
分为自动应答和手动应答。
不建议自动应答。
手动应答:
channel.basicAsk() (用于确认应答)
channel.basicNask() (用于否认应答)
channel.basicReject() (用于否认应答)
multiple 批量应答 true false 建议使用false
4、消息重新入队:
设置为手动应答后,当一个消费者 断连,mq会将消息重新入队,交给其他的消费者。
public class Producer {
private final static String ACK_QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
channel.queueDeclare(ACK_QUEUE_NAME, false, false, false, null);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String message = scanner.next();
channel.basicPublish("", ACK_QUEUE_NAME, null, message.getBytes("UTF-8"));
}
}
}
public class Consumer1 {
private static final String QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
System.out.println("Consumer1等待接收消息的时间较短");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
SleepUtils.sleep(1);
System.out.println("C1接收到的消息:" + message);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), true);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息被中断");
};
channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
}
}
public class Consumer2 {
private static final String QUEUE_NAME = "ack_queue";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
System.out.println("Consumer2等待接收消息的时间较长");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
SleepUtils.sleep(10);
System.out.println("C2接收到的消息:" + message);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), true);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息被中断");
};
channel.basicConsume(QUEUE_NAME, false, deliverCallback, cancelCallback);
}
}
在Producer 控制台依次输入
aa 消息被C1接收到, 等待1秒,打印出“C1接收到的消息:aa”
bb 消息被C2接收到, 等待10秒,打印出“C2接收到的消息:bb”
cc 消息被C1接收到, 等待1秒,打印出“C1接收到的消息:cc”
dd 消息被C2接收到, 等待10秒的过程中关闭C2服务,消息重新进入队列,分配到C1服务。
消息被C1接收到, 等待1秒,打印出“C1接收到的消息:dd”
5、持久化
(1)队列持久化:
在生产者中:
channel.queueDeclare(ACK_QUEUE_NAME, true, false, false, null) 第二个参数为 boolean durable,值为true则进行持久化,在图形化界面队列后面显示大写字母‘D’
(2)消息持久化:
channel.basicPublish(“”, ACK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(“UTF-8”));
6、不公平分发:处理速度快的消费者多接受消息(能者多劳)。
与其相对的是轮询分发,完全按照一个消费者轮一次的方式接收信息。
在消息消费者设置
channel.basicQos(1);
Qos: Quality of Service
预取值Prefetch :信道的消息容量
在消息消费者C1设置
channel.basicQos(2)
在消息消费者C2设置
channel.basicQos(5);
四、发布确认
三种方式:
单个发布确认、批量发布确认、异步发布确认
public class Producer {
public static void main(String[] args) throws Exception {
// confirm01(); //单个发布确认耗时920ms
// confirm02(); //批量发布确认耗时92ms
confirm03(); // 批量发布确认耗时66ms
}
//单个发布确认
public static void confirm01() throws Exception {
Channel channel = ConnectUtils.connect();
channel.confirmSelect();
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, true, false, false, null);
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
String message = "消息" + i;
System.out.println("生产者发送消息:" + message);
channel.basicPublish("", queueName, null, message.getBytes("UTF-8"));
channel.waitForConfirms();
}
long end = System.currentTimeMillis();
System.out.println("单个发布确认耗时" + (end - begin) + "ms");
}
//批量发布确认
public static void confirm02() throws Exception {
Channel channel = ConnectUtils.connect();
channel.confirmSelect();
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, true, false, false, null);
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
String message = "消息" + i;
System.out.println("生产者发送消息:" + message);
channel.basicPublish("", queueName, null, message.getBytes("UTF-8"));
if ((i + 1) % 100 == 0) {
channel.waitForConfirms();
}
}
long end = System.currentTimeMillis();
System.out.println("批量发布确认耗时" + (end - begin) + "ms");
}
//异步发布确认
public static void confirm03() throws Exception {
Channel channel = ConnectUtils.connect();
channel.confirmSelect();
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, true, false, false, null);
ConfirmCallback c1 = (deliveryTag, multiple) -> {
System.out.println("确认的消息:" + deliveryTag);
};
ConfirmCallback c2 = (deliveryTag, multiple) -> {
System.out.println("未确认的消息:" + deliveryTag);
};
channel.addConfirmListener(c1, c2); // 异步通知
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
String message = "消息" + i;
System.out.println("生产者发送消息:" + message);
channel.basicPublish("", queueName, null, message.getBytes("UTF-8"));
}
long end = System.currentTimeMillis();
System.out.println("批量发布确认耗时" + (end - begin) + "ms");
}
}
五、交换机 Exchange:
1、作用:通过交换机,能够让消息被多个消费者接收
消息不能由生产者直接发送到队列,而是由生产者发送到交换机,再由交换机发送到队列。
2、交换机类型:
direct(直接)、topic(主题)、headers(标题)、fanout(扇出)
3、无名交换机:
channel.basicPublish("", queueName, null, message.getBytes("UTF-8"));
不指定交换机名称则使用默认交换机(AMQP default)
4、临时队列:
不进行持久化的队列
5、fanout类型:发布订阅模式
将消息发送到同一个交换机下不同队列的消费者
public class Producer {
private static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String message = scanner.next();
System.out.println("生产者发送消息:" + message);
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
}
}
}
public class Consumer1 {
private static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
System.out.println("Consumer1等待接收消息的时间较短");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
System.out.println("C1接收到的消息:" + message);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息被中断");
};
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "key001");
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
}
}
public class Consumer2 {
private static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
System.out.println("Consumer2等待接收消息的时间较短");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
System.out.println("C2接收到的消息:" + message);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息被中断");
};
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "key001");
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
}
}
生产者控制台:
aa
生产者发送消息:aa
bb
生产者发送消息:bb
cc
生产者发送消息:cc
dd
生产者发送消息:dd
ff
生产者发送消息:ff
消费者C1控制台:
Consumer1等待接收消息的时间较短
C1接收到的消息:aa
C1接收到的消息:bb
C1接收到的消息:cc
C1接收到的消息:dd
C1接收到的消息:ff
消费者C2控制台:
Consumer1等待接收消息的时间较短
C1接收到的消息:aa
C1接收到的消息:bb
C1接收到的消息:cc
C1接收到的消息:dd
C1接收到的消息:ff
6、direct类型:路由模式
与交换机绑定的队列的routingKey不相同,在发送消息时指定交换机和routingKey就能找到唯一的队列。
消息生产者:
public class Producer {
private static final String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String message = scanner.next();
System.out.println("生产者发送消息:" + message);
channel.basicPublish(EXCHANGE_NAME, "key201", null, message.getBytes("UTF-8"));
}
}
}
消费者C1:
public class Consumer1 {
private static final String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
System.out.println("C1接收到的消息:" + message);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息被中断");
};
String queueName = "direct1-1";
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, EXCHANGE_NAME, "key101");
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
}
}
消费者C2:
public class Consumer2 {
private static final String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
System.out.println("C2接收到的消息:" + message);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息被中断");
};
String queueName1 = "direct2-1";
channel.queueDeclare(queueName1, false, false, false, null);
channel.queueBind(queueName1, EXCHANGE_NAME, "key201");
channel.basicConsume(queueName1, true, deliverCallback, cancelCallback);
String queueName2 = "direct2-2";
channel.queueDeclare(queueName2, false, false, false, null);
channel.queueBind(queueName2, EXCHANGE_NAME, "key202");
channel.basicConsume(queueName2, true, deliverCallback, cancelCallback);
}
}
在生产者控制台输入:
aaa
生产者发送消息:aaa
bbb
生产者发送消息:bbb
ccc
生产者发送消息:ccc
ddd
生产者发送消息:ddd
消费者控制台显示:
C2接收到的消息:aaa
C2接收到的消息:bbb
C2接收到的消息:ccc
C2接收到的消息:ddd
7、topic类型:主题模式
消费者订阅主题,获取符合条件的队列中的消息
- 匹配一个单词
匹配一个或多个单词
例:
cat.*.pop cat.temp.pop
cat.# cat.end.yield
*.keep.believe queen.keep.believe
代码示例:
生产者:
public class Producer {
private static final String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String message = scanner.next();
System.out.println("生产者发送消息:" + message);
channel.basicPublish(EXCHANGE_NAME, "start.sss.so", null, message.getBytes("UTF-8"));
}
}
}
消费者C1 :
public class Consumer1 {
private static final String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String queueName = "Queue1";
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, EXCHANGE_NAME, "*.middle.*");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
System.out.println("C1接收到的消息:" + message);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息被中断");
};
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
}
}
消费者C2 :
public class Consumer2 {
private static final String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String queueName = "Queue2";
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, EXCHANGE_NAME, "#.end");
channel.queueBind(queueName, EXCHANGE_NAME, "start.*.*");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
System.out.println("C2接收到的消息:" + message);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息被中断");
};
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
}
}
在生产者控制台输入:
hello
生产者发送消息:hello
nihao
生产者发送消息:nihao
zaima
生产者发送消息:zaima
消费者C1控制台没有显示内容。
消费者C2控制台显示:
C2接收到的消息:hello
C2接收到的消息:nihao
C2接收到的消息:zaima
六、死信队列:
1、死信:不能被接受的消息
2、造成死信的原因:
消息TTL过期,
队列达到最大长度,
消息被拒绝,
3、消息TTL过期
代码:
生产者:
public class Producer {
private static final String EXCHANGE_NAME = "normal_exchange";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
AMQP.BasicProperties pro = new AMQP.BasicProperties().builder().expiration("10000").build();
for (int i = 0; i < 10; i++) {
String message = "消息" + i;
System.out.println("生产者发送消息:" + message);
channel.basicPublish(EXCHANGE_NAME, "zhangsan", pro, message.getBytes("UTF-8"));
}
}
}
消费者1:
public class Consumer1 {
private static final String NORMAL_EXCHANGE = "normal_exchange";
private static final String NORMAL_QUEUE = "normal_queue";
private static final String DEAD_EXCHANGE = "dead_exchange";
private static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
channel.exchangeDeclare(NORMAL_EXCHANGE, "direct");
channel.exchangeDeclare(DEAD_EXCHANGE, "direct");
Map<String, Object> map = new HashMap<>();
map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
map.put("x-dead-letter-routing-key", "lisi");
channel.queueDeclare(NORMAL_QUEUE, false, false, false, map);
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String mes = new String(message.getBody(), "UTF-8");
System.out.println("C1接收到的消息:" + mes);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息被中断");
};
channel.basicConsume(NORMAL_QUEUE, true, deliverCallback, cancelCallback);
channel.basicConsume(DEAD_QUEUE, true, deliverCallback, cancelCallback);
}
}
消费者2:
public class Consumer2 {
private static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
System.out.println("C2接收到的消息:" + message);
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息被中断");
};
channel.basicConsume(DEAD_QUEUE, true, deliverCallback, cancelCallback);
}
}
操作流程:
(1)先运行消费者1,声明 普通交换机、普通队列、死信交换机、死信队列
(2)然后关闭消费者1,运行生产者,发送消息
生产者控制台:
生产者发送消息:消息0
生产者发送消息:消息1
生产者发送消息:消息2
生产者发送消息:消息3
生产者发送消息:消息4
生产者发送消息:消息5
生产者发送消息:消息6
生产者发送消息:消息7
生产者发送消息:消息8
(3)然后运行消费者2,接收消息
消费者2控制台:
C2接收到的消息:消息0
C2接收到的消息:消息1
C2接收到的消息:消息2
C2接收到的消息:消息3
C2接收到的消息:消息4
C2接收到的消息:消息5
C2接收到的消息:消息6
C2接收到的消息:消息7
C2接收到的消息:消息8
C2接收到的消息:消息9
4、队列达到最大长度:
C1消费者添加代码:
map.put(“x-max-length”, 6);
5、消息被拒绝:
public class Consumer1 {
private static final String NORMAL_EXCHANGE = "normal_exchange";
private static final String NORMAL_QUEUE = "normal_queue";
private static final String DEAD_EXCHANGE = "dead_exchange";
private static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
channel.exchangeDeclare(NORMAL_EXCHANGE, "direct");
channel.exchangeDeclare(DEAD_EXCHANGE, "direct");
Map<String, Object> map = new HashMap<>();
map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
map.put("x-dead-letter-routing-key", "lisi");
channel.queueDeclare(NORMAL_QUEUE, false, false, false, map);
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String mes = new String(message.getBody(), "UTF-8");
if (mes.equals("消息6")) {
System.out.println("C1拒绝接收的消息:" + mes);
channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
} else {
System.out.println("C1接收到的消息:" + mes);
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息被中断");
};
channel.basicConsume(NORMAL_QUEUE, false, deliverCallback, cancelCallback);
channel.basicConsume(DEAD_QUEUE, true, deliverCallback, cancelCallback);
}
}
操作步骤:
先打开C1、C2消费者等待接收消息,然后打开生产者
生产者控制台:
生产者发送消息:消息0
生产者发送消息:消息1
生产者发送消息:消息2
生产者发送消息:消息3
生产者发送消息:消息4
生产者发送消息:消息5
生产者发送消息:消息6
生产者发送消息:消息7
生产者发送消息:消息8
生产者发送消息:消息9
C1消费者控制台:
C1接收到的消息:消息0
C1接收到的消息:消息1
C1接收到的消息:消息2
C1接收到的消息:消息3
C1接收到的消息:消息4
C1接收到的消息:消息5
C1拒绝接收的消息:消息6
C1接收到的消息:消息7
C1接收到的消息:消息8
C1接收到的消息:消息9
C2消费者控制台:
C2接收到的消息:消息6
七、Springboot整合RabbitMQ:
pom.xml
org.springframework.boot
spring-boot-starter-web
2.4.6
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-amqp
2.2.2.RELEASE
org.springframework.boot
spring-boot-starter
2.4.6
com.alibaba
fastjson
1.2.76
主启动类;
@SpringBootApplication
public class RabbitApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitApplication.class, args);
}
}
八、延迟队列:
优化版:添加QC队列
声明队列、交换机的配置类
@Configuration
public class RabbitmqConfig {
public static final String A_QUEUE = "aQueue";
public static final String B_QUEUE = "bQueue";
public static final String C_QUEUE = "cQueue";
public static final String D_QUEUE = "dQueue";
public static final String X_EXCHANGE = "xExchange";
public static final String Y_EXCHANGE = "yExchange";
@Bean("aQueue")
public Queue aQueue() {
HashMap<String, Object> map = new HashMap<>(3);
map.put("x-dead-letter-exchange", Y_EXCHANGE);
map.put("x-dead-letter-routing-key", "YD");
map.put("x-message-ttl", 10000);
return QueueBuilder.durable(A_QUEUE).withArguments(map).build();
}
@Bean("bQueue")
public Queue bQueue() {
HashMap<String, Object> map = new HashMap<>(3);
map.put("x-dead-letter-exchange", Y_EXCHANGE);
map.put("x-dead-letter-routing-key", "YD");
map.put("x-message-ttl", 40000);
return QueueBuilder.durable(B_QUEUE).withArguments(map).build();
}
@Bean("cQueue")
public Queue cQueue() {
HashMap<String, Object> map = new HashMap<>(3);
map.put("x-dead-letter-exchange", Y_EXCHANGE);
map.put("x-dead-letter-routing-key", "YD");
return QueueBuilder.durable(C_QUEUE).withArguments(map).build();
}
@Bean("dQueue")
public Queue dQueue() {
return QueueBuilder.durable(D_QUEUE).build();
}
@Bean("xExchange")
public DirectExchange xExchange() {
return new DirectExchange(X_EXCHANGE);
}
@Bean("yExchange")
public DirectExchange yExchange() {
return new DirectExchange(Y_EXCHANGE);
}
@Bean
public Binding aQueueBindX(@Qualifier("aQueue") Queue aQueue, @Qualifier("xExchange") DirectExchange xExchange) {
return BindingBuilder.bind(aQueue).to(xExchange).with("XA");
}
@Bean
public Binding bQueueBindX(@Qualifier("bQueue") Queue bQueue, @Qualifier("xExchange") DirectExchange xExchange) {
return BindingBuilder.bind(bQueue).to(xExchange).with("XB");
}
@Bean
public Binding cQueueBindX(@Qualifier("cQueue") Queue cQueue, @Qualifier("xExchange") DirectExchange xExchange) {
return BindingBuilder.bind(cQueue).to(xExchange).with("XC");
}
@Bean
public Binding dQueueBindY(@Qualifier("dQueue") Queue dQueue, @Qualifier("yExchange") DirectExchange yExchange) {
return BindingBuilder.bind(dQueue).to(yExchange).with("YD");
}
}
消息生产者:
@RestController
@RequestMapping(“/ttl”)
public class SendMsgController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/sendMsg/{message}")
public void sendMsg(@PathVariable("message") String message) {
System.out.println("当前时间:" + new Date().toString() +",发送的消息是:" + message);
rabbitTemplate.convertAndSend("xExchange", "XA", "消息来自ttl为10秒的队列:" + message);
rabbitTemplate.convertAndSend("xExchange", "XB", "消息来自ttl为40秒的队列:" + message);
}
@GetMapping("/sendMsg/{message}/{ttl}")
public void sendMsg(@PathVariable("message") String message, @PathVariable("ttl") String ttl) {
System.out.println("当前时间:" + new Date().toString() + ",发送的消息是:" + message);
rabbitTemplate.convertAndSend("xExchange", "XC", "消息来自队列C:" + message,
msg -> {
msg.getMessageProperties().setExpiration(ttl);
return msg;
});
}
}
消息消费者;
@Component
public class MsgConsumer {
@RabbitListener(queues = “dQueue”)
public void deliverMsg(Message message, Channel channel) {
String s = new String(message.getBody());
System.out.println(“当前时间:” + new Date().toString() + “,接收到的队列消息—>” + s);
}
}
九、发布确认高级篇:
消息发布者发布消息之后,在消息队列服务器遇到中断,消息会丢失。
消息队列服务器中断的情形有两种:
(1)在交换机环节中断
(2)在队列环节中断
代码示例:
application.properties
开启发布确认回调方法
spring.rabbitmq.publisher-confirm-type=correlated
开启发布返回回调方法
spring.rabbitmq.publisher-returns=true
@Configuration
public class ConfirmConfig {
private static final String CONFIRM_EXCHANGE_NAME = “confirm_exchange”;
private static final String CONFIRM_QUEUE_NAME = “confirm_queue”;
private static final String ROUTING_KEY_NAME = “key1”;
@Bean("confirmExchange")
public DirectExchange confirmExchange() {
return new DirectExchange(CONFIRM_EXCHANGE_NAME);
}
@Bean("confirmQueue")
public Queue confirmQueue() {
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}
@Bean
public Binding queueBindExchange(@Qualifier("confirmQueue") Queue confirmQueue,
@Qualifier("confirmExchange") DirectExchange confirmExchange) {
return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(ROUTING_KEY_NAME);
}
}
@RestController
@RequestMapping(“/confirm”)
public class ConfirmController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/sendMsg/{message}")
public void sendMsg(@PathVariable("message") String message) {
// Ⅰ、正确的发送消息
CorrelationData cor = new CorrelationData(“10001”);
System.out.println(“发送的消息是:” + message + “001”);
rabbitTemplate.convertAndSend(“confirm_exchange”, “key1”, “消息:” + message + “001”, cor);
// Ⅱ、交换机错误
CorrelationData cor2 = new CorrelationData(“10002”);
System.out.println(“发送的消息是:” + message + “002”);
rabbitTemplate.convertAndSend(“confirm_exchange123”, “key1”, “消息:” + message, cor2);
// Ⅲ、队列错误
CorrelationData cor3 = new CorrelationData(“10003”);
System.out.println(“发送的消息是:” + message + “003”);
rabbitTemplate.convertAndSend(“confirm_exchange”, “key3”, “消息:” + message + “003”, cor3);
}
}
@Component
public class ConfirmConsumer {
@RabbitListener(queues = "confirm_queue")
public void deliverMsg2(Message message, Channel channel) {
String s = new String(message.getBody());
System.out.println("接收到的队列消息--->" + s);
}
}
@Component
public class ConfirmCallback implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
private void init() {
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnsCallback(this);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String id = correlationData != null ? correlationData.getId() : "";
if (ack) {
System.out.println("已经接收id为" + id + "的消息");
} else {
System.out.println("没有接收到id为" + id + "的消息,原因为" + cause);
}
}
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
String message = new String(returnedMessage.getMessage().getBody());
String exchange = returnedMessage.getExchange();
String replyText = returnedMessage.getReplyText();
String routingKey = returnedMessage.getRoutingKey();
System.out.println("消息" + message + "被交换机" + exchange + "退回,原因是" + replyText + ",路由key是" + routingKey);
}
}
浏览器访问: http://localhost:8080/confirm/sendMsg/chrome
ConfirmController 只放开 Ⅰ ,控制台显示
发送的消息是:chrome001
接收到的队列消息—>消息:chrome001
已经接收id为10001的消息
ConfirmController 只放开 Ⅱ ,控制台显示
发送的消息是:chrome002
2022-06-06 19:34:47.360 ERROR 18088 — [.168.1.100:5672] o.s.a.r.c.CachingConnectionFactory : Shutdown Signal: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange ‘confirm_exchange123’ in vhost ‘/’, class-id=60, method-id=40)
没有接收到id为10002的消息,原因为channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange ‘confirm_exchange123’ in vhost ‘/’, class-id=60, method-id=40)
ConfirmController 只放开 Ⅲ,控制台显示
发送的消息是:chrome003
消息消息:chrome003被交换机confirm_exchange退回,原因是NO_ROUTE,路由key是key3
已经接收id为10003的消息
备份交换机:
代码:
ConfirmConfig :添加备份交换机、备份队列、报警队列,绑定队列和交换机
@Configuration
public class ConfirmConfig {
private static final String CONFIRM_EXCHANGE_NAME = “confirm_exchange”;
private static final String CONFIRM_QUEUE_NAME = “confirm_queue”;
private static final String ROUTING_KEY_NAME = “key1”;
private static final String BACKUP_EXCHANGE_NAME = “backup_exchange”;
private static final String BACKUP_QUEUE_NAME = “backup_queue”;
private static final String WARNING_QUEUE_NAME = “warning_queue”;
@Bean("confirmExchange")
public DirectExchange confirmExchange() {
return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME)
.durable(true).alternate(BACKUP_EXCHANGE_NAME).build();
}
@Bean("confirmQueue")
public Queue confirmQueue() {
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}
@Bean
public Binding confirmQueueBindConfirmExchange(@Qualifier("confirmQueue") Queue confirmQueue, @Qualifier("confirmExchange") DirectExchange confirmExchange) {
return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(ROUTING_KEY_NAME);
}
@Bean("backupExchange")
public FanoutExchange backupExchange() {
return new FanoutExchange(BACKUP_EXCHANGE_NAME);
}
@Bean("backupQueue")
public Queue backupQueue() {
return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();
}
@Bean("warningQueue")
public Queue warningQueue() {
return QueueBuilder.durable(WARNING_QUEUE_NAME).build();
}
@Bean
public Binding backupQueueBindBackupExchange(@Qualifier("backupQueue") Queue backupQueue,
@Qualifier("backupExchange") FanoutExchange backupExchange) {
return BindingBuilder.bind(backupQueue).to(backupExchange);
}
@Bean
public Binding warningQueueBindBackupExchange(@Qualifier("warningQueue") Queue warningQueue,
@Qualifier("backupExchange") FanoutExchange backupExchange) {
return BindingBuilder.bind(warningQueue).to(backupExchange);
}
}
ConfirmController 放开Ⅲ。
MsgConsumer: 添加备份消费者和报警消费者
@Component
public class MsgConsumer {
@RabbitListener(queues = "dQueue")
public void deliverMsg(Message message, Channel channel) {
String s = new String(message.getBody());
System.out.println("当前时间:" + new Date().toString() + ",接收到的队列消息--->" + s);
}
@RabbitListener(queues = "confirm_queue")
public void deliverMsg2(Message message, Channel channel) {
String s = new String(message.getBody());
System.out.println("接收到的队列消息--->" + s);
}
@RabbitListener(queues = "backup_queue")
public void deliverMsg3(Message message, Channel channel) {
String s = new String(message.getBody());
System.out.println("备份队列接收到的消息--->" + s);
}
@RabbitListener(queues = "warning_queue")
public void deliverMsg4(Message message, Channel channel) {
String s = new String(message.getBody());
System.out.println("WARNING!WARNING!WARNING!报警队列接收到的消息--->" + s);
}
}
浏览器访问: http://localhost:8080/confirm/sendMsg/chrome
控制台显示:
发送的消息是:chrome003
已经接收id为10003的消息
WARNING!WARNING!WARNING!报警队列接收到的消息—>消息:chrome003
备份队列接收到的消息—>消息:chrome003
配置了备份交换机,则不走消息回退,而是走备份交换机。
表明:备份交换机比消息回退优先级高。
十、其他知识点
1、幂等性
对一个操作发起多次请求。
解决思路:使用全局ID或者唯一标识
解决办法:
(1)唯一ID+指纹锁机制
(2)redis的原子性
利用redis执行setx命令,天然具有幂等性,从而实现不重复消费
2、优先级队列:
生产者发送消息时,给消息设置优先级数,在队列中按照优先级对消息重新排序,优先级高的先发送到消费者,优先级低的后发送。
public class Producer {
private static final String QUEUE_NAME = “priority_queue”;
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
HashMap<String, Object> map = new HashMap<>();
map.put("x-max-priority", 10);
channel.queueDeclare(QUEUE_NAME, false, false, false, map);
for (int i = 0; i < 10; i++) {
String msg = "hello" + i;
if (i % 3 == 0) {
AMQP.BasicProperties build = new AMQP.BasicProperties().builder().priority(6).build();
channel.basicPublish("", QUEUE_NAME, build, msg.getBytes());
} else if (i == 8) {
AMQP.BasicProperties build = new AMQP.BasicProperties().builder().priority(3).build();
channel.basicPublish("", QUEUE_NAME, build, msg.getBytes());
} else {
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
}
}
System.out.println("消息发送完毕");
}
}
public class Consumer {
private static final String QUEUE_NAME = “priority_queue”;
public static void main(String[] args) throws Exception {
Channel channel = ConnectUtils.connect();
DeliverCallback d = (consumerTag, message) -> {
System.out.println(new String(message.getBody()));
};
CancelCallback c = consumerTag -> {
System.out.println("消息被中断");
};
channel.basicConsume(QUEUE_NAME, true, d, c);
}
}
控制台显示:
hello0
hello3
hello6
hello9
hello8
hello1
hello2
hello4
hello5
hello7
3、惰性队列:
队列的模式分为默认(default)和惰性(lazy)
默认队列消息保存在内存中,惰性队列消息保存在磁盘中。
在声明队列时 ,添加 “x-queue-mode” 属性,设置值为 “lazy”