RabbitMQ中的消息确认ACK机制

RabbitMQ消息确认机制,那我们来看一下什么是消息确认ACK,如果在处理消息过程中,消费者服务器在处理消息时

出现异常,那可能这条正在处理的消息就没有完成消息消费,数据就会丢失,为了确保数据不丢失,RabbitMQ支持消息

确认ACK,也就是RabbitMQ当中,确保消费端,如果消费端出现问题了,让这个消息不会丢失,它采用的是一个ACK的消息

确认机制,那我们再来看ACK的消息确认机制是什么,ACK机制是消费者从RabbitMQ收到消息并处理完以后,反馈给Rabbit

MQ,RabbitMQ收到反馈后才将此消息从队列中删除,也就是我们向消费者发送一条消息的时候,对于RabbitMQ来讲,他需要

收到消费端的ACK反馈,如果没有收到ACK反馈呢,他认为你这个消息没有正常消费,那么他就会讲消息再次放到队列当中,

当你收到消费者的ACK反馈,那么他就认为你这个消息被正常消费了,所以他会把这个消息从队列当中删除,如果一个消费者

在处理消息出现了网络不稳定,服务器异常现象,那么就不会有ACK反馈,RabbitMQ会认为这个消息没有正常消费,会将消息

重新放入到队列当中,就是我刚才说过的,再来看第二点,如果在集群情况下,RabbitMQ会立即将这个消息推送给这个在线的

其他消费者,这种机制保证了在消费者服务端故障的时候,不丢失任何消息和任务,这里什么意思呢,如果我们消费服务是一个

集群的模式下,那么消息队列,RabbitMQ,发送消息的时候,如果某个消费者消费消息的时候出现问题,这个消息没有正常被消费,

那么这个时候RabbitMQ,将消息重新放入到队列当中,并且把消息发送给集群的其他节点,消费这个消息,主要是为了解决消息的

可靠性,第三消息永远都不会从RabbitMQ中删除,只有当消费者正确发送ACK反馈,RabbitMQ收到后,消息才会从RabbitMQ中删除,

也就是如果你这个数据没有成功的返回,这里没有正常的返回ACK,那么消息就一直驻留到这里面的,第四消息的ACK确认机制默认

是打开的,我们把什么是ACK,ACK消息确认机制,接下来我们就来演示一下ACK机制的特点

回到我们的项目当中,首先我们创建一个接收端和发送端,我们拿哪个呢,我们拿direct项目去做一个演示,

大家需要注意,我们只是拿这个去讲解ACK,并不是ACK只适合于direct交换器,它是对所有交换器都适用的,

他跟交换器是没有关系的,无论是fanout还是topic,消费方都是有ACK反馈,我们先去创建一个Provider,我们

改个名字

rabbitmq-ack-direct-provider

创建一个Consumer

rabbitmq-ack-direct-consumer

然后我们把两个文件的pom文件做一个修改,把artifactId和name改一下,粘到笔记当中,创建项目,然后我们来看一下

这两个项目发送消息的一个过程,先看Consumer,Consumer里面有一个ErrorReceiver,还有InfoReceiver,有一个Provider,

在Provider的Sender里,我们发送消息的路由key,是error的路由key

@Value("${mq.config.queue.error.routing.key}")
private String routingkey;

那么也就是说error Receiver会接收到消息,在正常的情况下,比如Consumer在消费消息的时候,出现问题了,怎么出现问题了呢,

我们认为给他抛出一个异常,我们new一个RuntimeException,那么就是我的消费者在消费消息的时候突然抛出异常了,什么样呢,

我们先来运行一下,看一下这个结果,找到我们的启动类,然后把我们的消费者运行,然后回到我们的消息发送者这儿,我们去找他的

测试代码,然后我们来运行,我们注意观察控制台,看到了吗,现在已经有异常产生了,但是注意看你的控制台,现在他在不停的发送

这个消息,因为你的消费端并没有返回一个ACK,所以他认为消息没有正确的消费,所以会让你再次让你消费,可是=我在消费的时候

又出现异常了,又没有出现反馈,所以整个的消费消息这一块呢,成了一个死循环了,一直在发送,一直在接收,一直在出问题,再发送再

接收就出问题,我们看一下管理界面,在这里我们可以看到,准备了40次了,然后有一个消息是被锁定的,还没有被接收到的,没有相应的

ACK,那么怎么解决这个问题呢,这块我们需要注意,看我的这块笔记,ACK开发的注意事项,如果忘记了ACK,那么后果很严重,如果一旦你的

消息处理出现异常,如果你没有考虑到这一点的话,那么后果是非常严重的,当Consumer退出时,Message会一直重新发送,RabbitMQ就会

占用越来越多的内存,由于RabbitMQ会长时间的运行,因此这个内存泄露是致命的,只要你消费方没有ACK返回,就会一直在堆积消息,

这里已经85条了,90,还会往里堆,直到你RabbitMQ端内存服务器的内存被耗尽,那么就会产生内存泄露,内存溢出,所以这个问题是

非常严重的,我们先把他停掉,那么怎么解决这个问题呢,我们先把while true删掉,因为是while true的,所以一直在发送,

现在我把这个去掉,他也是这个效果的

@Test
public void test1()throws Exception{
	this.sender.send("Hello RabbitMQ");
}

观察控制台,也是一直在发送的,一直在闪烁,又是类似于进入一个死循环一样,只不过不同的是消息的数量,只有一条,只有一条在队列,

因为之前是while true堆的队列消息会比较多,但是你一条没有发送成功,你这个发送方再发消息的时候,所有的消息都会堆到这个队列里,

所以我们在编码时,一定要考虑到ACK的机制,刚才我也说了,如果你不注意这个的话,你的RabbitMQ会有内存泄露的现象,那么怎么解决这个

问题呢,第一种方式,就是我们在处理消息的方法当中呢,添加try catch,我们可以通过try catch去捕获异常,然后在catch里处理异常,

保证你的消费方可以正确的运行,第二种方式是什么呢,是在配置文件里,添加对于接受消息的次数,重试次数的一个设定,当Rabbit服务器

给你发送消息的时候,根据你配置的信息来决定,做几次重试,达到重试的次数以后呢,如果还没有ACK返回,那么他就不会发送这个消息了,

也可以解决这个问题,那么接下来演示的就是采用第二种方式,在配置文件当中,添加对于发送次数的一个配置,首先我们在配置文件

当中呢,开启重试,先配置一个开启重试,他的配置键和值是什么呢,注意这一块的键,我不能随便乱写了,因为这个键值对,

由我们的springboot去读取的

spring.rabbitmq.listener.retry.enabled=true

这是开启重试的一个key和value,我们还需要配置一个什么呢,配置一个重试的次数,默认为3次

//消费者异常之后的最大重试次数,JavaConfig方式需显示构建retryConfig
spring.rabbitmq.listener.retry.max-attempts=5

然后重试次数的key是什么呢,spring.rabbitmq.listener.retry.max-attempts,这里注意这一块就不是点了,max-attempts,

然后我们给一个5,这样我们就给了一个消息重试的机制,首先开启重试,然后重试5次,如果5次都没有收到ACK反馈,那么RabbitMQ

就把消息直接删除,不再给你发送,我们把它加了以后,接下来我们再去运行,看看这个效果,我们把消费者启动了,再来启动Provider

去测试,运行,我们观察控制台,出现异常了,注意看,没有出现刚才我们的死循环的现象,消费者就不再接受消息了,但是消费者服务

没有关,只是这个消息不再发送了,然后我们再看一下这个消息队列,看到了吗,现在并没有一个被锁定的一个消息,所以说我们可以

通过这种机制,来解决ACK确认在RabbitMQ返回机制所带来的一个问题,我们可以通过加上两个配置,可以化解这个问题,下面我们

把笔记整理到笔记当中,先来看配置文件,Consumer,修改Consumer配置文件,解决ACK反馈问题,然后把这个加到这里面,我们主要

是改动Consumer这一侧,消费者这一侧,对于消息的提供者呢,是什么都不用处理,以上我们就演示了什么是ACK反馈,我们在

使用RabbitMQ的时候,怎么解决开发中所带来的一个问题,就是接收者出现问题以后,怎么能绕开ACK的反馈,这块我们也通过代码

做了演示,主要是这两个,那么到目前为止,我们RabbitMQ也就全部讲解完毕了
<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.learn</groupId>
  <artifactId>rabbitmq-ack-direct-consumer</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
  
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.12.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>
		<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
		<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.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>
	
	<!-- 这个插件,可以将应用打包成一个可执行的jar包 -->
	<build>
	    <plugins>
	        <plugin>
	            <groupId>org.springframework.boot</groupId>
	            <artifactId>spring-boot-maven-plugin</artifactId>
	        </plugin>
	    </plugins>
	</build>
  
  
</project>
spring.application.name=rabbitmq-ack-direct-consumer

spring.rabbitmq.host=59.110.158.145
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

mq.config.exchange=log.direct
mq.config.queue.info=log.info
mq.config.queue.info.routing.key=log.info.routing.key
mq.config.queue.error=log.error
mq.config.queue.error.routing.key=log.error.routing.key

spring.rabbitmq.listener.retry.enabled=true
spring.rabbitmq.listener.retry.max-attempts=5
package com.learn;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * 消息接收者
 * @author Administrator
 * @RabbitListener bindings:绑定队列
 * @QueueBinding  value:绑定队列的名称
 *                exchange:配置交换器
 * 
 * @Queue value:配置队列名称
 *        autoDelete:是否是一个可删除的临时队列
 * 
 * @Exchange value:为交换器起个名称
 *           type:指定具体的交换器类型
 */
@Component
@RabbitListener(
			bindings=@QueueBinding(
					value=@Queue(value="${mq.config.queue.error}",autoDelete="true"),
					exchange=@Exchange(value="${mq.config.exchange}",type=ExchangeTypes.DIRECT),
					key="${mq.config.queue.error.routing.key}"
			)
		)
public class ErrorReceiver {

	/**
	 * 接收消息的方法。采用消息队列监听机制
	 * @param msg
	 */
	@RabbitHandler
	public void process(String msg){
		System.out.println("Error..........receiver: "+msg);
		throw new RuntimeException();
	}
}
package com.learn;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RabbitAckDirectConsumerApplication {

	public static void main(String[] args) {
		SpringApplication.run(RabbitAckDirectConsumerApplication.class, args);
	}
}
<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.learn</groupId>
  <artifactId>rabbitmq-ack-direct-provider</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
  
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.12.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>
		<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
		<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.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>
	
	<!-- 这个插件,可以将应用打包成一个可执行的jar包 -->
	<build>
	    <plugins>
	        <plugin>
	            <groupId>org.springframework.boot</groupId>
	            <artifactId>spring-boot-maven-plugin</artifactId>
	        </plugin>
	    </plugins>
	</build>
  
  
</project>
spring.application.name=rabbitmq-direct-provider

spring.rabbitmq.host=59.110.158.145
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
mq.config.exchange=log.direct
mq.config.queue.info.routing.key=log.info.routing.key
mq.config.queue.error.routing.key=log.error.routing.key
mq.config.queue.error=log.error
package com.learn;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 消息发送者
 * @author Administrator
 *
 */
@Component
public class Sender {

	@Autowired
	private AmqpTemplate rabbitAmqpTemplate;
	
	//exchange 交换器名称
	@Value("${mq.config.exchange}")
	private String exchange;
	
	//routingkey 路由键
	@Value("${mq.config.queue.error.routing.key}")
	private String routingkey;
	/*
	 * 发送消息的方法
	 */
	public void send(String msg){
		//向消息队列发送消息
		//参数一:交换器名称。
		//参数二:路由键
		//参数三:消息
		this.rabbitAmqpTemplate.convertAndSend(this.exchange, this.routingkey, msg);
	}
}
package com.learn;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RabbitAckDirectProviderApplication {

	public static void main(String[] args) {
		SpringApplication.run(RabbitAckDirectProviderApplication.class, args);
	}
}

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值