RabbitMQ,2024年最新大数据开发基础入门

public static final String EXCHANGE_NAME=“logs”;

public static void main(String[] args) throws Exception {
//获取信道
Channel channel = RabbitMQUtils.getChannel();
//选择交换机一个交换机名字和类型
channel.exchangeDeclare(EXCHANGE_NAME, “fanout”);
//声明一个临时队列 队列名称随机 当消费者断开连接,该队列会自动删除 会返回一个队列名
String queueName = channel.queueDeclare().getQueue();
//绑定交换机和队列 1.是队列名 2.是交换机名 3是路由key
channel.queueBind(queueName, EXCHANGE_NAME, “”);
System.out.println(“客户端1等待接收消息…”);

//接收消息
DeliverCallback deliverCallback=(x,y)->{
String s=new String(y.getBody(),“UTF-8”);
System.out.println(“客户端1接收到的消息:”+s);
};
//消费者取消消息时回调接口
CancelCallback cancelCallback=x->{
System.out.println(“客户端1接收消息被中断…”);
};
//1.消费哪个队列 2.是否自动应答 3.消费者成功接收到消息的回调 4.消费者取消消费的回调 就是被中断
channel.basicConsume(queueName, true, deliverCallback,cancelCallback);
}
}

消费者2

public class ReceiveLogs02 {//接收消息

//选择交换机的名称
public static final String EXCHANGE_NAME=“logs”;

public static void main(String[] args) throws Exception {
//获取信道
Channel channel = RabbitMQUtils.getChannel();
//选择交换机一个交换机名字和类型
channel.exchangeDeclare(EXCHANGE_NAME, “fanout”);
//声明一个临时队列 队列名称随机 当消费者断开连接,该队列会自动删除 会返回一个队列名
String queueName = channel.queueDeclare().getQueue();
//绑定交换机和队列 1.是队列名 2.是交换机名 3是路由key
channel.queueBind(queueName, EXCHANGE_NAME, “”);
System.out.println(“客户端2等待接收消息…”);

//接收消息
DeliverCallback deliverCallback=(x, y)->{
String s=new String(y.getBody(),“UTF-8”);
System.out.println(“客户端2接收到的消息:”+s);
};
//消费者取消消息时回调接口
CancelCallback cancelCallback= x->{
System.out.println(“客户端2接收消息被中断…”);
};
//1.消费哪个队列 2.是否自动应答 3.消费者成功接收到消息的回调 4.消费者取消消费的回调 就是被中断
channel.basicConsume(queueName, true, deliverCallback,cancelCallback);
}
}

交换机(direct)直接交换机

和扇出(Fanout)区别在于RoutingKey   RoutingKey相同就是扇出(Fanout)   不同就是(direct)

比如一个消息,希望C1接收不希望C2接收  这种区别对待 有选择性的交换机

生产者

public class DirectLogs {
//交换机的名称
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);

Scanner scanner=new Scanner(System.in);
String message=“”;
while(true){
System.out.println(“输入消息:”);
message=scanner.nextLine();
System.out.println(“输入RoutingKey”);
String routingKey=scanner.nextLine();
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(“生产者发送消息完毕”);
}
}
}

消费者1

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);
//绑定交换机和队列 1.是队列名 2.是交换机名 3是路由key
channel.queueBind(“console”, EXCHANGE_NAME, “info”);
channel.queueBind(“console”, EXCHANGE_NAME, “warning”);
System.out.println(“客户端1等待接收消息…”);

//接收消息
DeliverCallback deliverCallback=(x, y)->{
String s=new String(y.getBody(),“UTF-8”);
System.out.println(“客户端1接收到的消息:”+s);
};
//消费者取消消息时回调接口
CancelCallback cancelCallback= x->{
System.out.println(“客户端1接收消息被中断…”);
};
//1.消费哪个队列 2.是否自动应答 3.消费者成功接收到消息的回调 4.消费者取消消费的回调 就是被中断
channel.basicConsume(“console”, true, deliverCallback,cancelCallback);
}
}

消费者2

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);
//绑定交换机和队列 1.是队列名 2.是交换机名 3是路由key
channel.queueBind(“disk”, EXCHANGE_NAME, “error”);
System.out.println(“客户端2等待接收消息…”);

//接收消息
DeliverCallback deliverCallback=(x, y)->{
String s=new String(y.getBody(),“UTF-8”);
System.out.println(“客户端2接收到的消息:”+s);
};
//消费者取消消息时回调接口
CancelCallback cancelCallback= x->{
System.out.println(“客户端2接收消息被中断…”);
};
//1.消费哪个队列 2.是否自动应答 3.消费者成功接收到消息的回调 4.消费者取消消费的回调 就是被中断
channel.basicConsume(“disk”, true, deliverCallback,cancelCallback);
}
}

交换机(Topic)主题交换机

结合了Fanout和Direct,更加灵活,想发给谁就发给谁

通过RoutingKey匹配,好比一个通配符的概念

当一个队列绑定键是#,那么这个队列将接收所有数据,就有点像fanout

如果队列绑定键当中没有#和*出现,那么该队列绑定类型就是direct

生产者

public class EmitLogTopic {
//交换机的名称
public static final String EXCHANGE_NAME=“topic_logs”;

public static void main(String[] args) throws Exception {
//获取信道
Channel channel = RabbitMQUtils.getChannel();
//选择交换机一个交换机名字和类型
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

Map<String,String> bindingKeyMap=new HashMap<>();
bindingKeyMap.put(“quick.orange.rabbit”,“被队列 Q1Q2 接收到”);
bindingKeyMap.put(“lazy.orange.elephant”,“被队列 Q1Q2 接收到”);
bindingKeyMap.put(“quick.orange.fox”,“被队列 Q1 接收到”);
bindingKeyMap.put(“lazy.brown.fox”,“被队列 Q2 接收到”);
bindingKeyMap.put(“lazy.pink.rabbit”,“虽然满足两个绑定但只被队列 Q2 接收一次”);
bindingKeyMap.put(“quick.brown.fox”,“不匹配任何绑定不会被任何队列接收到会被丢弃”);
bindingKeyMap.put(“quick.orange.male.rabbit”,“是四个单词不匹配任何绑定会被丢弃”);
bindingKeyMap.put(“lazy.orange.male.rabbit”,“是四个单词但匹配 Q2”);

for(Map.Entry<String,String> stringStringEntry:bindingKeyMap.entrySet()){
channel.basicPublish(EXCHANGE_NAME, stringStringEntry.getKey(), null, stringStringEntry.getValue().getBytes(StandardCharsets.UTF_8));
System.out.println(“生产者发出消息:”+stringStringEntry.getValue());
}

// Scanner scanner=new Scanner(System.in);
// String message=“”;
// while(true){
// System.out.println(“输入消息:”);
// message=scanner.nextLine();
// System.out.println(“输入RoutingKey”);
// String routingKey=scanner.nextLine();
// channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes(StandardCharsets.UTF_8));
// System.out.println(“生产者发送消息完毕”);
// }
}
}

消费者1

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, BuiltinExchangeType.TOPIC);
//声明队列
String queueName=“Q1”;
channel.queueDeclare(queueName, false, false, false, null);
//绑定 1.队列名 2.交换机名 3.RoutingKey
channel.queueBind(queueName,EXCHANGE_NAME , “.orange.”);

System.out.println(“C1等待接收消息”);

//接收消息
DeliverCallback deliverCallback=(x, y)->{
String s=new String(y.getBody(),“UTF-8”);
System.out.println(“C1接收到的消息:”+s+“,绑定的键:”+y.getEnvelope().getRoutingKey());
};
//消费者取消消息时回调接口
CancelCallback cancelCallback= x->{
System.out.println(“C1接收消息被中断…”);
};
//1.消费哪个队列 2.是否自动应答 3.消费者成功接收到消息的回调 4.消费者取消消费的回调 就是被中断
channel.basicConsume(queueName, true, deliverCallback,cancelCallback);
}
}

消费者2

public class ReceiveLogsTopic02 {//声明主题交换机及相关队列 消费者1
//交换机的名称
public static final String EXCHANGE_NAME=“topic_logs”;

//接收消息
public static void main(String[] args) throws Exception {
//获取信道
Channel channel = RabbitMQUtils.getChannel();
//指定交换机名字和类型
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//声明队列
String queueName=“Q2”;
channel.queueDeclare(queueName, false, false, false, null);
//绑定 1.队列名 2.交换机名 3.RoutingKey
channel.queueBind(queueName,EXCHANGE_NAME , “..rabbit”);
channel.queueBind(queueName,EXCHANGE_NAME , “lazy.#”);//#代表匹配多个.分割的单词 如"lazy.rabbit", “lazy.orange.rabbit”

System.out.println(“C2等待接收消息”);

//接收消息
DeliverCallback deliverCallback=(x, y)->{
String s=new String(y.getBody(),“UTF-8”);
System.out.println(“C2接收到的消息:”+s+“,绑定的键:”+y.getEnvelope().getRoutingKey());
};
//消费者取消消息时回调接口
CancelCallback cancelCallback= x->{
System.out.println(“C2接收消息被中断…”);
};
//1.消费哪个队列 2.是否自动应答 3.消费者成功接收到消息的回调 4.消费者取消消费的回调 就是被中断
channel.basicConsume(queueName, true, deliverCallback,cancelCallback);
}
}

死信队列
1.死信的概念

死信:无法被消费的消息.

由于特定原因:producer(生产者)将消息投递到broker(中间件)或者直接到queue里,Consumer(消费者)从queue取出消息进行消费,但是由于某种原因导致queue中的某些消息无法被消费.这样的消息如果没有后续处理就变成了死信,有死信就有了死信队列

2.死信的来源(产生的原因)

1.消息TTL过期

2.队列达到最大长度(队列满了,无法再添加信的数据到MQ中,MQ会采取一定的策略来处理这些无法存储的新消息.最常见的策略是丢弃队列中的旧消息(例如最早进入队列未被消费的消息)来为新消息腾出空间.被丢弃的就消息就成为死信,如果队列配置了死信交换机(DLX),这些死信会被发送到指定的死信交换机,或被路由到特定的死信队列中)–>注意如果MQ采用的是拒绝新消息,因新消息无法根本没办法进队列,更没有机会到死信队列

3.消息被拒绝(NACK):当消费者接收到一条消息后,如果它决定不处理这条消息,并且调用了拒绝(basic.rejectbasic.nack)方法拒绝消息,同时设置requeue参数为false,意味着不希望RabbitMQ重新将这条消息放入队列中,那么这条消息就会变成死信。

3.死信实战
1.代码架构图

2.消息TTL过期

首先启动客户端1

public class Consumer01 {//死信队列实战 消费者1
//普通交换机名称
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();
//指定交换机名字和类型
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);

//参数
Map<String,Object> arguments=new HashMap<>();
//TTL过期时间 一般 生产者者发送消息时指定比较常见
//arguments.put(“x-message-ttl”, 100000);//单位毫秒 这个过期时间也可以由生产者发送消息时候指定
//正常队列设置死信交换机
arguments.put(“x-dead-letter-exchange”,DEAD_EXCHANGE);
//设置死信RoutingKey
arguments.put(“x-dead-letter-routing-key”,“lisi”);

//声明队列 1.队列名称 2.持久化 3.排他性 4.自动删除 5.其他参数(可以通过这个参数设置队列的TTL(Time-To-Live),最大长度等)
channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);
channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
//绑定 1.队列名 2.交换机名 3.RoutingKey
//绑定普通交换机与普通队列
channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE , “zhangsan”);
//绑定死信的交换机与死信队列
channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE , “lisi”);
//接收消息
DeliverCallback deliverCallback=(x, y)->{
String s=new String(y.getBody(),“UTF-8”);
System.out.println(“C1接收到的消息:”+s+“,绑定的键:”+y.getEnvelope().getRoutingKey());
};
//消费者取消消息时回调接口
CancelCallback cancelCallback= x->{
System.out.println(“C1接收消息被中断…”);
};
System.out.println(“C1等待接收消息…”);
//1.消费哪个队列 2.是否自动应答 3.消费者成功接收到消息的回调 4.消费者取消消费的回调 就是被中断
channel.basicConsume(NORMAL_QUEUE, true, deliverCallback,cancelCallback);
}
}

下面将客户端1关闭   开启生产者  由于客户端1已经关闭  TTL时间为10秒,10秒后这些消息就会到死信队列

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();//10秒

for(int i=1;i<11;i++){
String message=“info”+i;
channel.basicPublish(NORMAL_EXCHANGE, “zhangsan”, properties, message.getBytes(StandardCharsets.UTF_8));
}
}
}

由于客户端已经关闭 10秒后

下面开启C2对死信队列中的消息进行消费

public class Consumer02 {
//消费者从队列中接收消息时,并不需要直接指定交换机。这是因为消息的路由(即从生产者到队列的过程)
// 是通过交换机进行的,但一旦消息到达队列,消费者就直接从队列中获取消息,而与交换机无关。
//死信队列名称
public static final String DEAD_QUEUE=“dead_queue”;

public static void main(String[] args) throws Exception {
//获取信道
Channel channel = RabbitMQUtils.getChannel();

//接收消息
DeliverCallback deliverCallback=(x, y)->{
String s=new String(y.getBody(),“UTF-8”);
System.out.println(“C2接收到的消息:”+s+“,绑定的键:”+y.getEnvelope().getRoutingKey());
};
//消费者取消消息时回调接口
CancelCallback cancelCallback= x->{
System.out.println(“C2接收消息被中断…”);
};
System.out.println(“C2等待接收消息…”);
//1.消费哪个队列 2.是否自动应答 3.消费者成功接收到消息的回调 4.消费者取消消费的回调 就是被中断
channel.basicConsume(DEAD_QUEUE, true, deliverCallback,cancelCallback);
}
}

一启动,马上接收到消息

3.队列达到最大长度

设置队列长度

public class Consumer01 {//死信队列实战 消费者1
//普通交换机名称
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();
//指定交换机名字和类型
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);

//参数
Map<String,Object> arguments=new HashMap<>();
//TTL过期时间 一般 生产者者发送消息时指定比较常见
//arguments.put(“x-message-ttl”, 100000);//单位毫秒 这个过期时间也可以由生产者发送消息时候指定
//正常队列设置死信交换机
arguments.put(“x-dead-letter-exchange”,DEAD_EXCHANGE);
//设置死信RoutingKey
arguments.put(“x-dead-letter-routing-key”,“lisi”);
//设置队列长度限制
arguments.put(“x-max-length”, 6);
//声明队列 1.队列名称 2.持久化 3.排他性 4.自动删除 5.其他参数(可以通过这个参数设置队列的TTL(Time-To-Live),最大长度等)
channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);
channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
//绑定 1.队列名 2.交换机名 3.RoutingKey
//绑定普通交换机与普通队列
channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE , “zhangsan”);
//绑定死信的交换机与死信队列
channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE , “lisi”);
//接收消息
DeliverCallback deliverCallback=(x, y)->{
String s=new String(y.getBody(),“UTF-8”);
System.out.println(“C1接收到的消息:”+s+“,绑定的键:”+y.getEnvelope().getRoutingKey());
};
//消费者取消消息时回调接口
CancelCallback cancelCallback= x->{
System.out.println(“C1接收消息被中断…”);
};
System.out.println(“C1等待接收消息…”);
//1.消费哪个队列 2.是否自动应答 3.消费者成功接收到消息的回调 4.消费者取消消费的回调 就是被中断
channel.basicConsume(NORMAL_QUEUE, true, deliverCallback,cancelCallback);
}
}

因为队列属性变了,需要在界面把原先队列删除掉

启动客户端1  可以看到下图,  然后关闭客户端1  假死

启动生产者

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();//10秒

for(int i=1;i<11;i++){
String message=“info”+i;
//channel.basicPublish(NORMAL_EXCHANGE, “zhangsan”, properties, message.getBytes(StandardCharsets.UTF_8));
channel.basicPublish(NORMAL_EXCHANGE, “zhangsan”, null, message.getBytes(StandardCharsets.UTF_8));
}
}
}

可以看到 只能6个   剩下4个在死信队列

4.消息被拒

可以启动消费者1和消费者2将上面普通队列和死信队列中的消息消费掉,下面演示消息被拒

先删除队列,因为属性又要变了

启动客户端1   要开启手动应答,如果是自动应答就不存在拒绝

public class Consumer01 {//死信队列实战 消费者1
//普通交换机名称
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();
//指定交换机名字和类型
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);

//参数
Map<String,Object> arguments=new HashMap<>();
//TTL过期时间 一般 生产者者发送消息时指定比较常见
//arguments.put(“x-message-ttl”, 100000);//单位毫秒 这个过期时间也可以由生产者发送消息时候指定
//正常队列设置死信交换机
arguments.put(“x-dead-letter-exchange”,DEAD_EXCHANGE);
//设置死信RoutingKey
arguments.put(“x-dead-letter-routing-key”,“lisi”);
//设置队列长度限制
//arguments.put(“x-max-length”, 6);
//声明队列 1.队列名称 2.持久化 3.排他性 4.自动删除 5.其他参数(可以通过这个参数设置队列的TTL(Time-To-Live),最大长度等)
channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);
channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
//绑定 1.队列名 2.交换机名 3.RoutingKey
//绑定普通交换机与普通队列
channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE , “zhangsan”);
//绑定死信的交换机与死信队列
channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE , “lisi”);
//接收消息
DeliverCallback deliverCallback=(x, y)->{
String s=new String(y.getBody(),“UTF-8”);
//演示拒绝消息
if(s.equals(“info5”)){//让info5成为死信
//第一个参数是标签 第二个表示放不放回队列(false表示不放回普通队列,成死信)
channel.basicReject(y.getEnvelope().getDeliveryTag(), false);
System.out.println(“C1接收到:”+s+“,此消息被拒绝”);
}else{
System.out.println(“C1接收消息:”+s);
channel.basicAck(y.getEnvelope().getDeliveryTag(), false);
}
//如下打印还是会显示被拒绝的消息
//System.out.println(“C1接收到的消息:”+s+“,绑定的键:”+y.getEnvelope().getRoutingKey());
};
//消费者取消消息时回调接口
CancelCallback cancelCallback= x->{
System.out.println(“C1接收消息被中断…”);
};
System.out.println(“C1等待接收消息…”);
//1.消费哪个队列 2.是否自动应答 3.消费者成功接收到消息的回调 4.消费者取消消费的回调 就是被中断
channel.basicConsume(NORMAL_QUEUE, false, deliverCallback,cancelCallback);//开启手动应答
}
}

启动生产者

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();//10秒

for(int i=1;i<11;i++){
String message=“info”+i;
//channel.basicPublish(NORMAL_EXCHANGE, “zhangsan”, properties, message.getBytes(StandardCharsets.UTF_8));
channel.basicPublish(NORMAL_EXCHANGE, “zhangsan”, null, message.getBytes(StandardCharsets.UTF_8));
}
}
}

可以点进去看看是不是info5

开启客户端2消费死信

public class Consumer02 {
//消费者从队列中接收消息时,并不需要直接指定交换机。这是因为消息的路由(即从生产者到队列的过程)
// 是通过交换机进行的,但一旦消息到达队列,消费者就直接从队列中获取消息,而与交换机无关。
//死信队列名称
public static final String DEAD_QUEUE=“dead_queue”;

public static void main(String[] args) throws Exception {
//获取信道
Channel channel = RabbitMQUtils.getChannel();

//接收消息
DeliverCallback deliverCallback=(x, y)->{
String s=new String(y.getBody(),“UTF-8”);
System.out.println(“C2接收到的消息:”+s+“,绑定的键:”+y.getEnvelope().getRoutingKey());
};
//消费者取消消息时回调接口
CancelCallback cancelCallback= x->{
System.out.println(“C2接收消息被中断…”);
};
System.out.println(“C2等待接收消息…”);
//1.消费哪个队列 2.是否自动应答 3.消费者成功接收到消息的回调 4.消费者取消消费的回调 就是被中断
channel.basicConsume(DEAD_QUEUE, true, deliverCallback,cancelCallback);
}
}

被消费

延迟队列
1.延迟队列概念

延迟队列其实是死信队列的一种,消息的TTL过期就是延迟队列

2.延迟队列使用场景

1.订单在十分钟之内未支付则自动取消

2.新创建的店铺,如果在10天内没有上传过商品,则自动发送消息提醒

3.用户注册成功后,如果三天内没有登录则进行短信提醒

4.用户发起退款,如果三天内没有得到处理则通知相关运营人员

5.预定会议后,需要在预定的时间点前10分钟通知各个参会人员参加会议等等

整合SpringBoot(延迟队列)

引入lombok spring web rabbitMQ

fastjson  swagger   rabbittest

<?xml version="1.0" encoding="UTF-8"?>


4.0.0

org.springframework.boot
spring-boot-starter-parent
2.4.5


com.example
springboot-rabbitmq
0.0.1-SNAPSHOT
springboot-rabbitmq
Demo project for Spring Boot

<java.version>8</java.version>



org.springframework.boot
spring-boot-starter-amqp


com.alibaba
fastjson
2.0.43


io.springfox
springfox-swagger2
2.9.2


io.springfox
springfox-swagger-ui
2.9.2


org.springframework.amqp
spring-rabbit-test


org.springframework.boot
spring-boot-starter-web

org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.amqp spring-rabbit-test test org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok

application.properties

添加Swagger配置类

@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket webApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName(“webApi”)
.apiInfo(webApiInfo())
.select()
.build();
}
private ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title(“rabbitmq 接口文档”)
.description(“本文档描述了 rabbitmq 微服务接口定义”)
.version(“1.0”)
.contact(new Contact(“hrui”, “http://xxxx.com”,
“376084295@qq.com”))
.build();
}
}

队列TTL

代码架构图

创建两个队列QA和QB,两个队列TTL分别设置为10S和40秒,然后创建一个交换机X和死新交换机Y,他们的类型都是direct,创建一个死信队列QD,他们的绑定关系如下:

在SpringBoot中,交换机,队列   绑定关系 都需要在一个配置类中声明

package com.example.springbootrabbitmq.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;

/**

  • @author hrui
  • @date 2024/2/29 7:00
    */
    @Configuration
    public class TtlQueueConfig {
    //普通交换机的名称
    public static final String X_EXCHANGE = “X”;
    //普通队列名称
    public static final String QUEUE_A = “QA”;
    //普通队列名称
    public static final String QUEUE_B = “QB”;
    //死信交换机名称
    public static final String Y_DEAD_LETTER_EXCHANGE = “Y”;
    //死信队列名称
    public static final String DEAD_LETTER_QUEUE = “QD”;
    // 声明 xExchange
    @Bean(“xExchange”)
    public DirectExchange xExchange(){
    return new DirectExchange(X_EXCHANGE);
    }
    // 声明 xExchange
    @Bean(“yExchange”)
    public DirectExchange yExchange(){
    return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
    }
    //声明队列 A ttl 为 10s 并绑定到对应的死信交换机
    @Bean(“queueA”)
    public Queue queueA(){
    Map<String, Object> args = new HashMap<>(3);
    //声明当前队列绑定的死信交换机
    args.put(“x-dead-letter-exchange”, Y_DEAD_LETTER_EXCHANGE);
    //声明当前队列的死信路由 key
    args.put(“x-dead-letter-routing-key”, “YD”);
    //声明队列的 TTL
    args.put(“x-message-ttl”, 10000);
    return QueueBuilder.durable(QUEUE_A).withArguments(args).build();
    }
    // 声明队列 A 绑定 X 交换机
    @Bean
    public Binding queueaBindingX(@Qualifier(“queueA”) Queue queueA,
    @Qualifier(“xExchange”) DirectExchange xExchange){
    return BindingBuilder.bind(queueA).to(xExchange).with(“XA”);
    }
    //声明队列 B ttl 为 40s 并绑定到对应的死信交换机
    @Bean(“queueB”)
    public Queue queueB(){
    Map<String, Object> args = new HashMap<>(3);
    //声明当前队列绑定的死信交换机
    args.put(“x-dead-letter-exchange”, Y_DEAD_LETTER_EXCHANGE);
    //声明当前队列的死信路由 key
    args.put(“x-dead-letter-routing-key”, “YD”);
    //声明队列的 TTL
    args.put(“x-message-ttl”, 40000);
    return QueueBuilder.durable(QUEUE_B).withArguments(args).build();
    }
    //声明队列 B 绑定 X 交换机
    @Bean
    public Binding queuebBindingX(@Qualifier(“queueB”) Queue queue1B,
    @Qualifier(“xExchange”) DirectExchange xExchange){
    return BindingBuilder.bind(queue1B).to(xExchange).with(“XB”);
    }
    //声明死信队列 QD
    @Bean(“queueD”)
    public Queue queueD(){
    return new Queue(DEAD_LETTER_QUEUE);
    }
    //声明死信队列 QD 绑定关系
    @Bean
    public Binding deadLetterBindingQAD(@Qualifier(“queueD”) Queue queueD,
    @Qualifier(“yExchange”) DirectExchange yExchange){
    return BindingBuilder.bind(queueD).to(yExchange).with(“YD”);
    }
    }

接收前端发送请求 并做为生产者发送给MQ

package com.example.springbootrabbitmq.controller;

import lombok.AllArgsConstructor;
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 java.util.Date;

/**

  • @author hrui
  • @date 2024/2/29 8:01
    */
    @RestController
    @RequestMapping(“ttl”)
    @Slf4j
    @AllArgsConstructor
    public class SendMsgController {//发送延迟消息

private final RabbitTemplate rabbitTemplate;

//开始发消息
@GetMapping(“/sendMsg/{message}”)
public void sendMsg(@PathVariable String message){
log.info(“当前时间:{},送法一条消息给两个TTL队列:{}”,new Date().toString(),message);
rabbitTemplate.convertAndSend(“X”, “XA”, “消息来自ttl为10S的队列”);
rabbitTemplate.convertAndSend(“X”, “XB”, “消息来自ttl为40S的队列”);
}

}

消费者监听

package com.example.springbootrabbitmq.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.io.UnsupportedEncodingException;
import java.util.Date;

/**

  • @author hrui
  • @date 2024/2/29 8:20
    */
    @Component
    @Slf4j
    public class DeadLetterQueueConsumer {//队列TTL消费者

//接收消息
@RabbitListener(queues = “QD”)//表示监听QD队列
public void receiveD(Message message, Channel channel) throws UnsupportedEncodingException {
String msg=new String(message.getBody(),“UTF-8”);
log.info(“当前时间:{},收到死信队列的消息:{}”,new Date().toString(),msg);
}
}

启动SpringBoot

访问:localhost:8081/ttl/sendMsg/哈哈哈哈

上面例子中存在一个问题,前端发送后,该消息分别给两个队列10S后,40秒后变成死信队列,这样一个延迟队列就打造完成,但是如果时间上加个新需求,就要新增一个队列,这样的话要创建很多队列

延迟队列优化

新增一个队列QC,绑定关系如下,该队列不设置TTL时间

QC的TTL时间在生产者给MQ发送消息时候指定

也就是说本来就应该这么去干

配置类中加以下代码

//普通队列名称
public static final String QUEUE_C = “QC”;
@Bean(“queueC”)
public Queue queueC(){
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定的死信交换机
args.put(“x-dead-letter-exchange”, Y_DEAD_LETTER_EXCHANGE);
//声明当前队列的死信路由 key
args.put(“x-dead-letter-routing-key”, “YD”);
//声明队列的 TTL
//args.put(“x-message-ttl”, 40000);//不设置TTL时间 生产者指定
return QueueBuilder.durable(QUEUE_C).withArguments(args).build();
}
//声明队列 C 绑定 X 交换机
@Bean
public Binding queuecBindingX(@Qualifier(“queueC”) Queue queue1B,
@Qualifier(“xExchange”) DirectExchange xExchange){
return BindingBuilder.bind(queue1C).to(xExchange).with(“XC”);
}

生产者代码

@RestController
@RequestMapping(“ttl”)
@Slf4j
@AllArgsConstructor
public class SendMsgController {//发送延迟消息

private final RabbitTemplate rabbitTemplate;

//开始发消息
@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);

rabbitTemplate.convertAndSend(“X”, “XC”, “消息来自ttl为5S的队列:”+message, x->{
x.getMessageProperties().setExpiration(“5000”);//这个参数也可以前端传进来
return x;
});

}

}

启动测试

localhost:8081/ttl/sendMsg/哈哈哈

延迟队列基于死信存在的问题

@RestController
@RequestMapping(“ttl”)
@Slf4j
@AllArgsConstructor
public class SendMsgController {//发送延迟消息

private final RabbitTemplate rabbitTemplate;

//开始发消息
@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);

rabbitTemplate.convertAndSend(“X”, “XC”, “消息来自ttl为50S的队列:”+message, x->{
x.getMessageProperties().setExpiration(“5000”);//这个参数也可以前端传进来
return x;
});

}
@GetMapping(“/sendMsg/{message}/{ttl}”)
public void sendMsg(@PathVariable String message,@PathVariable String ttl){
log.info(“当前时间:{},送法一条消息给两个TTL队列:{}”,new Date().toString(),message);

rabbitTemplate.convertAndSend(“X”, “XC”, “消息来指定ttl为”+ttl+“的队列:”+message, x->{
x.getMessageProperties().setExpiration(ttl);
return x;
});

}
}

出现的问题是

时间有点乱TTL

就是说TTL 2秒   和TTL20秒  所用的时候可能超过20秒  就是说来的时候是一起来的

死信队列的缺陷:RabbitMQ只会检查第一个消息是否过期,如果过期则丢到死信队列,如果第一个消息的延长时间很长,而第二个消息的延长时间很短,第二个消息并不会优先得到执行

延迟队列基于插件解决上述问题

安装延时队列插件

需要安装插件rabbitmq_delayed_message_exchange

Releases · rabbitmq/rabbitmq-delayed-message-exchange · GitHub

将文件拷贝到容器内

docker cp /usr/local/develop/rabbitmq_delayed_message_exchange-3.12.0.ez rabbitmq:/plugins/

进入rabbitmq容器实例

docker exec -it rabbitmq /bin/bash

cd plugins

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

注意:确保你的容器是后台启动的  不然docker stop rabbitmq的话就找不到运行过的容器了

如果你前台启动那只能重装了

docker restart rabbitmq

现在就好了  有明显的时间顺序

插件安装完成后 会多这么个玩意

基于插件的延迟队列

安装玩插件之后多了一个延迟类型

新增配置文件

@Configuration
public class DelayedQueueConfig {
public static final String DELAYED_QUEUE_NAME = “delayed.queue”;
public static final String DELAYED_EXCHANGE_NAME = “delayed.exchange”;
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> args = new HashMap<>();
//自定义交换机的类型
args.put(“x-delayed-type”, “direct”);
return new CustomExchange(DELAYED_EXCHANGE_NAME, “x-delayed-message”, true, false,
args);
}
@Bean
public Binding bindingDelayedQueue(@Qualifier(“delayedQueue”) Queue queue,
@Qualifier(“delayedExchange”) CustomExchange
delayedExchange) {
return BindingBuilder.bind(queue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();
}
}

控制器代码

public static final String DELAYED_EXCHANGE_NAME = “delayed.exchange”;
public static final String DELAYED_ROUTING_KEY = “delayed.routingkey”;
@GetMapping(“sendDelayMsg/{message}/{delayTime}”)
public void sendMsgA(@PathVariable String message,@PathVariable Integer delayTime) {
rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, message,
correlationData -> {
correlationData.getMessageProperties().setDelay(delayTime);
return correlationData;
});
log.info(" 当 前 时 间 : {}, 发送一条延迟 {} 毫秒的信息给队列 delayed.queue:{}", new
Date(), delayTime, message);
}

消费者代码

@Component
@Slf4j
public class DeadLetterQueueConsumer {//队列TTL消费者

//接收消息
@RabbitListener(queues = “QD”)//表示监听QD队列
public void receiveD(Message message, Channel channel) throws UnsupportedEncodingException {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数大数据工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上大数据开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注大数据获取)
img

ELAYED_EXCHANGE_NAME = “delayed.exchange”;
public static final String DELAYED_ROUTING_KEY = “delayed.routingkey”;
@GetMapping(“sendDelayMsg/{message}/{delayTime}”)
public void sendMsgA(@PathVariable String message,@PathVariable Integer delayTime) {
rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, message,
correlationData -> {
correlationData.getMessageProperties().setDelay(delayTime);
return correlationData;
});
log.info(" 当 前 时 间 : {}, 发送一条延迟 {} 毫秒的信息给队列 delayed.queue:{}", new
Date(), delayTime, message);
}

消费者代码

@Component
@Slf4j
public class DeadLetterQueueConsumer {//队列TTL消费者

//接收消息
@RabbitListener(queues = “QD”)//表示监听QD队列
public void receiveD(Message message, Channel channel) throws UnsupportedEncodingException {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数大数据工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
[外链图片转存中…(img-uehu7XDJ-1712532358515)]
[外链图片转存中…(img-qF34lLqP-1712532358516)]
[外链图片转存中…(img-Qgf39MgC-1712532358517)]
[外链图片转存中…(img-xi8lCWyy-1712532358517)]
[外链图片转存中…(img-0LDeCvl5-1712532358518)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上大数据开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注大数据获取)
[外链图片转存中…(img-EWWoupgX-1712532358518)]

  • 9
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值