RabbitMQ代码位置
E:\java220729\RabbitMQ
课件位置
E:\动力结点java资料\RabbitMQ\笔记
一.RabbitMQ简介
1.消息是什么
消息(Message)是指在应用间传送的数据。消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象。
消息队列(Message Queue)是一种应用间的通信方式,消息发送后可以立即返回,由消息系统来确保消息的可靠传递。消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不管是谁发布的。这样发布者和使用者都不用知道对方的存在。
2.RabbitMQ简介
RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。
AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。
1、可靠性(Reliability)RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。
2、灵活的路由(Flexible Routing)
在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在一起,也通过插件机制实现自己的 Exchange 。
3、 消息集群(Clustering)多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。
4、 高可用(Highly Available Queues)队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。
5、多种协议(Multi-protocol)RabbitMQ 支持多种消息队列协议,比如 STOMP、MQTT 等等。
6、多语言客户端(Many Clients)RabbitMQ 几乎支持所有常用语言,比如 Java、.NET、Ruby 等等。
7、 管理界面(Management UI)RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker 的许多方面。
8、 跟踪机制(Tracing)如果消息异常,RabbitMQ 提供了消息跟踪机制,使用者可以找出发生了什么。
3.RabbitMQ的三大功能
1.流量消峰 2.异步处理 3.应用解耦
4.MQ的分类
1.activeMQ 不做介绍
2.kafka 大数据量的杀手锏
3.RocketMQ 阿里巴巴的,参考了kafka
4.RabbitMQ 适用于中小型项目,是一个基于AMQP 高级消息队列协议 的基础上完成的
5.RabbitMQ的四大核心

1.生产者:是一个发送消息的角色
2.交换机:适用于把生产者发送出来的消息,经过本身传递给队列
3.队列:用于存储消息,等待消费者消费
4.消费者:是一个去队列中消费消息的角色
5.信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。
二:RabbitMQ的使用
1.Work Queues工作队列

一个消息只能被消费一次,不可以被消费多次,所以工作线程要竞争消息,而队列采用轮训的方式分发消息。
2.消息应管
为了保证消息在发送过程中不丢失,RabbitMQ引入了消息应答机制,就是消费者在接受到消息并处理完成后,告诉rabbitMQ他已经处理了,那RabbitMQ就可以把消息删除。
1.自动应管:默认就是自动应管的
2.手动应管:
channel.basicAck(用于消息肯定确认)
channel.basicNack(用于消息否定确认)
channel.basicReject(用于消息否定确认)
有multiple=true配置的是批量应答
false只应答当前
在成功接收消息的回调中:
DeliverCallback d=(var1,var2)->{
/*在手动应答之前先睡觉*/
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new String(var2.getBody(),"UTF-8"));
/*手动应答 通过信道 第一个参数是获取消息的标记
* 第二个参数是 是否批量应答*/
channel.basicAck(var2.getEnvelope().getDeliveryTag(),false);
};
取消接收消息的回调:
CancelCallback c=(var1)->{
System.out.println("消息取消接收的时候执行的方法");
};
3.消息重新入队
如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或 TCP 连接丢失),导致消息未发送 ACK 确认,RabbitMQ 将了解到消息未完全处理,并将对其重新排队。如果此时其他消费者可以处理,它将很快将其重新分发给另一个消费者。这样,即使某个消费者偶尔死亡,也可以确保不会丢失任何消息
4.RabbitMQ持久化
用于保障当RabbitMQ服务器停掉以后消息从生产者发送过来消息不会丢失,为了保证消息不丢失需要做两件事
将队列和消息都标记为持久化,还有发布确认
1.队列持久化:
/*4.通过连接来获取管道*/
Channel channel = connection.createChannel();
/*5.通过管道来创建队列
* 第一个参数 :队列名称
* 二:队列里的消息是否要持久化,默认在内存false
* 三:该队列是否只供一个消费者 true多个
* 四:是否自动删除,最后一个消费者端连接以后,该队列是否自动删除 true删
* 五:其他参数*/
/*在声明队列的时候声明为优先级队列*/
HashMap<String, Object> map = new HashMap<>();
map.put("x-max-priority",10);
channel.queueDeclare(QUEUE_NAME,false,false,false,map);
2,消息持久化
/*消息持久化 生产者发消息的时候带上参数
* 代表消息持久化MessageProperties.PERSISTENT_TEXT_PLAIN*/
channel.basicPublish("",TEST_QUEUE, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes("UTF-8"));
5.Rabbit的不公平分发
就是能者多劳模式,当有的消费者还没有处理完消息是,会分发给其他人。
/*设置分发策略为能者多劳 不公平分发*/
// channel.basicQos(1);
channel.basicQos(5); 信道上最多有多少消息条数
channel.basicConsume(TEST_QUEUE,false,d,c);
6.消息发布确认
解决消息不丢失:当收到消息并把消息保存在磁盘上,在和生产者说,就叫发布确认。生产者用boolean b = channel.ConfirmSelect(); 来开启发布确认。
1.单个发布确认
public static void dange() throws IOException, TimeoutException, InterruptedException {
com.rabbitmq.client.Channel channel = Channel.getChannel();
String s = UUID.randomUUID().toString();
/*声明一个队列*/
channel.queueDeclare(s,true,false,false,null);
/*开启消息确认发布*/
channel.confirmSelect();
/*记录开始时间*/
long l = System.currentTimeMillis();
for(int i=0;i<MAX;i++){
/*发布消息*/
String message=i+"";
channel.basicPublish("",s,null,message.getBytes());
/*消息发出去就确认*/
boolean b = channel.waitForConfirms();
if(b){
System.out.println("消息送成功");
}
}
/*记录结束时间*/
long last= System.currentTimeMillis();
System.out.println("总耗时为:"+(last-l));
}
2.批量发布确认(无法确定哪个消息没有被确认)
/*每一百次才确认一次*/
if(i%100==0){
boolean b = channel.waitForConfirms();
if(b){
sum++;
System.out.println("第"+sum+"次确认");
}
}
3.异步确认发布(性价比最高)
不管是接收到消息还是没有接收到消息,都会返回给队列(异步),这样生产者只管发送消息。
/*异步消息确认*/
public static void yibu() throws IOException, TimeoutException {
com.rabbitmq.client.Channel channel = Channel.getChannel();
String s = UUID.randomUUID().toString();
/*声明一个队列*/
channel.queueDeclare(s,true,false,false,null);
/*开启消息确认发布*/
channel.confirmSelect();
/*创建消息存放的map 是线程安全有序支持多并发的 ConcurrentSkipListMap*/
ConcurrentSkipListMap<Long,String> cslm=new ConcurrentSkipListMap<>();
/*var1 当前确认的消息的标记
* var3 是否为批量确认*/
ConfirmCallback cf=(var1, var3)->{
/*消息确认就把map里删除已经确认的消息 通过消息的标记返回这个map里已经确认的消息map*/
/*headMap(var1) 是 NavigableMap 接口中的一个方法。
它返回一个视图,包含了在当前映射中所有小于指定键(在此例中为 var1)的键值对。
这个返回的视图是动态的,即对返回的 headMap 进行的修改会反映到原始映射中。*/
ConcurrentNavigableMap<Long, String> lscnm = cslm.headMap(var1); /*具有并发性。它允许在多个线程之间安全地读取和写入映射数据。*/
/*如果是批量删除就 清理掉这个map*/
if(var3){
lscnm.clear();
}else {/*如果不是批量删除就直接 删除掉这个标记的消息*/
cslm.remove(var1);
}
System.out.println(var1+"消息已经确认");
};
ConfirmCallback cb=(var1, var3)->{
System.out.println("丢失的消息"+var1);
};
/*给信道添加一个监听器 监听消息发送的成功和失败
* 第一个参数为消息成功确认的回调函数
* 第二个参数为消息确认失败到的回调函数*/
channel.addConfirmListener(cf,cb); //这个监听是异步的
long l = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
String message=i+"";
channel.basicPublish("",s,null,message.getBytes());
/*消息发送成功就假如map中
* getNextPublishSeqNo() 获取下一个要发送的消息的序号*/
cslm.put(channel.getNextPublishSeqNo(),message);
}
/*记录结束时间*/
long last= System.currentTimeMillis();
System.out.println("总耗时为:"+(last-l));
}
5.四种交换机类型的解释
首先通过交换机就可以让一个消息给不同的消费者消费
生产者只需要将消息传到交换机,其他都可由交换机来完成,比如:
1.消息的推送
2.消息放入哪一个特定的队列
3.消息是否丢弃

direct直接、fanout扇出、topic主题、headers标题(与直接交换机一样但是性能差)
直接direct

fanout扇出
topic主题

临时队列的解释:
一旦断开消费者连接,队列就会被自动删除
/*创建零食队列*/
String queue = channel.queueDeclare().getQueue();
/*交换机与队列绑定 队列名 交换机名字 routingkey*/
channel.queueBind(queue,EXCHANGE_NAME,"");
DeliverCallback d=(var1,var2)->{
System.out.println("cunsume1消费成功"+new String(var2.getBody()));
};
6.死信队列
死信:无法被消费的消息
应用场景:
为了保证订单业务的消息数据不丢失,需要使用到RabbitMQ的死信队列机制,当消息消费发生异常的时候,消息将会投入到死信队列中。还如比:用户在商城下单成功并去支付后再指定的时间内未支付自动失败。
死信来源:
1.消息TTL过期
2.队列达到最大长度
3.消息被拒绝—》否定应答并且 requeue = false 不重新放入队列
代码的架构图

代码展示:
消费者1:
c1要声明两个交换机两个队列
需要先运行起来C1消费者把交换机和队列创建起来,在关闭。要模拟消息过期,再运行生产者,从Q1里发消息,等着消息过期就会转到交换机2,再会转到队列2,再就是消费者消费Q2里面的消息
package sixin_duilie;
import Gong.Channel;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
public class consume1 {
/*c1要声明两个交换机两个队列
* 死信队列是消息的三种情况会放入死信队列中
* 1.是消息ttl过时
* 2.队列已满
* 3.消息被拒绝并且不重新放入队列*/
public static final String NOM_EX="nom_ex";
public static final String DEAD_EX="dead_ex";
public static final String QUEUE1="Q1";
public static final String QUEUE2="Q2";
public static void main(String[] args) throws IOException, TimeoutException {
com.rabbitmq.client.Channel channel = Channel.getChannel();
/*声明队列 队列一要加条件 十秒过期和过期的消息转入别的交换机*/
Map<String, Object> sohm = new HashMap<>();
sohm.put("x-dead-letter-exchange",DEAD_EX);
/*设置这个交换机到的消息到哪个队列的routingkey*/
sohm.put("x-dead-letter-routing-key","lisi");
/*设置交换的routingkey*/
//sohm.put("x-message-ttl",10000);
/*成为死信的第二种方式 队列已满*/
// sohm.put("x-max-length",6);
channel.queueDeclare(QUEUE1,false,false,false,sohm);
channel.queueDeclare(QUEUE2,false,false,false,null);
/*声明交换机*/
channel.exchangeDeclare(NOM_EX, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EX, BuiltinExchangeType.DIRECT);
/*交换机与队列绑定routingkey*/
channel.queueBind(QUEUE1,NOM_EX,"zhangsan");
channel.queueBind(QUEUE2,DEAD_EX,"lisi");
DeliverCallback d=(var1,var3)->{
/*成为死信的第三种方式 消息拒收并且不返回队列 并且消费时不自动应答*/
if(new String(var3.getBody()).equals("message5")){
//拒收消息的标记 拒收后不返回队列
channel.basicReject(var3.getEnvelope().getDeliveryTag(),false);
System.out.println("正在消费Q1队列的要拒收的消息"+new String(var3.getBody()));
}else {
channel.basicAck(var3.getEnvelope().getDeliveryTag(),false);
System.out.println("正在消费Q1队列"+new String(var3.getBody()));
}
};
/*consume1消费Q1*/
channel.basicConsume(QUEUE1,false,d, CancelCallback->{});
}
}
消费者2消费死信
package sixin_duilie;
import Gong.Channel;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class consume2 {
public static final String DEAD_EX="dead_ex";
public static final String QUEUE2="Q2";
public static void main(String[] args) throws IOException, TimeoutException {
com.rabbitmq.client.Channel channel = Channel.getChannel();
DeliverCallback d=(var1,var3)->{
System.out.println("消费了"+new String(var3.getBody()));
};
channel.basicConsume(QUEUE2,true,d, CancelCallback->{});
}
}
生产者
public class produce {
public static final String NOM_EX="nom_ex";
public static final String QUEUE1="Q1";
public static void main(String[] args) throws IOException, TimeoutException {
com.rabbitmq.client.Channel channel = Channel.getChannel();
/*这个是设置发送消息到队列的有效时间*/
AMQP.BasicProperties a=new AMQP.BasicProperties().builder().expiration("1000").build();
for(int i=1;i<11;i++){
String s="message"+i;
channel.basicPublish(NOM_EX,"zhangsan",null,s.getBytes());
}
}
}
7.延迟队列
解释:
延迟队列的内部消息是有序的,最重要的是体现在他的延迟属性上,延迟队列就是希望消息在一定时间后再被消费,其实延迟队列就是死信队列的一种(消息过期)
应用场景:
订单在十分钟内未支付,则自动取消
8.整合SpringBoot,配置延迟队列
可以看RabbitMQ的讲义:
E:\动力结点java资料\RabbitMQ
代码位置:
E:\java220729\RabbitMQ\ranchiRabbitMQ
1.新建SpringBoot项目,并且配置依赖
架构图

2.编写上图的MQ配置
package com.example.yanchi_rabbitmq.confurg;
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;
@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";
/*声明队列和交换机 和绑定关系*/
/*申明交换机*/
@Bean("XexChange")
public DirectExchange getchange1(){
return new DirectExchange(X_EXCHANGE);
}
@Bean("YexChange")
public DirectExchange getchange2(){
return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
}
/*声明队列*/
@Bean("queueA")
public Queue getQueue1(){
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
/*参数为绑定死信交换机和routingkey和设置消息过期时间*/
stringObjectHashMap.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
stringObjectHashMap.put("x-dead-letter-routing-key","YD");
stringObjectHashMap.put("x-message-ttl",10000);
Queue build = QueueBuilder.durable(QUEUE_A).withArguments(stringObjectHashMap).build();
return build;
}
/*声明队列*/
@Bean("queueB")
public Queue getQueue2(){
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
/*参数为绑定死信交换机和routingkey和设置消息过期时间*/
stringObjectHashMap.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
stringObjectHashMap.put("x-dead-letter-routing-key","YD");
stringObjectHashMap.put("x-message-ttl",40000);
Queue build = QueueBuilder.durable(QUEUE_B).withArguments(stringObjectHashMap).build();
return build;
}
/*声明队列*/
@Bean("queueD")
public Queue getQueue3(){
/*队列名字*/
Queue build = QueueBuilder.durable(DEAD_LETTER_QUEUE).build();
return build;
}
/*绑定关系*/
@Bean("queueBindingA")
public Binding queueBinding1(@Qualifier("XexChange")DirectExchange exchange,
@Qualifier("queueA")Queue queue){
/*X交换机与QA绑定*/
return BindingBuilder.bind(queue).to(exchange).with("XA");
}
/*绑定关系*/
@Bean("queueBindingB")
public Binding queueBinding2(@Qualifier("XexChange")DirectExchange exchange,
@Qualifier("queueB")Queue queue){
/*X交换机与QA绑定*/
return BindingBuilder.bind(queue).to(exchange).with("XB");
}
/*绑定关系*/
@Bean("queueBindingD")
public Binding queueBinding3(@Qualifier("YexChange")DirectExchange exchange,
@Qualifier("queueD")Queue queue){
/*X交换机与QA绑定*/
return BindingBuilder.bind(queue).to(exchange).with("YD");
}
/*优化延迟队列的配置 新增一个不设置消息过时的队列 */
@Bean("queueC")
public Queue getQueue4(){
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
stringObjectHashMap.put("x-dead-letter-routing-key","YD");
return QueueBuilder.durable("QC").withArguments(stringObjectHashMap).build();
}
/*绑定*/
@Bean("queueBindingC")
public Binding bang(@Qualifier("XexChange")DirectExchange directExchange,
@Qualifier("queueC")Queue queue){
return BindingBuilder.bind(queue).to(directExchange).with("XC");
}
}
3.编写生产者controller通过URL的方式发送请求
发送给40s队列和10队列
package com.example.yanchi_rabbitmq.controller;
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.stereotype.Component;
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.nio.charset.StandardCharsets;
import java.util.Date;
@Slf4j
@RequestMapping("ttl")
@RestController
public class produce {
public static final String X_EXCHANGE = "X";
/*用springboot提供的一个类来发消息*/
@Autowired
RabbitTemplate rabbitTemplate;
// http://localhost:8080/ttl/sendMsg/嘻嘻嘻
@RequestMapping("sendMsg/{message}")
public void ceshi1(@PathVariable("message")String messgae){
log.info("当前时间:{},发送一条消息给两个TTL队列{},",new Date().toString(),messgae);
/*这个方法的参数是 交换机名,routingkey,内容*/
rabbitTemplate.convertAndSend(X_EXCHANGE,"XA",messgae.getBytes(StandardCharsets.UTF_8));
rabbitTemplate.convertAndSend(X_EXCHANGE,"XB",messgae.getBytes(StandardCharsets.UTF_8));
}
@RequestMapping("sendExpirationMsg/{message}/{ttlTime}")
public void ceshi2(@PathVariable("message")String messgae,@PathVariable("ttlTime")String ttlTime){
log.info("当前时间:{},发送一条时长{}毫秒 TTL 信息给队列 C:{}", new Date(),ttlTime, messgae);
rabbitTemplate.convertAndSend("X","XC",messgae,messag -> {
messag.getMessageProperties().setExpiration(ttlTime);
return messag;
});
}
@RequestMapping("sendDelayMsg/{message}/{delayTime}")
public void getyanchi1(@PathVariable("message")String message
,@PathVariable("delayTime")int delayTime){
log.info(" 当 前 时 间 : {}, 发 送 一 条 延 迟 {} 毫 秒 的 信 息 给 队 列 delayed.queue:{}", new
Date(),delayTime, message);
/*发送消息 设置延迟时间 这个延迟是在交换机里面延迟*/
rabbitTemplate.convertAndSend("delayed.exchange","delayed.routingkey",message, CorrelationData->{
CorrelationData.getMessageProperties().setDelay(delayTime);
return CorrelationData;
});
}
/*发布确认高级解决rabbitmq宕机消息丢失问题*/
@RequestMapping("a/{message}")
public void getgaoji(@PathVariable("message")String message){
log.info("{}发送了一条消息内容是:{}",new Date(),message);
CorrelationData correlationData = new CorrelationData();
correlationData.setId("1");
rabbitTemplate.convertAndSend("J1","K11",message,correlationData);
}
}
4.编写消费者(带有监听器)
注意Message和Channel的包名
package com.example.yanchi_rabbitmq.xiaofei;
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.IOException;
import java.util.Date;
@Slf4j
@Component
public class consume1 {
/*消费者要用监听器消费*/
@RabbitListener(queues = "QD")
public void receiveD(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody());
log.info("当前时间:{},收到死信队列信息{}", new Date().toString(), msg);
}
@RabbitListener(queues = "delayed.queue")
public void xiaofei(Message message){
String msg = new String(message.getBody());
log.info("当前时间:{},收到延时队列的消息:{}", new Date().toString(), msg);
}
/*消费高级发布确认的消费者*/
@RabbitListener(queues = "Q1")
public void xiaofei2(Message message){
log.info("当前消费Q1中的消息内容是{}",new String(message.getBody()));
}
/*写一个消费者消费警告队列里的消息*/
@RabbitListener(queues = "JQ1")
public void xiao(Message message){
log.error("警告队列的消息已经被消费 内容是:{}",new String(message.getBody()));
}
}
5.延迟队列的优化
1.需要增加一个队列Qc不设置过期时间,他消息的过期时间是由生产者决定的。
生产者:
@RequestMapping("sendExpirationMsg/{message}/{ttlTime}")
public void ceshi2(@PathVariable("message")String messgae,@PathVariable("ttlTime")String ttlTime){
log.info("当前时间:{},发送一条时长{}毫秒 TTL 信息给队列 C:{}", new Date(),ttlTime, messgae);
rabbitTemplate.convertAndSend("X","XC",messgae,messag -> {
messag.getMessageProperties().setExpiration(ttlTime);
return messag;
});
}
6.基于死信队列存在的一些问题以及解决
延迟消费要排队,导致发两条以及以上的不同过期时间的消息消费时时间不准确。(RabbitMQ只会检查第一个消息是否过期)
可以用延迟插件解决
delayed-messgae-exchange-3.8.0.eq这个插件需要看讲义安装
安装后重启RabbitMQ,在里面就会多一种交换机类型
x-delayed-message延迟消息交换机
他的原理就是:
消息不会直接到队列中,而是在交换机中延迟。
编写配置类
package com.example.yanchi_rabbitmq.confurg;
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;
@Configuration
public class delayconfug {
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("delay")
public CustomExchange getchange3(){
HashMap<String, Object> map = new HashMap<>();
/*//
自 定义交换机的类型*/
map.put("x-delayed-type","direct");
/*第一个参数为交换机的名字 第二个类型为延迟类型 是否持久化 是否自动删除 其他参数*/
return new CustomExchange(DELAYED_EXCHANGE_NAME,"x-delayed-message",false,false,map);
}
/*声明一个延迟队列*/
@Bean("getqueue5")
public Queue getqueue5(){
return QueueBuilder.durable(DELAYED_QUEUE_NAME).build();
}
/*绑定*/
@Bean("bangyi")
public Binding bangyi(@Qualifier("getqueue5")Queue queue,
@Qualifier("delay")CustomExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with(DELAYED_ROUTING_KEY).noargs();
}
}
编写生产者,基于插件的消息以及延迟时间
@RequestMapping("sendDelayMsg/{message}/{delayTime}")
public void getyanchi1(@PathVariable("message")String message
,@PathVariable("delayTime")int delayTime){
log.info(" 当 前 时 间 : {}, 发 送 一 条 延 迟 {} 毫 秒 的 信 息 给 队 列 delayed.queue:{}", new
Date(),delayTime, message);
/*发送消息 设置延迟时间 这个延迟是在交换机里面延迟*/
rabbitTemplate.convertAndSend("delayed.exchange","delayed.routingkey",message, CorrelationData->{
CorrelationData.getMessageProperties().setDelay(delayTime);
return CorrelationData;
});
}
编写消费者
@RabbitListener(queues = "delayed.queue")
public void xiaofei(Message message){
String msg = new String(message.getBody());
log.info("当前时间:{},收到延时队列的消息:{}", new Date().toString(), msg);
}
7.RabbitMQ宕机了(发布确认高级)
因为考虑到rabbitmq可能会宕机 包括交换机和队列
- 交换机没了 有一个接口专门来确定生产者的消息是不是已送达交换机RabbitTemplate.ConfirmCallback
- 队列默认交换机没找到队列对会把消息丢弃 而RabbitTemplate.returnCallback这个接口会只有当消息目的不可达才会调用这个回调函数
1.生产者发消息没有到达交换机回调 confirm
/*这个接口的回调是只要生产者发消息给交换机就会回调*/
@Override /*这个是消息的信息 生产者要发过来才有 这个是是否接收到 这个是失败原因*/
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String s= correlationData!=null?correlationData.getId():"";
log.info("这个消息的id为{}发送的结果为{},原因为{}",s,ack,cause);
}
2.消息从交换机没有到达队列回调 returnedMessage
@Override /*消息 失败码 失败原因 交换机 */
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("{}消息失败的原因是{},交换机是{},routingkey是{}",new String(message.getBody()),
replyText,exchange,routingKey);
}
3.完整的配置类(重新实现回调函数)
要把当前的实现类注入到ConfirmCallback里面,因为是内部的接口所以要注入
package com.example.yanchi_rabbitmq.ExchangehuidiaoANDQUEUE;
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;
@Component
@Slf4j
public class Exchangehuidiao implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback{
/*要把这个实现类注入到这个接口中去*/
@Autowired
RabbitTemplate rabbitTemplate;
/*消息发入队列失败也要注入*/
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
/*这个接口的回调是只要生产者发消息给交换机就会回调*/
@Override /*这个是消息的信息 生产者要发过来才有 这个是是否接收到 这个是失败原因*/
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
String s= correlationData!=null?correlationData.getId():"";
log.info("这个消息的id为{}发送的结果为{},原因为{}",s,ack,cause);
}
@Override /*消息 失败码 失败原因 交换机 */
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("{}消息失败的原因是{},交换机是{},routingkey是{}",new String(message.getBody()),
replyText,exchange,routingKey);
}
}
生产者发送
回调函数中的CorrelationData是要生产者发送过来才会有
/*发布确认高级解决rabbitmq宕机消息丢失问题*/
@RequestMapping("a/{message}")
public void getgaoji(@PathVariable("message")String message){
log.info("{}发送了一条消息内容是:{}",new Date(),message);
CorrelationData correlationData = new CorrelationData();
correlationData.setId("1");
rabbitTemplate.convertAndSend("J1","K11",message,correlationData);
}
使用回调函数还需要再配置文件中进行一个配置
# 配置生产者发消息给交换机的确认类型回调
spring.rabbitmq.publisher-confirm-type=correlated
# 默认是NONE 禁用发布确认模式
# SIMPLE 同步确定
但是加入是消息在交换机里不可路由到队列,那消息会被交换机丢弃,这样生产者不会知道。 解决:
通过设置Mandatory参数:可以当消息传递过程中不可达目的地的消息会返回到生产者。
1.在配置文件中进行配置
spring.rabbitmq.publisher-returns=true
2.回调接口中实现ReturnCallback
@Override /*消息 失败码 失败原因 交换机 */
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("{}消息失败的原因是{},交换机是{},routingkey是{}",new String(message.getBody()),
replyText,exchange,routingKey);
}
3668

被折叠的 条评论
为什么被折叠?



