SpringBoot RabbitMQ 集成 六 ACK

Message acknowledgment 消息确认

默认情况下,RabbitMQ 会顺序的分发每个Message。当分发后,会将该Message删除,然后将下一个Message分发到下一个Consumer。这种分发方式叫做round-robin

每个Consumer可能需要一段时间才能处理完收到的数据。如果在这个过程中,Consumer出错了,异常退出了,而数据还没有处理完成,那么非常不幸,这段数据就丢失了。因为我们采用no-ack的方式进行确认,也就是说,每次Consumer接到数据后,而不管是否处理完成,RabbitMQ Server会立即把这个Message标记为完成,然后从queue中删除了。

如果一个Consumer异常退出了,它处理的数据能够被另外的Consumer处理,这样数据在这种情况下就不会丢失了(注意是这种情况下)。

为了保证数据不被丢失,RabbitMQ支持消息确认机制,即acknowledgments。为了保证数据能被正确处理而不仅仅是被Consumer收到,那么我们不能采用no-ack。而应该是在处理完数据后发送ack。

在处理数据后发送的ack,就是告诉RabbitMQ数据已经被接收,处理完成,RabbitMQ可以去安全的删除它了。

如果Consumer退出了但是没有发送ack,那么RabbitMQ就会把这个Message发送到下一个Consumer。这样就保证了在Consumer异常退出的情况下数据也不会丢失。

这里并没有用到超时机制。RabbitMQ仅仅通过Consumer的连接中断来确认该Message并没有被正确处理。也就是说,RabbitMQ给了Consumer足够长的时间来做数据处理。

  消息确认,对于spring-boot来说,就是一个开关,它就是spring.rabbitmq.listener.simple.acknowledge-mode

    acknowledgeMode有三值:

    A、NONE = no acks will be sent (incompatible with channelTransacted=true).

          RabbitMQ calls this "autoack" because the broker assumes all messages are acked without any action from the consumer.

    B、MANUAL = the listener must acknowledge all messages by calling Channel.basicAck().

    C、AUTO = the container will acknowledge the message automatically, unless the MessageListener throws an exception.

Note that acknowledgeMode is complementary to channelTransacted - if the channel is transacted then the broker requires a commit notification in addition to the ack. This is the default mode. See also txSize.
 

 

一、编写代码

 

1、修改配置文件application.yml

spring:
  rabbitmq:
    host: 192.168.2.202   #服务地址  内网地址
    port: 5672  #端口  内网地址
    username: admin #用户名
    password: qaz123  #密码
    virtual-host: /  #vhost
    publisher-confirms: true  #支持发布确认
    publisher-returns: true   #支持发布返回
    listener:
      simple:
        acknowledge-mode: manual    #acknowledgeMode设置为手动模式(NONE,MANUAL,AUTO)
        concurrency: 1              #当前监听容器数
        max-concurrency: 1          #最大数
        retry:
          enabled: true             #是否支持重试
      direct:
        acknowledge-mode: manual    #acknowledgeMode设置为手动模式

2、编写常量类RabbitAckConstant 

package com.lvgang.springbootrabbitmq.messageack;

/**
 * @author lvgang
 */
public class RabbitAckConstant {

    public  static final  String QUEUQ = "Queue_ACK";
}

 

3、编写配置类RabbitAckConfig 

package com.lvgang.springbootrabbitmq.messageack;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author lvgang
 */
@Configuration
public class RabbitAckConfig {
    private static Logger logger = LoggerFactory.getLogger(RabbitAckConfig.class);

    /**
     * Queue 可以有4个参数
     *      1.队列名
     *      2.durable       持久化消息队列 ,rabbitmq重启的时候不需要创建新的队列 默认true
     *      3.auto-delete   表示消息队列没有在使用时将被自动删除 默认是false
     *      4.exclusive     表示该消息队列是否只在当前connection生效,默认是false
     */
    @Bean
    public Queue createAckQueue() {
        return new Queue(RabbitAckConstant.QUEUQ,true);
    }

}

4、编写消息生产者AckSender 

 

package com.lvgang.springbootrabbitmq.messageack;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.UUID;

/**
 * @author lvgang
 */
@Component
public class AckSender implements  RabbitTemplate.ConfirmCallback ,RabbitTemplate.ReturnCallback {

	private static Logger logger = LoggerFactory.getLogger(AckSender.class);
	@Autowired
	private RabbitTemplate rabbitTemplate;

	public void send() {
		//设置回调对象
		this.rabbitTemplate.setConfirmCallback(this);
		this.rabbitTemplate.setReturnCallback(this);
		//构建回调返回的数据
		CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());

		String content = "Confirm= " + new Date() + ", content= " + UUID.randomUUID().toString();
		this.rabbitTemplate.convertAndSend(RabbitAckConstant.QUEUQ,(Object) content,correlationData);
		logger.info("Confirm Send ok,"+new Date()+","+content);
	}

	/**
	 * 消息回调确认方法
	 * 如果消息没有到exchange,则confirm回调,ack=false
	 * 如果消息到达exchange,则confirm回调,ack=true
	 * @param
	 */
	@Override
	public void confirm(CorrelationData correlationData, boolean isSendSuccess, String s) {
		//logger.info("confirm--message:回调消息ID为: " + correlationData.getId());
		if (isSendSuccess) {
			//logger.info("confirm--message:消息发送成功");
		} else {
			logger.info("confirm--message:消息发送失败" + s);
		}
	}

	/**
	 * exchange到queue成功,则不回调return
	 * exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)
	 */
	@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);
	}







}

5、编写消息消费者A  AckReceiverA  

package com.lvgang.springbootrabbitmq.messageack;

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.util.Date;

/**
 * @author lvgang
 */
@Component
@RabbitListener(queues = RabbitAckConstant.QUEUQ)
public class AckReceiverA {
    private static Logger logger = LoggerFactory.getLogger(AckReceiverA.class);
    @RabbitHandler
    public void process(String str,Channel channel, Message message)  {


        logger.info("ReceiverA : " + str +","+ new Date());

        try {
            //告诉服务器收到这条消息 已经被我消费了 可以在队列删掉 这样以后就不会再发了 否则消息服务器以为这条消息没处理掉 后续还会在发
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            logger.info("消息消费A成功!");
        } catch (Exception e) {
            logger.error("消息消费A失败:"+ e.getMessage(),e);
            //丢弃这条消息
            //channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


//消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息
//channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
//ack返回false,并重新回到队列,api里面解释得很清楚
//channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
//拒绝消息
//channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);

}

 

6、编写消息消费者B  AckReceiverB 

package com.lvgang.springbootrabbitmq.messageack;

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;

/**
 * @author lvgang
 */
@Component
@RabbitListener(queues = RabbitAckConstant.QUEUQ)
public class AckReceiverB {
    private static Logger logger = LoggerFactory.getLogger(AckReceiverB.class);
    @RabbitHandler
    public void process(String str,Channel channel, Message message){

        logger.info("ReceiverB : " + str +","+ new Date());

        try {
            //告诉服务器收到这条消息 已经被我消费了 可以在队列删掉 这样以后就不会再发了 否则消息服务器以为这条消息没处理掉 后续还会在发
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            logger.info("消息消费B成功!");
        } catch (Exception e) {
            logger.error("消息消费B失败:"+ e.getMessage(),e);
            //丢弃这条消息
            //channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
        }

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

//消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息
//channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
//ack返回false,并重新回到队列,api里面解释得很清楚
//channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
//拒绝消息
//channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);

}

 

二、测试结果

1、编写测试类TopicTests 

 

package com.lvgang.springbootrabbitmq.messageack;


import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


/**
 * @author lvgang
 */

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class AckTests {
	@Autowired
	private AckSender ackSender;

	@Test
	public void hello() {
		int i=1;
		while (true) {
			try {
				if(i<10) {
					ackSender.send();
				}
				i++;
				Thread.sleep(1000);
			} catch (Exception e) {
				;
			}
		}
	}
}

2、执行测试类,并查看结果

d82578434903f620fa184084efd3f09e81c.jpg

通过执行测类,查看到了消息消费的情况,生产者共计生产了10个消息,被消费者A消费了8条,消费都B消费了2条。

每一次消费都消费完成消息之后需要回复消息中心一个信息就是channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);

这样消息中心才会把下一条消息发给消费者。

转载于:https://my.oschina.net/sdlvzg/blog/3045833

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot集成RabbitMQ可以通过以下步骤完成: 1. 添加Maven依赖:在pom.xml文件中添加RabbitMQ的Spring Boot Starter依赖。 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> ``` 2. 配置RabbitMQ连接信息:在application.properties(或application.yml)文件中配置RabbitMQ的连接信息。 ```properties spring.rabbitmq.host=your_rabbitmq_host spring.rabbitmq.port=your_rabbitmq_port spring.rabbitmq.username=your_rabbitmq_username spring.rabbitmq.password=your_rabbitmq_password ``` 3. 创建RabbitMQ发送者:创建一个发送消息的类,使用`RabbitTemplate`发送消息到指定的交换机和队列。 ```java import org.springframework.amqp.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class RabbitMQSender { @Autowired private RabbitTemplate rabbitTemplate; public void sendMessage(String exchange, String routingKey, Object message) { rabbitTemplate.convertAndSend(exchange, routingKey, message); } } ``` 4. 创建RabbitMQ接收者:创建一个接收消息的类,使用`@RabbitListener`注解监听指定的队列,处理接收到的消息。 ```java import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class RabbitMQReceiver { @RabbitListener(queues = "your_queue_name") public void receiveMessage(Object message) { // 处理接收到的消息 System.out.println("Received message: " + message.toString()); } } ``` 5. 发送和接收消息:在需要发送或接收消息的地方调用对应的方法。 ```java @Autowired private RabbitMQSender rabbitMQSender; public void sendMessage() { rabbitMQSender.sendMessage("your_exchange_name", "your_routing_key", "Hello, RabbitMQ!"); } ``` 以上是基本的使用方式,你可以根据实际需求进行扩展和配置。注意,你还需要安装并启动RabbitMQ服务。 希望对你有所帮助!如果有任何疑问,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值