<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-stream-rabbitmq-provider8801</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Broker:接收和分发消息的应用,RabbitMQ Server 就是Message Broker
Virtual host:出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace概念。当多个不同的用户使用同一个RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等。
package com.atguigu.springcloud.service.impl;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.HashMap;
import java.util.Map;
public class Producer {
public static final String queue_name="hello";
public static void main(String[] args) throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setUsername("guest");
factory.setPassword("guest");
//创建连接
Connection connection = factory.newConnection();
//获取信道
Channel channel = connection.createChannel();
/**
* 1.队列名称
* 2.队列里的消息是否持久化(硬盘)默认情况消息存在内存中
* 3.该队列是否只供一个消费者进行消费,是否进行消息共享,true:可以多个消费者消费
* 4.是否自动删除,最后一个消费断开链接以后,该队列是否自动删除 true:自动删除
* 5.其他参数(延迟或死信)
*/
Map<String,Object> arguments = new HashMap();
arguments.put("x-max-priority",10);//官方允许是0-255之间,此处设置10,不要设置过大,浪费cpu与内存
channel.queueDeclare(queue_name,true,false,false,arguments);
//String message = "hellow world";
for (int i = 1; i <11 ; i++) {
String message = "info"+i;
if(i==5){
//设置消息优先级为5
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().priority(5).build();
channel.basicPublish("",queue_name,properties,message.getBytes());
}else{
channel.basicPublish("",queue_name,null,message.getBytes());
}
}
/**
* 发送消息
* 1.发送到那个交换机
* 2.路由的key值时那个,本次是队列名称
* 3.其他参数
* 4.发送消息的消息体
*/
//channel.basicPublish("",queue_name,null,message.getBytes());
System.out.println("消息发送完毕");
}
}
package com.atguigu.springcloud.service.impl;
import com.rabbitmq.client.*;
public class Consumer {
public static final String queue_name="hello";
//接收消息
public static void main(String[] args) throws Exception{
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
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.消费成功之后是否要自动应答 true:自动应答
* 3.消费者未成功消费的回调
* 4.消费者取消消费的回调
*/
channel.basicConsume(queue_name,true,deliverCallback,cancelCallback);
/**
* A.Channel.basicAck(用于肯定确认)
* RabbitMQ已经知道该消息并且成功的处理消息,可以将其丢弃了
* B.Channel.basicNack(用于否定确认)
* C.Channel.basicReject(用于否定确认)
* 与Channel.basicNack相比少一个参数
* 不处理消息直接拒绝,可以将其丢弃了
*/
}
}
工作队列又称任务队列,主要思想是避免立即执行资源密集型任务,而不得不等待它完场。相反我们安排在任务之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当有多个工作线程时,这些工作线程讲义气处理这些任务
这个案例中我们会启动两个工作线程,一个消息发送线程,我们来看看他们两个工作线程是如何工作的。
package com.atguigu.springcloud.service.impl;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class RabbitMqUtils {
public static Channel getChannel() throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setUsername("guest");
factory.setPassword("guest");
//创建连接
Connection connection = factory.newConnection();
//获取信道
Channel channel = connection.createChannel();
return channel;
}
}
先启动生产者产生hellow队列
package com.atguigu.springcloud.two;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
/**
* 工作线程相当于一个消费者
*/
public class Worker01 {
public static final String queue_name="hello";
//接收消息
public static void main(String[] args) throws Exception{
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("C1等待接收消息.....");
/**
* 1.队列名称
* 2.队列里的消息是否持久化(硬盘)默认情况消息存在内存中
* 3.该队列是否只供一个消费者进行消费,是否进行消息共享,true:可以多个消费者消费
* 4.是否自动删除,最后一个消费端开链接以后,改队列是否自动删除 true:自动删除
* 5.其他参数(延迟或死信)
*/
channel.basicConsume(queue_name,true,deliverCallback,cancelCallback);
}
}
package com.atguigu.springcloud.two;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.util.Scanner;
/**
* 生产者发送大量的消息
*/
public class task01 {
public static final String queue_name="hello";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
//队列的声名
/**
* 1.队列名称
* 2.队列里的消息是否持久化(硬盘)默认情况消息存在内存中
* 3.该队列是否只供一个消费者进行消费,是否进行消息共享,true:可以多个消费者消费
* 4.是否自动删除,最后一个消费端开链接以后,改队列是否自动删除 true:自动删除
* 5.其他参数(延迟或死信)
*/
channel.queueDeclare(queue_name,false,false,false,null);
//控制台中输入信息
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
String message = scanner.next();
channel.basicPublish("",queue_name,null,message.getBytes());
System.out.println("消息发送成功");
}
}
}
消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成了部分突然它挂掉了,会发生什么情况。RabbitMQ一旦向消费者传递了一条消息,便立即将该消息标记为删除。在这种情况下,突然有个消费者挂掉了,我们将丢失正在处理的消息。以及后续发给该消费者的消息,因为它无法接收到。
为了保证消息在发送过程中不丢失,rabbitmq引入消息应答机制,消息应答就是消费者在收到消息并处理该消息后,告诉rabbitmq它已经处理了,rabbitmq可以把改消息删除。
手动应答的好处是可以批量应答且减少网络拥堵
channel.basicAck(deliveryTag,true);
multiple的true和false代表不通意思
true代表批量应答channel上未应答的消息
比如说channel上有传送tag的消息5,6,7,8 当前tag是8那么此时5-8的这些还未应答的消息都会被确认消息收到消息应答false同上面相比只会应答tag=8的消息5,6,7这三个消息依然不会被确认收到消息应答
如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或者TCP连接丢失),导致消息未发送ACK确认,RabbitMQ将了解到消息未完全处理,并将对其重新排队。如果此时消费者可以处理,它将很快将其重新分发给另一个消费者。这样,即使某个消费者偶尔死亡,也可以确保不会丢失任何消息。
package com.atguigu.springcloud.three;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import java.util.Scanner;
/**
* 消息在手动应答时不丢失,放回队列中重新消费
*/
public class Task2 {
public static final String task_queue_name="ack_queue";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
boolean durable= true;//需要让queue持久化
channel.queueDeclare(task_queue_name,durable,false,false,null);
//从控制台中输入信息
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
String message = scanner.next();
//MessageProperties.PERSISTENT_TEXT_PLAIN :持久化消息到硬盘
channel.basicPublish("",task_queue_name, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes("UTF-8"));
System.out.println("生产者发送消息"+message);
}
}
}
package com.atguigu.springcloud.three;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.atguigu.springcloud.util.SleepUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
/**
* 消息在手动应答时不丢失,放回队列中重新消费
*/
public class Work03 {
public static final String task_queue_name="ack_queue";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
System.out.println("C1等待接收消息处理时间短");
DeliverCallback deliverCallback = (consumerTag,message) ->{
SleepUtils.sleep(1);
System.out.println("接收到的消息:"+new String(message.getBody()));
//手动应答
/**
* 1.消息的标记 tag
* 2.是否批量应答 false:不批量应答
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
//设置能者多劳模式
//int prefetchCount = 1;
//比1大的都是预取值 与消费者2成正比
int prefetchCount = 2;
channel.basicQos(prefetchCount);
//采用手动应答
boolean autoAck = false;
channel.basicConsume(task_queue_name,autoAck,deliverCallback,(consumerTag ->{
System.out.println(consumerTag+"消费者取消消费接口逻辑");
}));
}
}
package com.atguigu.springcloud.three;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.atguigu.springcloud.util.SleepUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
/**
* 消息在手动应答时不丢失,放回队列中重新消费
*/
public class Work04 {
public static final String task_queue_name="ack_queue";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
System.out.println("C2等待接收消息处理时间短");
DeliverCallback deliverCallback = (consumerTag,message) ->{
SleepUtils.sleep(30);
System.out.println("接收到的消息:"+new String(message.getBody()));
//手动应答
/**
* 1.消息的标记 tag
* 2.是否批量应答 false:不批量应答
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
//设置能者多劳模式
//int prefetchCount = 1;
//比1大的都是预取值 与消费者1成正比
int prefetchCount = 5;
channel.basicQos(prefetchCount);
//采用手动应答
boolean autoAck = false;
channel.basicConsume(task_queue_name,autoAck,deliverCallback,(consumerTag ->{
System.out.println(consumerTag+"消费者取消消费接口逻辑");
}));
}
}
需要把durable参数设置为持久化
boolean durable= true;//需要让queue持久化
channel.queueDeclare(task_queue_name,durable,false,false,null);
channel.basicQos(1);//不设置为1就是公平分发
如果有7条消息消费者1会先执行2条,消费者2会先执行5条,后面的会按能者多劳模式分发
消费者1中的设置
//设置能者多劳模式
//int prefetchCount = 1;
//比1大的都是预取值 与消费者2成正比
int prefetchCount = 2;
channel.basicQos(prefetchCount);
消费者2中的设置
//设置能者多劳模式
//int prefetchCount = 1;
//比1大的都是预取值 与消费者1成正比
int prefetchCount = 5;
channel.basicQos(prefetchCount);
发布确认原理
单个,批量,异步确认发布消息
package com.atguigu.springcloud.four;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmCallback;
import java.util.UUID;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
/**
* 发布确认模式
* 使用的时间,比较那种方式是最好的
* 1.单个确认
* 2.批量确认
* 3.异步批量确认
*/
public class ConfirmMessage {
//批量发消息的个数
public static final int message_count = 1000;
public static void main(String[] args) throws Exception{
//1.单个确认
ConfirmMessage.publishMessageIndividually();//耗时641ms
//2.批量确认
//ConfirmMessage.publishMessageBathch();//耗时141ms
//3.异步确认
// ConfirmMessage.publishMessageIndividually();//耗时62ms
}
//单个确认
public static void publishMessageIndividually ()throws Exception{
Channel channel = RabbitMqUtils.getChannel();
//队列的声名
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName,true,false,false,null);
//开启确认发布
channel.confirmSelect();
//开始时间
long begin = System.currentTimeMillis();
//批量发消息
for (int i = 0; i < message_count; i++) {
String message = i+"";
channel.basicPublish("",queueName,null,message.getBytes());
//单个消息马上进行发布确认
boolean flag = channel.waitForConfirms();
if(flag){
System.out.println("消息发送成功:"+message);
}
}
long end = System.currentTimeMillis();
System.out.println("发布"+message_count+"条单独确认消息,耗时"+(end-begin)+"ms");
}
//批量发布确认
public static void publishMessageBathch() throws Exception{
Channel channel = RabbitMqUtils.getChannel();
//队列的声名
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName,true,false,false,null);
//开启确认发布
channel.confirmSelect();
//开始时间
long begin = System.currentTimeMillis();
//批量确认消息大小
int batchSize = 100;
//批量发消息 批量发布确认
for (int i = 0; i < message_count; i++) {
String message = i+"";
channel.basicPublish("",queueName,null,message.getBytes());
//判断达到100条消息的时候批量确认一次
if(i%batchSize==0){
//发布确认
channel.waitForConfirms();
}
}
long end = System.currentTimeMillis();
System.out.println("发布"+message_count+"条批量确认消息,耗时"+(end-begin)+"ms");
}
//异步发布确认
public static void publishMessageAsync() throws Exception{
Channel channel = RabbitMqUtils.getChannel();
//队列的声名
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName,true,false,false,null);
//开启确认发布
channel.confirmSelect();
/**
* 线程安全有序的一个哈希表,适用于高并发的情况下
* 1.轻松的将需要与消息进行关联
* 2.轻松的批量删除条目,只要给到序号
* 3.支持高并发
*/
ConcurrentSkipListMap<Long,String> coutstandingConfirms = new ConcurrentSkipListMap<>();
//开始时间
long begin = System.currentTimeMillis();
//准备消息的监听器,监听那些消息成功了,那些消息失败了
//消息确认成功回调函数
ConfirmCallback ackCallback = (deliveryTag,multiple) ->{
if(multiple){
//2.删除掉已经确认的消息,剩下的就是未确认的消息
ConcurrentNavigableMap<Long, String> confirmed = coutstandingConfirms.headMap(deliveryTag);
confirmed.clear();
}else{
coutstandingConfirms.remove(deliveryTag);
}
System.out.println("确认的消息:"+deliveryTag);
};
//消息确认失败回调函数
/**
* 1.消息的标识
* 2.是否为批量确认
*/
ConfirmCallback nackCallback = (deliveryTag,multiple) ->{
System.out.println("未确认的消息:"+deliveryTag );
};
/**
* 1.监听那些消息成功
* 2.监听那些消息失败
*/
channel.addConfirmListener(ackCallback,nackCallback);//异步通知
//批量发送消息
for (int i = 0; i < message_count; i++) {
String message = "消息"+i;
channel.basicPublish("",queueName,null,message.getBytes());
//1.此处记录下所有要发送的消息,消息的总和
coutstandingConfirms.put(channel.getNextPublishSeqNo(),message);
}
long end = System.currentTimeMillis();
System.out.println("发布"+message_count+"条异步确认消息,耗时"+(end-begin)+"ms");
}
}
单独发布消息:同步等待确认,简单,但吞吐量有限。
批量发布消息:简单合理的吞吐量,一旦出现问题很难判断出是哪条消息出现了问题。
异步处理:最佳性能和资源使用,在出现错误的情况下可以很好的控制,但是实现起来比较复杂。
RabbitMq消息传递模型的核心思想是:生产者生产的消息从不会直接发送到队列。实际上,通常生产者甚至不知道这些消息传递到了那些队列中。
相反,生产者只能讲消息发送到交换机(exchange),交换机工作内容非常简单,一方面它接收来自生产者的消息,另一方面将他们推入队列。交换机必须确切知道如何处理收到的消息。是应该把这些消息放到特定队列还是把他们推到许多队列还是说该丢弃它们。这就由交换机的类型来决定。
直接/路由(direct)
主题 (topic)
标题 (headers)
扇出/发布订阅 (fanout)
exhange和queue之间的桥梁,它告诉我们exchange和那个队列进行了绑定关系。比如说下面这张图告诉我们就是x与Q1和Q2进行了绑定
手动绑定
同一个交换机下面的队列通过RoutingKey区别
生产者->交换机->绑定关系一样的队列->消费者1 消费者2
Logs和临时队列的绑定关系如下图
生产者
package com.atguigu.springcloud.five;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.connection.RabbitUtils;
import java.util.Scanner;
/**
*
*/
public class EmitLog {
private static final String EXCHANGE_NAME= "logs";
public static void main(String[] args) throws Exception{
try (Channel channel = RabbitMqUtils.getChannel()){
/**
* 声名一个exchange
* 1.exchange的名称
* 2.exchange的类型
*/
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
Scanner sc = new Scanner(System.in);
System.out.println("请输入信息");
while (sc.hasNext()){
String message= sc.nextLine();
channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("UTF-8"));
System.out.println("生产者发出消息:"+message);
}
}
}
}
消费者1
package com.atguigu.springcloud.five;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
/**
* 消息的接收
*/
public class ReceiveLogs01 {
public static final String EXHCANGE_NAME = "logs";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
//声名一个交换机 扇出类型
channel.exchangeDeclare(EXHCANGE_NAME,"fanout");
//声名一个队列 临时队列
/**
* 生成一个临时队列,队列名称是随机的
* 当消费者断开与队列的链接,队列就删除
*/
String queueName = channel.queueDeclare().getQueue();
/**
* 绑定交换机与队列
*/
channel.queueBind(queueName,EXHCANGE_NAME,"");
System.out.println("等待接收消息,把接收到的消息打印到屏幕上.....");
//接收消息
DeliverCallback deliverCallback = (consumerTag,message) ->{
System.out.println("控制台打印接收到的消息:"+ new String(message.getBody(),"UTF-8"));
};
//消费者取消消息时回调接口
channel.basicConsume(queueName,true,deliverCallback,consumerTag ->{});
}
}
消费者2
package com.atguigu.springcloud.five;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
/**
* 消息的接收
*/
public class ReceiveLogs02 {
public static final String EXHCANGE_NAME = "logs";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
//声名一个交换机
channel.exchangeDeclare(EXHCANGE_NAME,"fanout");
//声名一个队列 临时队列
/**
* 生成一个临时队列,队列名称是随机的
* 当消费者断开与队列的链接,队列就删除
*/
String queueName = channel.queueDeclare().getQueue();
/**
* 绑定交换机与队列
*/
channel.queueBind(queueName,EXHCANGE_NAME,"");
System.out.println("等待接收消息,把接收到的消息打印到屏幕上.....");
//接收消息
DeliverCallback deliverCallback = (consumerTag,message) ->{
System.out.println("ReceiveLogs02控制台打印接收到的消息:"+ new String(message.getBody(),"UTF-8"));
};
//消费者取消消息时回调接口
channel.basicConsume(queueName,true,deliverCallback,consumerTag ->{});
}
}
生产者
package com.atguigu.springcloud.six;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.util.Scanner;
/**
*/
public class DirectLogs {
private static final String EXCHANGE_NAME= "direct_logs";
public static void main(String[] args) throws Exception{
try (Channel channel = RabbitMqUtils.getChannel()){
Scanner sc = new Scanner(System.in);
System.out.println("请输入信息");
while (sc.hasNext()){
String message= sc.nextLine();
// channel.basicPublish(EXCHANGE_NAME,"info",null,message.getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME,"error",null,message.getBytes("UTF-8"));
System.out.println("生产者发出消息:"+message);
}
}
}
}
消费者1
package com.atguigu.springcloud.six;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
/**
*/
public class ReceiveLogsDirect01 {
public static final String EXCHANGE_NAME="direct_logs";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
//声名一个交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//声名一个队列名字 不持久化 不共享 不自动删除
channel.queueDeclare("console",false,false,false,null);
channel.queueBind("console",EXCHANGE_NAME,"info");
channel.queueBind("console",EXCHANGE_NAME,"warning");
//接收消息
DeliverCallback deliverCallback = (consumerTag, message) ->{
System.out.println("ReceiveLogsDirect01控制台打印接收到的消息:"+ new String(message.getBody(),"UTF-8"));
};
//消费者取消消息时回调接口
channel.basicConsume("console",true,deliverCallback,consumerTag ->{});
}
}
消费者2
package com.atguigu.springcloud.six;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
/**
*/
public class ReceiveLogsDirect02 {
public static final String EXCHANGE_NAME="direct_logs";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
//声名一个交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//声名一个队列名字 不持久化 不共享 不自动删除
channel.queueDeclare("disk",false,false,false,null);
channel.queueBind("disk",EXCHANGE_NAME,"error");
//接收消息
DeliverCallback deliverCallback = (consumerTag, message) ->{
System.out.println("ReceiveLogsDirect02控制台打印接收到的消息:"+ new String(message.getBody(),"UTF-8"));
};
//消费者取消消息时回调接口
channel.basicConsume("disk",true,deliverCallback,consumerTag ->{});
}
}
之前类型的问题
topic的要求
*(星号)可以代替一个单词
#(井号)可以替代零个或多个单词
下图绑定关系如下
Q1–>绑定的是 中间带orange带3个单词的字符串(.orange.)
Q2–>绑定的是 最后一个单词是rabbit的3个单词(..rabbit)
第一个单词是lazy的多个单词(lazy.#)
当队列绑定关系是下列这种情况试需要引起注意
当一个队列绑定的是#,那么这个队列将接收所有数据,有点像fanout(扇出)了
如果队列绑定键中没有#和*出现,那么该队列绑定类型就是direct(直接)了
package com.atguigu.springcloud.seven;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.util.HashMap;
import java.util.Map;
/**
* 生产者
*/
public class EmitLogTopic {
public static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
/**
* 绑定关系如下
* Q1-->绑定的是
* 中间带orange带3个单词的字符串(*.orange.*)
* Q2-->绑定的是
* 最后一个单词是rabbit的3个单词(*.*.rabblt)
* 第一个单词是lazy的多个单词(lazy.#)
*/
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 routingKey = bindingKeyEntry.getKey();
String message = bindingKeyEntry.getValue();
channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes("UTF-8"));
System.out.println("生产者发出消息:"+message);
}
}
}
消费者1
package com.atguigu.springcloud.seven;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
/**
* 声名主题交换机及相关队列
*/
public class ReceiveLogsTopic01 {
public static final String EXCHANGE_NAME = "topic_logs";
//接收消息
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
//声名交换机
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
//声名队列
String queuename="Q1";
channel.queueDeclare(queuename,false,false,false,null);
channel.queueBind(queuename,EXCHANGE_NAME,"*.orange.*");
System.out.println("等待接收消息.....");
DeliverCallback deliverCallback = (consumerTag,message) ->{
System.out.println(new String(message.getBody(),"UTF-8"));
System.out.println("接收队列:"+queuename+"绑定键:"+message.getEnvelope().getRoutingKey());
};
//接收消息
channel.basicConsume(queuename,true,deliverCallback,consumerTag -> {});
}
}
消费者2
package com.atguigu.springcloud.seven;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
/**
* 声名主题交换机及相关队列
*/
public class ReceiveLogsTopic02 {
public static final String EXCHANGE_NAME = "topic_logs";
//接收消息
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
//声名交换机
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
//声名队列
String queuename="Q2";
channel.queueDeclare(queuename,false,false,false,null);
channel.queueBind(queuename,EXCHANGE_NAME,"*.*.rabbit");
channel.queueBind(queuename,EXCHANGE_NAME,"lazy.#");
System.out.println("ReceiveLogsTopic02等待接收消息.....");
DeliverCallback deliverCallback = (consumerTag,message) ->{
System.out.println(new String(message.getBody(),"UTF-8"));
System.out.println("接收队列:"+queuename+"绑定键:"+message.getEnvelope().getRoutingKey());
};
//接收消息
channel.basicConsume(queuename,true,deliverCallback,consumerTag -> {});
}
}
应用场景:为了保证订单业务的消息数据不丢失,需要使用到RabbitMQde 死信队列机制,当消息消费发生异常时,将消息投入死信队列中。还有比如说:用户在商城下单成功并点击去支付后再指定时间未支付时自动失效
消息TTL过期
队列达到最大长度(队列满了,无法添加数据到mq中)
消息被拒绝(basic.reject或basic.nack)并且requeue=false.
生产者
package com.atguigu.springcloud.eight;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
/**
* 死信队列 生产者代码
*/
public class Producer {
//普通交换机的名称
public static final String NORMAL_EXCHANGE="normal_exchange";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
//死信消息 设置TTL时间 单位是毫秒
//AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
for (int i = 1; i < 11; i++) {
String message="info"+i;
//channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",null,message.getBytes());
}
}
}
消费者1 (绑定与死信队列关系)
package com.atguigu.springcloud.eight;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.util.HashMap;
import java.util.Map;
/**
* 死信队列实战
* 消费者1
*/
public class Consumer01 {
//普通交换机的名称
public static final String NORMAL_EXCHANGE="normal_exchange";
//死信交换机的名称
public static final String DEAD_EXCHANGE="dead_exchange";
//普通队列名称
public static final String NORMAL_QUEUE="normal_queue";
//死信队列名称
public static final String DEAD_QUEUE="dead_queue";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
//声名死信和普通的交换机类型为direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
//声名普通队列
Map<String,Object> arguments = new HashMap();
//过期时间 设置到生产者更灵活
arguments.put("x-message-ttl",10000);
//正常队列设置死信交换机
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//设置死信RoutingKey
arguments.put("x-dead-letter-routing-key","lisi");
//设置正常队列长度的限制
//arguments.put("x-max-length",6);
channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);
//声名死信队列
channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
//绑定普通的交换机与队列
channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");
//绑定死信的交换机与死信的队列
channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");
System.out.println("等待接收消息。。。。。");
DeliverCallback deliverCallback = (consumerTag,message) ->{
String msg = new String(message.getBody(),"UTF-8");
if(msg.equals("info5")){
System.out.println("Consumer01接收的消息时:"+msg+":此消息时被C1拒绝的");
//参数说明 1:消息的标签 2:是否放回原队列(放到了死信)
channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
}else{
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
System.out.println("Consumer01接收的消息时:"+msg);
}
};
//开启手动应答
channel.basicConsume(NORMAL_QUEUE,false,deliverCallback,consumerTag ->{});
}
}
消费者2(死信队列)
package com.atguigu.springcloud.eight;
import com.atguigu.springcloud.service.impl.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.util.HashMap;
import java.util.Map;
/**
* 死信队列实战
* 消费者1
*/
public class Consumer02 {
//死信队列名称
public static final String DEAD_QUEUE="dead_queue";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
System.out.println("等待接收消息。。。。。");
DeliverCallback deliverCallback = (consumerTag,message) ->{
System.out.println("Consumer02接收的消息时:"+new String(message.getBody(),"UTF-8"));
};
channel.basicConsume(DEAD_QUEUE,true,deliverCallback,consumerTag ->{});
}
}
创建两个队列QA和QB,两者队列TTL分别设置为10S和40S,然后创建一个交换机X和死信交换机Y,他们的类型都是direct,创建一个死信队列QD,他们的绑定关系如下:
这种方式每增加一个新的时间需求,就需要新增一个队列。
package com.atguigu.springcloud.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* ttl队列,配置文件类代码
*/
@Configuration
public class TtlQueueConfig {
//普通交换机的名称
public static final String X_EXCHANGE = "X";
//死信交换机的名称
public static final String Y_DEAD_LETTER_EXCHANGE = "Y";
//普通队列的名称
public static final String QUEUE_A = "QA";
public static final String QUEUE_B = "QB";
//死信队列的名称
public static final String DEAD_LETTER_QUEUE = "QD";
//普通队列的名称
public static final String QUEUE_C = "QC";
//声名QC
@Bean("queueC")
public Queue queueC(){
Map<String,Object> arguments = new HashMap(3);
//设置死信交换机
arguments.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
//设置死信RoutingKey
arguments.put("x-dead-letter-routing-key","YD");
//ttl 不写就是适合所有时间的队列
return QueueBuilder.durable(QUEUE_C).withArguments(arguments).build();
}
@Bean
public Binding queueCBindingX(@Qualifier("queueC") Queue queueC,
@Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueC).to(xExchange).with("XC");
}
//声名xExchange 别名
@Bean("xExchange")
public DirectExchange xExchange(){
return new DirectExchange(X_EXCHANGE);
}
//声名yExchange 别名
@Bean("yExchange")
public DirectExchange yExchange(){
return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
}
//声名普通队列 ttl:10s
@Bean("queueA")
public Queue queueA(){
Map<String,Object> arguments = new HashMap(3);
//设置死信交换机
arguments.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
//设置死信RoutingKey
arguments.put("x-dead-letter-routing-key","YD");
//设置TTL
arguments.put("x-message-ttl",10000);
return QueueBuilder.durable(QUEUE_A).withArguments(arguments).build();
}
//声名普通队列 ttl:40s
@Bean("queueB")
public Queue queueB(){
Map<String,Object> arguments = new HashMap(3);
//设置死信交换机
arguments.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
//设置死信RoutingKey
arguments.put("x-dead-letter-routing-key","YD");
//设置TTL
arguments.put("x-message-ttl",40000);
return QueueBuilder.durable(QUEUE_B).withArguments(arguments).build();
}
//死信队列
@Bean("queueD")
public Queue queueD(){
return QueueBuilder.durable(DEAD_LETTER_QUEUE).build();
}
//绑定
@Bean
public Binding queueABindingX(@Qualifier("queueA") Queue queueA,
@Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueA).to(xExchange).with("XA");
}
@Bean
public Binding queueBBindingX(@Qualifier("queueB") Queue queueB,
@Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueB).to(xExchange).with("XB");
}
@Bean
public Binding queueDBindingY(@Qualifier("queueD") Queue queueD,
@Qualifier("yExchange") DirectExchange yExchange){
return BindingBuilder.bind(queueD).to(yExchange).with("YD");
}
}
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.config.DelayedQueueConfig;
import com.atguigu.springcloud.service.IMessageProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
*/
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMessageController {
@Resource
private IMessageProvider messageProvider;
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/sendMessage")
public String sendMessage(){
return messageProvider.send();
}
@GetMapping("sendMsg/{message}")
public void sendMsg(@PathVariable String message){
log.info("当前时间:{},发送一条消息给两个TTL队列:{}",new Date().toString(),message);
rabbitTemplate.convertAndSend("X","XA","消息来自ttl为10s的队列:"+message);
rabbitTemplate.convertAndSend("X","XB","消息来自ttl为40s的队列:"+message);
}
//开始发消息 消息 ttl
@GetMapping("/sendExpirationMsg/{message}/{ttlTime}")
public void sendMsg(@PathVariable String message,@PathVariable String ttlTime){
log.info("当前时间:{},发送一条时长{}毫秒消息给TTL队列:{}",new Date().toString(),ttlTime,message);
rabbitTemplate.convertAndSend("X","XC",message,msg ->{
//发送消息的延时
msg .getMessageProperties().setExpiration(ttlTime);
return msg;
});
}
//开始发送消息,基于插件的消息及延迟的时间
@GetMapping("sendDelayMsg/{message}/{delayTime}")
public void sendMsg(@PathVariable String message,@PathVariable Integer delayTime){
log.info("当前时间:{},发送一条时长{}毫秒信息给延时队列delayed.queue:{}",new Date().toString(),delayTime,message);
rabbitTemplate.convertAndSend(DelayedQueueConfig.DELAYED_EXCHANGE_NAME
,DelayedQueueConfig.DELAYED_ROUTING_KEY,message,msg ->{
//发送消息的时候 延时时长 单位:ms
msg.getMessageProperties().setDelay(delayTime);
return msg;
});
}
}
package com.atguigu.springcloud.consumer;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 队列TTL消费者
*/
@Slf4j
@Component
public class DeadLetterQueueConsumer {
//接收消息
@RabbitListener(queues="QD")
public void receiveD(Message message, Channel channel) throws Exception{
String msg = new String(message.getBody());
log.info("当前时间:{},收到死信队列的消息:{}",new Date().toString(),msg);
}
}
在这里新增了一个队列QC,绑定关系如下,该队列不设置TTL时间
在官网上下载https://www.rabbitmq.com/community-plugins.html下载rabbitmq_delayed_message_exchange插件,然后解压放置到RabbitMq的插件目录。进入rabbitMq的安装目录下的plgins目录,执行下面命令让插件生效,然后重启RabbitMQ
/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
装完插件在交换机会多一个类型
package com.atguigu.springcloud.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DelayedQueueConfig {
//队列
public static final String DELAYED_QUEUE_NAME = "delayed.queue";
//交换机
public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";
//routingKey
public static final String DELAYED_ROUTING_KEY="delayed.routingkey";
@Bean
public Queue delayedQueue(){
return new Queue(DELAYED_QUEUE_NAME);
}
//声名交换机 基于插件
@Bean
public CustomExchange delayedExchange(){
Map<String,Object> arguments = new HashMap();
arguments.put("x-delayed-type","direct");
/**
* 1.交换机名称
* 2.交换机类型
* 3.是否需要持久化
* 4.是否需要自动删除
* 5.其他参数
*/
return new CustomExchange(DELAYED_EXCHANGE_NAME,"x-delayed-message",true,false,arguments);
}
@Bean
public Binding delayedQueueBindingDelayedExchange(
@Qualifier("delayedQueue") Queue delayedQueue,
@Qualifier("delayedExchange") CustomExchange delayedExchange
){
return BindingBuilder.bind(delayedQueue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();
}
}
DelayQueueConsumer
package com.atguigu.springcloud.consumer;
import com.atguigu.springcloud.config.DelayedQueueConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 延时队列消费者 ,基于插件的延时
*/
@Slf4j
@Component
public class DelayQueueConsumer {
//监听消息
@RabbitListener(queues= DelayedQueueConfig.DELAYED_QUEUE_NAME)
public void receiveDelayQueue(Message message){
String msg = new String(message.getBody());
log.info("当前时间:{},收到延时队列的消息:{}",new Date().toString(),msg);
}
}
RabbitMQ特性:消息可靠发送,消息可靠投递。死信队列保障消息至少消费一次以及未被正确处理的消息不会被丢弃。另外通过RabbitMQ集群的特性,可以很好的解决单点故障问题,不会因为单个节点挂掉导致延时队列不可用或者消息丢失。
当然,延时队列还有其他选择,比如利用java的DelayQueue,利用Redis的zset,利用Quart或者利用kafka的时间轮,这些方式各有各的特点,看需要适用的场景
spring:
application:
name: cloud-stream-provider
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
publisher-confirm-type: correlated #开启交换机确认回调
publisher-returns: true #一旦交换机路由不出去消息会回退消息
package com.atguigu.springcloud.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置类 发布确认(高级)
*/
@Configuration
public class ConfirmConfig {
//交换机
public static final String CONFIRM_EXCHANGE_NAME="confirm_exchange";
//队列
public static final String CONFIRM_QUEUE_NAME="confirm_queue";
//RoutingKey
public static final String CONFIRM_ROUTING_KEY="key1";
//备份交换机
public static final String BACKUP_EXCHANGE_NAME = "backup_exchange";
//备份队列
public static final String BACKUP_QUEUE_NAME = "backup_queue";
//报警队列
public static final String WARNING_QUEUE_NAME = "warning_queue";
//声名交换机
@Bean("confirmExchange")
public DirectExchange confirmExchange(){
return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME).durable(true)
.withArgument("alternate-exchange",BACKUP_EXCHANGE_NAME).build();
}
//声名队列
@Bean("confirmQueue")
public Queue confirmQueue(){
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}
//绑定
@Bean
public Binding queueBindingExchange(@Qualifier("confirmQueue") Queue confirmQueue,
@Qualifier("confirmExchange") DirectExchange confirmExchange){
return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_ROUTING_KEY);
}
//备份交换机
@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 backupQueueBindingbackupExchange(@Qualifier("backupQueue") Queue backupQueue,
@Qualifier("backupExchange") FanoutExchange fanoutExchange){
return BindingBuilder.bind(backupQueue).to(fanoutExchange);
}
@Bean
public Binding warningQueueBindingBackupExchange(@Qualifier("warningQueue") Queue warningQueue,
@Qualifier("backupExchange") FanoutExchange fanoutExchange){
return BindingBuilder.bind(warningQueue).to(fanoutExchange);
}
}
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.config.DelayedQueueConfig;
import com.atguigu.springcloud.service.IMessageProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
*/
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMessageController {
@Resource
private IMessageProvider messageProvider;
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/sendMessage")
public String sendMessage(){
return messageProvider.send();
}
@GetMapping("sendMsg/{message}")
public void sendMsg(@PathVariable String message){
log.info("当前时间:{},发送一条消息给两个TTL队列:{}",new Date().toString(),message);
rabbitTemplate.convertAndSend("X","XA","消息来自ttl为10s的队列:"+message);
rabbitTemplate.convertAndSend("X","XB","消息来自ttl为40s的队列:"+message);
}
//开始发消息 消息 ttl
@GetMapping("/sendExpirationMsg/{message}/{ttlTime}")
public void sendMsg(@PathVariable String message,@PathVariable String ttlTime){
log.info("当前时间:{},发送一条时长{}毫秒消息给TTL队列:{}",new Date().toString(),ttlTime,message);
rabbitTemplate.convertAndSend("X", "XC",message,msg ->{
//发送消息的延时
msg .getMessageProperties().setExpiration(ttlTime);
return msg;
});
}
//开始发送消息,基于插件的消息及延迟的时间
@GetMapping("sendDelayMsg/{message}/{delayTime}")
public void sendMsg(@PathVariable String message,@PathVariable Integer delayTime){
log.info("当前时间:{},发送一条时长{}毫秒信息给延时队列delayed.queue:{}",new Date().toString(),delayTime,message);
rabbitTemplate.convertAndSend(DelayedQueueConfig.DELAYED_EXCHANGE_NAME
,DelayedQueueConfig.DELAYED_ROUTING_KEY,message,msg ->{
//发送消息的时候 延时时长 单位:ms
msg.getMessageProperties().setDelay(delayTime);
return msg;
});
}
}
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 开始发消息 测试d发布确认
*/
@Slf4j
@RestController
@RequestMapping("/confirm")
public class ProducerController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/sendMessage/{message}")
public void sendMessage(@PathVariable String message){
CorrelationData correlationData = new CorrelationData("1");
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME ,ConfirmConfig.CONFIRM_ROUTING_KEY,message,correlationData);
log.info("发送消息内容为:{}",message+"key1");
//错误的RoutingKey 交换机会收到消息但是队列不会收到
CorrelationData correlationData2 = new CorrelationData("2");
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME ,ConfirmConfig.CONFIRM_ROUTING_KEY+"2",message+"key12",correlationData2);
log.info("发送消息内容为:{}",message+"key12");
//改错交换机的名称会出现回退
CorrelationData correlationData3 = new CorrelationData("3");
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME+"123" ,ConfirmConfig.CONFIRM_ROUTING_KEY,message,correlationData3);
log.info("发送消息内容为:{}",message+"key1");
}
}
package com.atguigu.springcloud.consumer;
import com.atguigu.springcloud.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 接收 发布确认的消息
*/
@Slf4j
@Component
public class Consumer {
@RabbitListener(queues = ConfirmConfig.CONFIRM_QUEUE_NAME)
public void receiveConfirmMessage(Message message){
String msg = new String(message.getBody());
log.info("接收到的队列confirm.queue消息:{}",msg);
}
}
package com.atguigu.springcloud.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* 回调接口
*/
@Slf4j
@Component
public class MyCallBack implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {
//
@Autowired
private RabbitTemplate rabbitTemplate;
//注入到RabbitTemplate
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
/**
* 交换机确认回调方法
* 1.发消息 交换机收到了 回调
* 1.1 correlationData 保存回调消息的ID及相关信息
* 1.2 交换机收到消息 ack = true
* 1.3 cause null
* 2.发消息 交换机接收失败了 回调
* 2.1 correlationData 保存回调消息的id及相关信息
* 2.2 交换机收到消息 ack = false
* 2.3 cause 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String id = correlationData!=null?correlationData.getId():"";
if(ack){
log.info("交换机已经收到id为:{}的消息",id);
}else{
log.info("交换机还未收到id为:{}的消息,原因为:{}",id,cause);
}
}
//可以在当消息传递过程中不可达目的地时将消息返还给生产者
//只有不可达目的地的时候才进行回退
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.error("消息:{},被交换机{}退回,退回原因:{},路由key.{}",
new String(message.getBody()),exchange,replyText,routingKey);
}
}
如果消息发给交换机交换机接收不到消息就发送给备份的交换机
BackupConsumer
package com.atguigu.springcloud.consumer;
import com.atguigu.springcloud.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 备份队列
*/
@Slf4j
@Component
public class BackupConsumer {
//接收备份消息
@RabbitListener(queues = ConfirmConfig.BACKUP_QUEUE_NAME)
public void receiveWarningMsg(Message message){
String msg = new String(message.getBody());
log.error("备份队列的消息:{}",msg);
}
}
WarningConsumer
package com.atguigu.springcloud.consumer;
import com.atguigu.springcloud.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 报警消费者
*/
@Slf4j
@Component
public class WarningConsumer {
//接收报警消息
@RabbitListener(queues = ConfirmConfig.WARNING_QUEUE_NAME)
public void receiveWarningMsg(Message message){
String msg = new String(message.getBody());
log.error("报警发现不可路由消息:{}",msg);
}
}
同时配置回退消息和备份消息优先走备份消息
正常队列消息保存在内存中
惰性队列消息保存在磁盘中
两种模式
1.修改3台机器的主机名称
vim/etc/hostname
2.配置各个节点上的hosts文件,让各个节点都能互相认识对方
vim/etc/hosts
10...** node1
10...** node2
10...** node3
3.确保各个节点上的cookie文件使用的是同一个值
在node1 上执行远程操作命令
4.启动RabbitMQ服务,顺带启动Erlang虚拟机和RbbitMQ应用服务(在三台节点上分别执行以下命令)
rabbitmq-server -detached
5.在节点2执行
6.在节点3执行
7.查询集群状态
rabbitmqctl cluster_status
8.需要重新设置用户
创建账号
rabbitmqctl add_user admin 123
设置用户角色
rabbitmqctl set_user_tags admin administrator
设置用户权限
rabbitmqctl set_permissions -p “/” admin “." ".” “.*”
9.解除集群节点(node2 和 node3机器分别执行)
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
rabbitmqctl cluster_status
rabbitmqctl forget_cluster_node rabbit@node2(node1机器上执行)
name:名称
pattem:以**为开头的队列名称
ha-mode=exactly 备份模式是指定
ha-params=2 由这个参数指定上个参数
ha-sync-mode=automatic 同步模式是自动同步
3.在node1上创建一个队列发送一条消息,队列存在镜像队列
搭建步骤
1.需要保证每台节点独立运行
2.在每台机器上开启federation相关插件
rabbitmq-plugins enable rabbitmq_federation
rabbitmq-plugins enable rabbitmq_federation_management
3.原理图(先运行consumer在node2创建fed_exchange)
4.在downstaream(node2)配置upstream(node1)
4.添加policy
联邦队列可以再多个Broker节点或集群之间为单个队列提供负载均衡的功能。一个联邦队列可以连接一个或者多个上游队列(upstream queue),并从这些上游队列中获取消息以满足本地消费者消费消息的需求
搭建步骤
1.原理图
2.添加upstream(同上)
3.添加policy
federation 剧本的数据转发功能类似,Shovel够可靠,持续的从一个Broker中的队列拉取数据并转发到另一个Broker中的交换器。作为源端的队列和作为目的端的交换器可以同时位于同一个Broker,也可以位于不通的Broker上。Shovel可以翻译为铲子这个铲子可以从消息一方铲向另一方
搭建步骤
1.开启插件
rabbitmq-plugins enable rabbitmq_shovel
rabbitmq-plugins enable rabbitmq_shovel_management