目录
MQ常见问题
消息可靠性问题
生产者消息确认
如何确定消息唯一性:
Publisher发送消息的配置
1.全局配置一个配置类,完成对消息由交换机发送到队列的监听
2.发送消息,指定消息的id,消息的ConfirmCallback
SpringAMQP处理消息确认的几种情况:
MQ把消息弄丢的处理
消费者消息的确认
我们可以利用Spring的retry机制
消费者消息处理失败策略
这个文章不错
(30条消息) RabbitMQ之消息可靠性问题(含Demo工程)_一切总会归于平淡的博客-CSDN博客_rabbitmq不可靠
MQ常见问题
消息可靠性问题:需要保证消息一定要被消费
延迟消息问题:消息延迟投递的实现,比如多久之后开始被处理,后面还会滋生消息堆积问题
消息堆积问题:百万消息堆积如何被消费
高可用问题:如何避免单点MQ故障而导致不可用问题
消息可靠性问题
1.发送时丢失:生产者发送消息未到交换机Exchange就丢失,交换机根据RoutingKey路由到Queue途中丢了消息
2.消息到达了队列中,但是MQ宕机,由于MQ是基于内存存储,消息保存内存中的,一宕机就没了
3.消费者接受到消息还没消费就宕机
生产者消息确认
MQ提供了pushlisher confirm机制来避免消息发送到MQ过程中丢失——>当消息发送到MQ以后,会返回一个结果给到publisher表示消息是否处理成功
1.publisher-confirm:发送者确认
消息成功投递到交换机,返回ack
消息未投递到交换机,返回nack
2.publisher-return:发送者回执
消息投递到交换机,但是没有路由到队列。返回ACK并且含有失败原因
确认机制发送消息时,会给每一个消息设定一个全局唯一id,以此区分不同消息
1.publish-confirm-type: 消息发送确认同步/异步
2.publish-returns:开启publish-return功能,发送回执(这里用的是ReturnCallback)
3.template.mandatory:定义消息路由失败的策略,true:调用ReturnCallback
SpringAmQP实现生产者确认,也就是回调
这里失败后记录日志或者重试都可以,ApplicationAware相当于全局应用的一个通知
这里配置的是一个ReturnCallback,也就是路由到队列
我们这里交换机绑定了队列
package cn.itcast.mq.config;
import cn.itcast.mq.admin.MqConstants;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author diao 2022/5/26
*/
@Configuration
public class MqConfig {
/**
* 得到一个交换机定义
*/
@Bean
public TopicExchange topicExchange() {
//第二个是保证交换机持久化
return new TopicExchange(MqConstants.EXCHANGE, true, false);
}
/**
* 定义两个消息队列
*/
@Bean
public Queue insertQueue() {
return new Queue(MqConstants.QUEUE, true);
}
/**
* 将交换机与消息队列进行绑定
*/
@Bean
public Binding insertQueueBinding() {
return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(MqConstants.KEY);
}
}
SpringAMQP_Fairy要carry的博客-CSDN博客
package cn.itcast.mq.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class CommonConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 获取RabbitTemplate对象
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
// 配置ReturnCallback
rabbitTemplate.setReturnCallback(((message, replyCode, replyText, exchange, routingKey) ->{
//记录日志
log.error("消息发送队列失败,响应码:{},失败原因:{},交换机:{},路由key:{},消息:{}",
replyCode,replyText,exchange,routingKey,message.toString());
//重发消息
rabbitTemplate.convertAndSend("camq.topic",routingKey,"push again");
} ));
}
}
三种情况:1.消息发送到Exchange,返回ack,2.消息发送失败,没有稻交换机,返回nack
3.消息发送出现异常没有收到回执
这里对比于上面封装了消息ID
package cn.itcast.mq.spring;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.CorrelationDataPostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.concurrent.FailureCallback;
import org.springframework.util.concurrent.SuccessCallback;
import java.util.UUID;
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage2SimpleQueue() throws InterruptedException {
//1.消息和key
String routingKey = "insert";
String message = "hello, spring amqp!";
//2.准备CorrelationData ,封装消息id唯一性
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
correlationData.getFuture().addCallback(result -> {
//2.1投递消息成功
if (result.isAck()) {
log.debug("消息投递到交换机成功,消息ID:{}", correlationData.getId());
} else {
//2.2投递消息失败
log.error("消息投递到交换机失败,消息ID:{}", correlationData.getId());
}
}, throwable -> log.error("消息发送失败",throwable));
rabbitTemplate.convertAndSend("camq.topic", routingKey, message,correlationData);
}
}
结果:
没有到达指定队列
SpringAMQP处理消息确认的几种情况:
MQ把消息弄丢的处理
1.首先我们模拟一下,在消费者服务中创建一个交换机和一个消息队列,监听队列消息
@Bean
public DirectExchange simpleDirect(){
return new DirectExchange("simple.direct",true,false);
}
@Bean
public Queue simpleQueue(){
return QueueBuilder.durable("simple.queue").build();
}
2.然后给simple.queue发个消息,然后将mq关闭重启看是否消息持久化
3.发现消息是不能持久化的,所以我们需要用MessageProperties进行处理
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage2SimpleQueue() throws InterruptedException {
//1.消息和key
String routingKey = "insert";
String message = "hello, spring amqp!";
//2.准备CorrelationData ,封装消息id唯一性
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
correlationData.getFuture().addCallback(result -> {
//2.1投递消息成功
if (result.isAck()) {
log.debug("消息投递到交换机成功,消息ID:{}", correlationData.getId());
} else {
//2.2投递消息失败
log.error("消息投递到交换机失败,消息ID:{}", correlationData.getId());
}
}, throwable -> log.error("消息发送失败", throwable));
rabbitTemplate.convertAndSend("camq.topic", routingKey, message, correlationData);
}
/**
* 持久化发信息
*/
@Test
public void testDurableMessage() {
//1.准备信息
Message message = MessageBuilder.withBody("hello,spring".getBytes(StandardCharsets.UTF_8))
.setDeliveryMode(MessageDeliveryMode.PERSISTENT)
.build();
//2.发送消息
rabbitTemplate.convertAndSend("simple.queue",message);
}
}
重启mq后, 发现消息持久化了
4.消息持久化细节处理
会发现队列默认处理时直接持久化的
然后我们看看发送消息的方法convertAndSend(),看看是怎么处理message的
然后看看Message的处理,也就是convertMessageIfNecessary()方法
发现传入MessageProperties作属性
发现默认就是持久化的
消费者消息的确认
MQ三种确认消息模式:
manual:全部执行完,自己手写返回,抛出异常返回nack也得自己手写,在代码中会有切入现象
auto:Spring的aop思想,减少了代码的侵入,Spring确认消息处理后返回ack
none:就是异步思想呗,就跟外卖一样的不管你真正拿到没,mq会假设消费者获取消息后一定会成功处理,所以消息被pulisher投递后就会立马删除,因为他默认就是收到了consumer处理成功的消息;
我们这里使用auto模式,发现消费者在处理消息时候,(异常出现之前)是Unacked状态,意思就是MQ还没有收到处理成功的回执,然后执行到异常会疯狂自旋(消费者出现异常,消息不断重新入队发送给消费者然后再次异常,导致mq的消息处理up,压力大),一直请求——>好处:没有导致消息的浪费,一直自旋没有返回给MQack的回执,就还是不能删除消息
意思就是不返回任何信息给到mq(unacked,ack....),而是自己做重试——>到到达一定的限制在做其他策略
配置开启retry
spring:
rabbitmq:
host: 192.168.184.129 # rabbitMQ的ip地址
port: 5672 # 端口
username: itcast
password: 123321
virtual-host: /
listener:
simple:
prefetch: 1
acknowledge-mode: auto
retry:
enabled: true
initial-interval: 1000
multiplier: 3
max-attempts: 4
max-interval: 4
结果:发现最大四次,超过之后拒绝,消息依然失败的话(Retry exhausted),就会删除该消息
开启retry模式若还失败则有三种实现方式——>1.RejectAndDontRequeueRecover:资源耗尽后直接丢弃消息(默认),2.ImmediateRequeueMessage:资源耗尽后,返回nack给到mq,让消息重新入队(自旋),3.RepublishMessageRecover:重试耗尽后,将失败的消息投递到指定的交换机
需要定义一个专门储存失败消息的交换机和队列
然后重写MessageRecover这个Bean,返回子类RepublishMessageRecover,将消息发送到交换机路由到指定队列中
/**
* @author diao 2022/6/18
*/
@Configuration
public class ErrorMessageConfig {
@Bean
public DirectExchange errorMessageExchange(){
return new DirectExchange("error,direct");
}
@Bean
public Queue errorQueue(){
return new Queue("error.queue");
}
@Bean
public Binding errorMessageBinding(){
return BindingBuilder.bind(errorQueue()).to(errorMessageExchange()).with("error");
}
@Bean
public MessageRecoverer republishMessage(RabbitTemplate rabbitTemplate){
return new RepublishMessageRecoverer(rabbitTemplate,"error.direct","error");
}
}
重启消费者服务,再在监听的消息队列中发送消息
发现消费者将消息路由到一个新的交换机中,给到一个消息队列