- 如果在处理消息的过程中,消费者的服务器在处理消息的时候出现异常,那么可能这条正在处理的消息就没有完成消息消费,数据就会丢失。为了确保数据不会丢失,RabbitMQ支持消息确定-ACK。
- ACK机制是消费者从RabbitMQ收到消息并处理完成后,反馈给RabbitMQ,RabbitMQ收到反馈后才将此消息从队列中删除。
- 如果一个消费者在处理消息出现了网络不稳定、服务器异常等现象,那么就不会有ACK反馈,RabbitMQ会认为这个消息没有正常消费,会将消息重新放入队列中。
- 如果在集群的情况下,RabbitMQ会立即将这个消息推送给这个在线的其他消费者。这种机制保证了在消费者服务端故障的时候,不丢失任何消息和任务。
- 消息永远不会从RabbitMQ中删除,只有当消费者正确发送ACK反馈,RabbitMQ确认收到后,消息才会从RabbitMQ服务器的数据中删除。
- 消息的ACK确认机制默认是打开的。
- 当Consumer退出时候,Message会一直重新分发。然后RabbitMQ会占用越来越多的内容,由于RabbitMQ会长时间运行,因此这个"内存泄漏"是致命的。
- RabbitMQ的交换器Exchange之direct(发布与订阅
完全匹配),这里借助这个进行消息持久化测试。生产者的代码不发生改变。控制层的触发生产者生产消息,这里只生产一条消息。方便观察现象。
package com.programb.example.springboot.rabbitmq.ack;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootRabbitmqAckApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootRabbitmqAckApplication.class, args);
}
}
package com.programb.example.springboot.rabbitmq.ack.config;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class RabbitConfig {
@Bean
public Queue QueueA() {
return new Queue("hello", true);
}
}
package com.programb.example.springboot.rabbitmq.ack.producer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.programb.example.springboot.rabbitmq.ack.receiver.HelloReceiver;
import java.util.Date;
@Component
public class HelloSender implements RabbitTemplate.ReturnCallback, RabbitTemplate.ConfirmCallback {
private Logger logger = LoggerFactory.getLogger(HelloSender.class);
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMessage(String context) {
rabbitTemplate.setMandatory(true);
this.rabbitTemplate.setReturnCallback(this);
this.rabbitTemplate.setConfirmCallback(this);
this.rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (!ack) {
logger.info("HelloSender 发送失败:" + cause + correlationData.toString());
} else {
logger.info("HelloSender 发送成功");
}
});
logger.info("HelloSender 发送的消息内容:{}", context);
this.rabbitTemplate.convertAndSend("hello", context);
}
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
logger.info("return--message:" + new String(message.getBody()) + ",replyCode:" + replyCode + ",replyText:" + replyText + ",exchange:" + exchange + ",routingKey:" + routingKey);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
logger.info("消息id: " + correlationData + "确认" + (ack ? "成功:" : "失败"));
}
}
package com.programb.example.springboot.rabbitmq.ack.receiver;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Date;
@Component
@RabbitListener(queues = "hello")
public class HelloReceiver {
private Logger logger = LoggerFactory.getLogger(HelloReceiver.class);
@RabbitHandler
public void process(String context, Message message, Channel channel) {
logger.info("HelloReceiver 监听到消息内容:{}", context);
try {
//告诉服务器收到这条消息 已经被我消费了 可以在队列删掉 这样以后就不会再发了 否则消息服务器以为这条消息没处理掉 后续还会在发
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
//消息确认 因为我在属性配置文件里面开启了ACK确认 所以如果代码没有执行ACK确认 你在RabbitMQ的后台会看到消息会一直留在队列里面未消费掉 只要程序一启动开始接受该队列消息的时候 又会收到
logger.info("HelloReceiver 消息接收成功");
} catch (Exception e) {
e.printStackTrace();
logger.info("HelloReceiver 消息接收失败");
// ack返回false,并重新放回队列
try {
logger.info("HelloReceiver ack返回false,并重新放回队列");
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
package com.programb.example.springboot.rabbitmq.ack.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.programb.example.springboot.rabbitmq.ack.producer.HelloSender;
@RestController
public class IndexController {
@Autowired
private HelloSender helloSender;
@RequestMapping("/send")
public void sendMessageTest(String context) throws Exception {
helloSender.sendMessage(context);
}
}
server.port=8080
spring.application.name=rabbitmq-hello
spring.rabbitmq.host=10.4.98.15
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=programb
spring.rabbitmq.publisher-confirms=true
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.listener.direct.acknowledge-mode=manual
spring.rabbitmq.listener.simple.acknowledge-mode=manual
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.programb.example</groupId>
<artifactId>spring-boot-rabbitmq-ack</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-rabbitmq-ack</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>