1、中间件消息传递可能出现的问题
从上图我们可以看到,使用生产者、消息中间件、消费者在进行消息传递时会涉及很多重要的中间环节的处理,如果这些中间环节不够健壮,就无法保证消息的正常传递、处理、保存、及响应。
做一下思维发散, 上图中那些环节可能会出现问题,具体是什么问题?
- 1、生产者消息一定能成功发送到交换机麽?
- 2、交换机中的消息一定可以路由到Queue麽?
- 3、Queue中的消息如果无法持久,服务异常或者网络中断等故障发生后, 服务恢复正常之后,消息是不是就丢了?
- 4、如何保证消费者成功消费了Queue中的消息呢?
2、RabbitMQ是处理上述问题的机制
2.1 生产者如何保证消息一定成功发送到Exchange
之前有一种方式就是这个Publisher Confirms我们没有介绍, 这种就是生产者确认的方式保证我生产者一定可以知道消息是不是成功的发送到了交换机,就算发送失败了, 我也可以通过补偿机制进行处理,RabbitMQ提供了以下3种具体的实现策略:
核心代码就是需要开启Publisher的confirm机制,可以通过Confirm效果保证消息一定送达到Exchange,方式上推荐对于效率影响最低的异步回调方式
// 开启confirm机制
channel.confirmSelect();
// 监听器监听是否发送成功并进行处理
channel.addConfirmListener(new ConfirmListener() {
// Exchange确认消息收到
@Override
public void handleAck(long l, boolean b) throws IOException {
System.out.println("消息已成功收到...");
}
// Exchange未收到消息
@Override
public void handleNack(long l, boolean b) throws IOException {
System.out.println("没收到消息,怎么回事,赶紧处理一下吧...");
}
});
2.2 Exchange保证消息一定路由到Queue
// 开启returns机制, 使用异步回调通知交换机消息是否接收成功
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
System.out.println("消息路由到Queue失败,请尽快处理...");
}
});
// 发送消息
2.3 Queue中消息的持久化存储
队列中消息如何不能持久化存储,当发生服务异常、宕机等情况, 服务恢复之后消息肯定就丢了, 数据丢失是不能接受的, rabbitMQ提供了开启队列持久化存储的属性,主要是通过AMQP.BasicProperties来进行设置是否开启持久化存储。
// 9、设置Queue开启持久化存储
AMQP.BasicProperties props = new AMQP.BasicProperties()
.builder()
.deliveryMode(2)
.build();
2.4 消费者正常消费Queue中的消息
关闭自动ack, 使用异步回调进行消息是否成功消费的ack确认。
DefaultConsumer callBack = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费queue-1队列的消息: " + new String(body, "UTF-8"));
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(Producer.QUEUE_NAME_ONE, false, callBack);
3、Springboot项目中如何进行rabbitMQ消息可靠性的处理
3.1 Springboot中如何处理Pulisher confirm
首先在application.yml文件中配置publisher-confirm-type为correlated
spring:
rabbitmq:
# 开启publisher confirm机制
publisher-confirm-type: correlated
编写生产者测试类,进行测试
package com.kkarma.confirm;
import com.kkarma.config.RabbitMQConfig;
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 Publisher {
private final RabbitTemplate rabbitTemplate;
@Autowired
public Publisher(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
@Test
public void push() throws Exception {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
if(b){
System.out.println("消息已经送达到交换机!!");
}else{
System.out.println("消息没有送达到Exchange,请尽快处理...");
}
}
});
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "com.kkarma.controller", "msg push need confirm");
System.out.println("message send success......");
// System.in.read();
}
}
3.2 Springboot中如何处理queue return
application.yml文件中配置信息
spring:
rabbitmq:
# 开启Return机制
publisher-returns: true
编写生产者测试类,进行测试
package com.kkarma.returns;
import com.kkarma.config.RabbitMQConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
@SpringBootTest
public class Publisher {
private final RabbitTemplate rabbitTemplate;
@Autowired
public Publisher(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
@Test
public void push() throws Exception {
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
String msg = message.getBody().toString();
System.out.println("消息: " + msg + "路由到queue失败,请尽快处理");
}
});
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,
"my.com.kkarma.controller",
"Exchange消息路由到queue,需要返回接收结果...");
System.out.println("消息发送成功。。。");
System.in.read();
}
}
测试结果
3.3 Springboot中如何处理queue消息持久化
在生产者中借助RabbitTemplate的convertAndSend方法中的MessagePostProcessor参数属性来开启持久化
package com.kkarma.persistence;
import com.kkarma.config.RabbitMQConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class Publisher {
private final RabbitTemplate rabbitTemplate;
@Autowired
public Publisher(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
@Test
public void push() {
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties props = message.getMessageProperties();
// 设置queue消息持久化
props.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
}
};
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "con.kkarma.controller", "开启信息持久化", processor);
System.out.println("消息发送成功。。。");
}
}
3.4 Springboot中如何处理消费正常消费消息
application.yml中开启手动ack
spring:
rabbitmq:
# 开启消费者手动ack
listener:
direct:
acknowledge-mode: manual
# 设置消息流控
simple:
prefetch: 10
# concurrency: 5
# 设置消费者并发数
# max-concurrency: 5
消费者端代码实现
package com.kkarma.consumer;
import com.kkarma.config.RabbitMQConfig;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Consumer {
private final RabbitTemplate rabbitTemplate;
@Autowired
public Consumer(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
@RabbitListener(queues = RabbitMQConfig.QUEUE_NAME)
public void pull(String msg, Channel channel, Message message) throws Exception {
System.out.println("消费消息内容: " + msg);
String correlationId = message.getMessageProperties().getCorrelationId();
System.out.println("唯一标识: " + correlationId);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
System.in.read();
}
}