消息可靠性涉及方面:
1、发送端确定机制
2、持久化存储机制:队列持久化、交换器持久化、消息持久化
3、消费端Ack机制
4、消费端限流
消息发送端确定机制:
以springboot进行实例演示,原生API自行查找了解,springboot整合参考:https://blog.csdn.net/qq_28326501/article/details/115509447
消息发送端只需修改配置项和controller即可。其余不涉及修改。
修改配置项:
添加配置项
spring.rabbitmq.publisher-confirm-type=correlated
spring.rabbitmq.publisher-returns=true
发送Controller类
package com.cc.rabbitmq.controller;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.UnsupportedEncodingException;
@RestController
public class RabbitBizController {
private RabbitTemplate rabbitTemplate;
@Autowired
public void setRabbitTemplate(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
//correlationData:消息体
//flag:是否确认
//cause:信息
this.rabbitTemplate.setConfirmCallback((correlationData, flag, cause) -> {
if(flag){
try {
System.out.println("确认消息"+correlationData.getId()
+ new String(correlationData.getReturnedMessage().getBody(), "utf-8") );
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}else {
System.out.println(cause);
}
});
}
@RequestMapping("/sendBiz")
public String sendMessage() throws UnsupportedEncodingException {
MessageProperties props = new MessageProperties();
props.setCorrelationId("1234");
props.setConsumerTag("ccmsg");
//消息类型
props.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN);
props.setContentEncoding("utf-8");
CorrelationData cd = new CorrelationData();
cd.setId("ccmsg");
cd.setReturnedMessage(new Message("这是msg1的响应".getBytes("utf-8"), null));
Message message = new Message("这是等待确认的消息".getBytes("utf-8"), props);
rabbitTemplate.convertAndSend("my.ex", "direct.biz2.ex", message, cd);
return "ok";
}
}
示例:
使用浏览器请求
发送端:
消费端:
持久化存储机制:
持久化是提高RabbitMQ可靠性的基础,否则当RabbitMQ遇到异常时(如:重启、断电、停机 等)数据将会丢失。主要从以下几个方面来保障消息的持久性:
1、交换器Exchange的持久化。通过定义时设置durable 参数为ture来保证Exchange相关的元数据不不丢失。
2、队列Queue的持久化。通过定义时设置durable 参数为ture来保证Queue相关的元数据不不丢失。
3、消息的持久化。通过将消息的投递模式 (BasicProperties 中的 deliveryMode 属性)设置为 2 即可实现消息的持久化,保证消息自身不丢失。
示例:
交换器持久化设置:
队列持久化设置:
消费端Ack机制:
abbitMQ在消费端会有Ack机制,即消费端消费消息后需要发送Ack确认报文给Broker端,告知自 己是否已消费完成,否则可能会一直重发消息直到消息过期(AUTO模式)。
一般而言,我们有如下处理手段:
1. 采用NONE模式,消费的过程中自行捕获异常,引发异常后直接记录日志并落到异常恢复表, 再通过后台定时任务扫描异常恢复表尝试做重试动作。如果业务不自行处理则有丢失数据的风险
2. 采用AUTO(自动Ack)模式,不主动捕获异常,当消费过程中出现异常时会将消息放回 Queue中,然后消息会被重新分配到其他消费者节点(如果没有则还是选择当前节点)重新被消费,默认会一直重发消息并直到消费完成返回Ack或者一直到过期
3. 采用MANUAL(手动Ack)模式,消费者自行控制流程并手动调用channel相关的方法返回 Ack
修改配置文件:
#最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=5
#是否开启消费者重试(为false时关闭消费者重试,意思不是“不重试”,而是一直收到消息直到jack确认或者一直到超时)
spring.rabbitmq.listener.simple.retry.enabled=true
#重试间隔时间(单位毫秒)
spring.rabbitmq.listener.simple.retry.initial-interval=5000
# 重试超过最大次数后是否拒绝
spring.rabbitmq.listener.simple.default-requeue-rejected=false
#ack模式
spring.rabbitmq.listener.simple.acknowledge-mode=manual
处理类:
package com.cc.rabbitmq_consumer.consumer;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Random;
@Component
public class MyMessageListener {
private Random random = new Random();
/**
* NONE模式,则只要收到消息后就立即确认(消息出列,标记已消费),有丢失数据的风险
* AUTO模式,看情况确认,如果此时消费者抛出异常则消息会返回到队列中
* MANUAL模式,需要显式的调用当前channel的basicAck方法
* @param message
*/
@RabbitListener(queues = "my.queue3", ackMode = "MANUAL")
public void getMessage3(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, @Payload String message){
System.out.println("消息内容:"+message);
try {
//随机获取一个数,随机模拟处理失败
if (random.nextInt(10) % 3 != 0){
// 手动nack,告诉broker消费者处理失败,最后一个参数表示是否需要将消息重新入列列
// channel.basicNack(deliveryTag, false, true);
// 手动拒绝消息。第二个参数表示是否重新入列
channel.basicReject(deliveryTag, true);
}else {
// 手动ack,deliveryTag表示消息的唯一标志,multiple表示是否是批量确认
channel.basicAck(deliveryTag, false);
System.err.println("已确认消息:" + message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}