SpringBoot整合RabbitMQ
参考以下博主的文章
RabbitMQ入门 安装 SpringAMQP简单队列、工作队列、发布订阅(扇出模式,广播模式)、Direct模式(Roting模式)、Topic模式
RabbitMQ(2)、MQ问题:消息可靠性、延迟消息( 延迟队列(插件 ))、消息堆积(惰性队列)、MQ的高可用。ConfirmCallback机制、ReturnCallback机制、死信交换机
我这里只会记录如何整合SpringBoot,安装和部署的具体详情可以看上面这位博主写的文章。
一、MQ解决什么问题
解决问题:实现异步、削峰、解耦
常见问题:
消息可靠性问题:如何确保消息被成功送达消费者,并且被消费者成功消费掉
延迟消息问题:如果一个消息,需要延迟15分钟再消费,像12306超时取消订单,如何实现消息的延迟投递
(如果你的消息设置的延迟时间超过MQ的极限,消息会立刻给消费掉的。消息过期时间必须是非负32为整数,即:0<=n<=2^32-1,单位(毫秒) 。2^32-1=4294967295 约等于49天左右)
消息堆积问题:如果消息无法被及时消费而堆积,如何解决百万级消息堆积的问题
MQ的高可用问题:如何避免MQ因为单点故障而不可用的问题
二、MQ的问题与解决方案
环节一 :生产者 -> 交换机
环节二 :交换机 -> 队列
环节三 : 队列 ->消费者
如果以上其中一个环节出了问题,消息就无法到达消费者
生产者发送消息可能出现的问题:
- 生产者发送的消息未送达交换机
- 生产者发送的消息到达交换机,但未到达队列
MQ收到消息后丢失的问题:
- MQ宕机,导致未持久化保存消息丢失
- 消费者接收消息后,尚未消费MQ就宕机
MQ对应的解决方案有以下:
- 生产者发送消息丢失:使用生产者确认机制
- MQ接收消息丢失:MQ消息持久化
- 消费者接收消息丢失:消费者确认机制与失败重试机制
三、生产者确认
生产者 -> 交换机 -> 队列
通过ConfirmCallback和ReturnCallback才能确认消息是否到达队列
1、ConfirmCallback(确认消息是否到达交换机)
application.yml添加配置publisher-confirm-type配置,确认机制异步还是同步,自己根据业务决定
模拟生产方确认消息是否到达交换机
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class ConfirmCallbackConfig {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void test(){
//创建一个CorrelationData Correlation:关联
CorrelationData correlationData = new CorrelationData();
//设置消息的id
correlationData.setId("msg-888888");
//准备好 回调函数:Callback,用于接收 将来:Future 的确认结果
correlationData.getFuture().addCallback(
/**
* 当消息发送没有出现异常时,这个方法会被调用
*/
result -> {
if (result.isAck()){
System.out.println("发送消息成功,消息已到达交换机,消息id:"+correlationData.getId());
}else {
System.out.println("发送消息成功,消息没有到达交换机,消息id:"+correlationData.getId()+" 原因:"+result.getReason());
} },
/**
* 当消息发送出现异常时,这个方法会被调用
*/
ex -> {
System.out.println("发送消息异常,消息id:"+correlationData.getId()+" 异常:"+ex);
}
);
//自己随便定一个没有的交换机
rabbitTemplate.convertAndSend("xxxx.exchange", "demo", "到达交换机了吗",correlationData);
}
}
名称为xxxx.exchange的交换机我没有
消息没有送达指定交换机
消息送达指定交换机
2、ReturnCallback(确认消息是否到达队列)
application.yml新增ReturnCallback的配置,写在生产者这边,我生产者和消费者都是同一个服务
创建ReturnCallback配置类,统一管理
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class RabbitReturnCallBackConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
//当交换机把消息路由到队列出现问题时,这个方法会自动执行
log.warn("把消息路由到队列失败,replyCode{},replyText{},RoutingKey={},msg={}", replyCode,replyText,exchange,routingKey, message);
});
}
}
定义交换机、队列
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 需要:
* 1. 声明Direct类型的交换机:direct.exchange
* 2. 声明队列1:direct.queue1
* 声明队列2:direct.queue2
* 3. 把交换机与队列1绑定:把 RoutingKey为orange的消息 -> 投递到队列1
* 把交换机与队列2绑定:把 RoutingKey为black的消息 -> 投递到队列2
* 把交换机与队列2绑定:把 RoutingKey为green的消息 -> 投递到队列2
*
* ↗ RoutingKey = orange -> direct.queue1 -> C(消费者1)
* P(生产者) -> direct.exchange
* ↘ RoutingKey = black
* -> direct.queue2 -> C(消费者2)
* ↘ RoutingKey = green
*
*/
@Configuration
public class DirectQueueConfig {
//定义direct类型交换机
@Bean
public DirectExchange directExchange() {
return ExchangeBuilder.directExchange("direct.exchange").build();
}
//定义持久化队列
@Bean
public Queue directQueue1() {
return new Queue("direct.queue1",true,false,false);
}
//定义持久化队列
@Bean
public Queue directQueue2() {
return QueueBuilder.durable("direct.queue2").build();
}
//把交换机与队列direct.queue1绑定:把 RoutingKey为orange的消息,投递到队列direct.queue1
@Bean
public Binding directQueue1Binding(Queue directQueue1, DirectExchange directExchange) {
return BindingBuilder.bind(directQueue1).to(directExchange).with("orange");
}
//把交换机与队列direct.queue2绑定:把 RoutingKey为black的消息,投递到队列direct.queue2
@Bean
public Binding directQueue2BindingInfo(Queue directQueue2, DirectExchange directExchange) {
return BindingBuilder.bind(directQueue2).to(directExchange).with("black");
}
//把交换机与队列direct.queue2绑定:把 RoutingKey为green的消息,投递到队列direct.queue2
@Bean
public Binding direcQueue2BindingError(Queue directQueue2, DirectExchange directExchange) {
return BindingBuilder.bind(directQueue2).to(directExchange).with("green");
}
}
模拟生产者
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class ReturnCallBackTest {
@Autowired
private RabbitTemplate rabbitTemplate;
//模拟生产者
@Test
public void test(){
//rabbitTemplate.convertAndSend("direct.exchange","orange","这是orange");
//rabbitTemplate.convertAndSend("direct.exchange","black","这是black");
//rabbitTemplate.convertAndSend("direct.exchange","green","这是green");
//没有绑定routingkey=xxxx的队列
rabbitTemplate.convertAndSend("direct.exchange","xxxx","这是green");
}
}
捕获到消息发送失败了