消息丢失
[服务器-持久化]
将内存的数据(交换器 Exchange、队列 Queue、消息 Message)存储到硬盘中,防止 RabbitMQ 宕机导致数据丢失。
[生产者-发布确认]
[消费者-消息应答]
消费者完成一个任务需要一段时间,如果其中一个消费者处理一个长任务并且仅仅完成了部分,此时它宕机了,那么我们将丢失正在处理的消息,以及后续发送给该消费者的消息。
为了保证消息在发送过程中不丢失,RabbitMQ 引入了消息应答机制:消费者处理完成该消息后通知 RabbitMQ,RabbitMQ 才可以删除该消息。
消息应答分为手动应答和自动应答。
[手动应答](推荐)
boolean autoAck = false;
将消息发送给消费者并且等待处理完成后,才会删除队列中的消息。
如果消费者由于某些原因导致消息未发送 ACK 确认,RabbitMQ 会将消息放回队列,将其重新分发给其他消费者处理,确保不会丢失任何消息。
手动应答支持批量应答(multiple),但不推荐使用。
public class Consumer {
channel.basicAck(long deliveryTag, boolean multiple)
channel.basicNack(long deliveryTag, boolean multiple, boolean requeue)
channel.basicReject(long deliveryTag, boolean requeue)
[自动应答]
boolean autoAck = true;
只要将消息发送给消费者就删除队列中的消息,不管是否处理完成。
默认采用自动应答,所以要想实现消息不丢失,需要改为手动应答。
消息堆积
消息被拒绝后不再重新入队,而是使用死信队列接收。
消息幂等性
https://help.aliyun.com/document_detail/177412.html
在互联网应用中,尤其在网络不稳定的情况下,消息队列 RabbitMQ 版的消息有可能会出现重复。
1、发送时消息重复
当一条消息已被成功发送到服务端并完成持久化,此时出现了网络闪断或者客户端宕机,导致服务端对客户端应答失败。
如果此时 Producer 意识到消息发送失败并尝试再次发送消息,Consumer 后续会收到两条内容相同并且 Message ID 也相同的消息。
2、投递时消息重复
消息消费的场景下,消息已投递到 Consumer 并完成业务处理,当客户端给服务端反馈应答的时候网络闪断。
为了保证消息至少被消费一次,消息队列 RabbitMQ 版的服务端将在网络恢复后再次尝试投递之前已被处理过的消息,Consumer 后续会收到两条内容相同并且 Message ID 也相同的消息。
3、负载均衡时消息重复(包括但不限于网络抖动、服务端重启以及 Consumer 应用重启)
当消息队列 RabbitMQ 版的服务端或客户端重启、扩容或缩容时,会触发 Rebalance,此时 Consumer 可能会收到重复消息。
setnx
消息顺序性

对于 RabbitMQ 来说,导致上面顺序错乱的原因通常是消费者是集群部署,不同的消费者消费到了同一订单的不同的消息,如消费者 A 执行了增加,消费者 B 执行了修改,消费者 C 执行了删除,但是消费者 C 执行比消费者 B 快,消费者 B 又比消费者 A 快,就会导致消费 binlog 执行到数据库的时候顺序错乱,本该顺序是增加、修改、删除,变成了删除、修改、增加。

RabbitMQ 的问题是由于不同的消息都发送到了同一个 queue 中,多个消费者都消费同一个 queue 的消息。解决这个问题,我们可以给 RabbitMQ 创建多个 queue,每个消费者固定消费一个 queue 的消息,生产者发送消息的时候,同一个订单号的消息发送到同一个 queue 中,由于同一个 queue 的消息是一定会保证有序的,那么同一个订单号的消息就只会被一个消费者顺序消费,从而保证了消息的顺序性。
死信队列
死信,即无法消费的消息。
通常来源于:
1、消息过期
messageProperties.setExpiration("5000");
2、消息超出队列长度
maxLength(5)
3、消息被拒绝
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
延迟队列
=====
下载安装
docker pull docker.io/rabbitmq:3.8-management
docker run -d -it \
--name rabbitmq5672 \
--restart=always \
--privileged=true \
-p 15672:15672 \
-p 5672:5672 \
docker.io/rabbitmq:3.8-management
docker exec -it rabbitmq5672 /bin/bash
rabbitmqctl add_user root rabbitmq
rabbitmqctl set_permissions -p / root ".*" ".*" ".*"
rabbitmqctl set_user_tags root administrator
exit
wget https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/v3.8.0/rabbitmq_delayed_message_exchange-3.8.0.ez
docker cp rabbitmq_delayed_message_exchange-3.8.0.ez rabbitmq5672:/opt/rabbitmq/plugins/
rm -rf rabbitmq_delayed_message_exchange-3.8.0.ez
docker exec -it rabbitmq5672 /bin/bash
cd /opt/rabbitmq/plugins/
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
exit
docker restart rabbitmq5672
访问 http://vmwarehost:15672/
账号 root 密码 rabbitmq
综合案例
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring:
rabbitmq:
host: vmwarehost
port: 5672
username: root
password: rabbitmq
listener:
simple:
acknowledge-mode: manual
default-requeue-rejected: false
direct:
acknowledge-mode: manual
default-requeue-rejected: false
template:
mandatory: true
publisher-confirm-type: correlated
publisher-returns: true
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;
import java.util.Map;
@Configuration
public class RabbitMQConfig {
@Bean("exchange01")
public TopicExchange exchange01() {
return ExchangeBuilder
.topicExchange("exchange01")
.durable(true)
.build();
}
@Bean("exchange_dead")
public TopicExchange exchangeDead() {
return ExchangeBuilder
.topicExchange("exchange_dead")
.durable(true)
.build();
}
@Bean("exchange_delayed")
public CustomExchange exchangeDelayed() {
Map<String, Object> arguments = new HashMap<>(1);
arguments.put("x-delayed-type", "topic");
return new CustomExchange("exchange_delayed", "x-delayed-message", true, false, arguments);
}
@Bean("queue01")
public Queue queue01() {
return QueueBuilder
.durable("queue01")
.deadLetterExchange("exchange_dead")
.deadLetterRoutingKey("dead")
.maxLength(5)
.build();
}
@Bean("queue02")
public Queue queue02() {
return QueueBuilder
.durable("queue02")
.deadLetterExchange("exchange_dead")
.deadLetterRoutingKey("dead")
.maxLength(5)
.build();
}
@Bean("queue_dead")
public Queue queueDead() {
return QueueBuilder
.durable("queue_dead")
.build();
}
@Bean("queue_delayed")
public Queue queueDelayed() {
return QueueBuilder
.durable("queue_delayed")
.deadLetterExchange("exchange_dead")
.deadLetterRoutingKey("dead")
.maxLength(5)
.build();
}
@Bean
public Binding bindingQueue01AndExchange01(@Qualifier("queue01") Queue queue, @Qualifier("exchange01") TopicExchange topicExchange) {
return BindingBuilder.bind(queue).to(topicExchange).with("*.abc");
}
@Bean
public Binding bindingQueue02AndExchange01(@Qualifier("queue02") Queue queue, @Qualifier("exchange01") TopicExchange topicExchange) {
return BindingBuilder.bind(queue).to(topicExchange).with("abc.#");
}
@Bean
public Binding bindingQueueDeadAndExchangeDead(@Qualifier("queue_dead") Queue queue, @Qualifier("exchange_dead") TopicExchange topicExchange) {
return BindingBuilder.bind(queue).to(topicExchange).with("dead");
}
@Bean
public Binding bindingQueueDelayedAndExchangeDelayed(@Qualifier("queue_delayed") Queue queue, @Qualifier("exchange_delayed") CustomExchange customExchange) {
return BindingBuilder.bind(queue).to(customExchange).with("delayed").noargs();
}
}
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
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 javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/producer")
public class Producer implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
@Resource
private RabbitTemplate rabbitTemplate;
@GetMapping("/send/{msg}")
public void send(@PathVariable String msg) {
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
rabbitTemplate.convertAndSend("exchange01", "1.abc", msg.getBytes(StandardCharsets.UTF_8),
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties messageProperties = message.getMessageProperties();
messageProperties.setExpiration("5000");
return message;
}
}
);
rabbitTemplate.convertAndSend("exchange01", "abc.123", msg.getBytes(StandardCharsets.UTF_8),
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties messageProperties = message.getMessageProperties();
messageProperties.setExpiration("5000");
return message;
}
}
);
rabbitTemplate.convertAndSend("exchange_delayed", "delayed", msg.getBytes(StandardCharsets.UTF_8),
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties messageProperties = message.getMessageProperties();
messageProperties.setDelay(10000);
return message;
}
}
);
rabbitTemplate.convertAndSend("exchange_delayed", "delayed", msg.getBytes(StandardCharsets.UTF_8),
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties messageProperties = message.getMessageProperties();
messageProperties.setDelay(5000);
return message;
}
}
);
System.out.println(LocalDateTime.now() + " 发送消息: " + msg);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
final String prefix = "[生产者] 发布到 [交换机] 的确认";
System.out.println(prefix + " correlationData: " + correlationData);
System.out.println(prefix + " ack: " + ack);
System.out.println(prefix + " cause: " + cause);
}
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
final String prefix = "[交换机] 发布到 [队列] 的确认";
System.out.println(prefix + " message: " + new String(message.getBody(), StandardCharsets.UTF_8));
System.out.println(prefix + " replyCode: " + replyCode);
System.out.println(prefix + " replyText: " + replyText);
System.out.println(prefix + " exchange: " + exchange);
System.out.println(prefix + " routingKey: " + routingKey);
}
}
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
@Component
public class Consumer {
@RabbitListener(queues = "queue01")
public void handleMessageFromQueue01(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
if ("abc".equals(msg)) {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("接收 queue01 消息: " + msg);
} else {
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}
@RabbitListener(queues = "queue02")
public void handleMessageFromQueue02(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
if (!"abc".equals(msg)) {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("接收 queue02 消息: " + msg);
} else {
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}
@RabbitListener(queues = "queue_dead")
public void handleMessageFromQueueDead(Message message, Channel channel) throws IOException {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println("接收 queue_dead 消息: " + msg);
}
@RabbitListener(queues = "queue_delayed")
public void handleMessageFromQueueDelayed(Message message, Channel channel) throws IOException {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println(LocalDateTime.now() + " 接收 queue_delayed 消息: " + msg);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix