Spring Boot与RabbitMQ的整合消息确认

一:确认种类

RabbitMQ的消息确认有两种。

一种是消息发送确认。这种是用来确认生产者将消息发送给交换器,交换器传递给队列的过程中,消息是否成功投递。发送确认分为两步,一是确认是否到达交换器,二是确认是否到达队列。

第二种是消费接收确认。这种是确认消费者是否成功消费了队列中的消息。

二:消息发送确认

(1)ConfirmCallback

通过实现ConfirmCallBack接口,消息发送到交换器Exchange后触发回调。

RabbitMQ的消息确认机制

 

使用该功能需要开启确认,spring-boot中配置如下:

spring.rabbitmq.publisher-confirms = true

(2)ReturnCallback

通过实现ReturnCallback接口,如果消息从交换器发送到对应队列失败时触发(比如根据发送消息时指定的routingKey找不到队列时会触发)

RabbitMQ的消息确认机制

 

使用该功能需要开启确认,spring-boot中配置如下:

spring.rabbitmq.publisher-returns = true

三:消息接收确认

(1)确认模式

  • AcknowledgeMode.NONE:不确认
  • AcknowledgeMode.AUTO:自动确认
  • AcknowledgeMode.MANUAL:手动确认

spring-boot中配置方法:

spring.rabbitmq.listener.simple.acknowledge-mode = manual

(2)手动确认

RabbitMQ的消息确认机制

未确认的消息数

上图为channel中未被消费者确认的消息数。

通过RabbitMQ的host地址加上默认端口号15672访问管理界面。

(2.1)成功确认

void basicAck(long deliveryTag, boolean multiple) throws IOException;

deliveryTag:该消息的index

multiple:是否批量. true:将一次性ack所有小于deliveryTag的消息。

消费者成功处理后,调用channel.basicAck(message.getMessageProperties().getDeliveryTag(), false)方法对消息进行确认。

(2.2)失败确认

void basicNack(long deliveryTag, boolean multiple, boolean requeue)

throws IOException;

deliveryTag:该消息的index。

multiple:是否批量. true:将一次性拒绝所有小于deliveryTag的消息。

requeue:被拒绝的是否重新入队列。

void basicReject(long deliveryTag, boolean requeue) throws IOException;

deliveryTag:该消息的index。

requeue:被拒绝的是否重新入队列。

channel.basicNack 与 channel.basicReject 的区别在于basicNack可以批量拒绝多条消息,而basicReject一次只能拒绝一条消息。

消息队列的基础知识可以参考:消息队列RabbitMQ基础知识详解

四:思考

(1)手动确认模式,消息手动拒绝中如果requeue为true会重新放入队列,但是如果消费者在处理过程中一直抛出异常,会导致入队-》拒绝-》入队的循环,该怎么处理呢?

第一种方法是根据异常类型来选择是否重新放入队列。

第二种方法是先成功确认,然后通过channel.basicPublish()重新发布这个消息。重新发布的消息网上说会放到队列后面,进而不会影响已经进入队列的消息处理。

void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)

throws IOException;

(2)消息确认的作用是什么?

为了防止消息丢失。消息丢失分为发送丢失和消费者处理丢失,相应的也有两种确认机制。

 

本文转自:https://www.toutiao.com/a6583957771840913934/?tt_from=mobile_qq&utm_campaign=client_share&timestamp=1532999387&app=news_article&utm_source=mobile_qq&iid=39062783162&utm_medium=toutiao_android

消息生产者和消费者
import com.rabbitmq.client.Channel;
 
  1. import org.slf4j.Logger;

  2. import org.slf4j.LoggerFactory;

  3. import org.springframework.amqp.core.*;

  4. import org.springframework.amqp.rabbit.connection.ConnectionFactory;

  5. import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;

  6. import org.springframework.amqp.rabbit.core.RabbitAdmin;

  7. import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;

  8. import org.springframework.amqp.rabbit.support.CorrelationData;

  9. import org.springframework.beans.factory.annotation.Autowired;

  10. import org.springframework.context.annotation.Bean;

  11. import org.springframework.stereotype.Controller;

  12. import org.springframework.web.bind.annotation.RequestMapping;

  13. import org.springframework.web.bind.annotation.ResponseBody;

  14.  
  15. import java.io.IOException;

  16. import java.util.Date;

  17. import java.util.UUID;

  18.  
  19. /**

  20. * Created by yangliu on 2018/4/8.

  21. */

  22. @Controller

  23. @RequestMapping("/rabbitMq")

  24. public class TestController {

  25. private Logger logger= LoggerFactory.getLogger(TestController.class);

  26. @Autowired

  27. RabbitAdmin rabbitAdmin;

  28.  
  29. @Bean

  30. public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {

  31. RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);

  32. rabbitAdmin.getRabbitTemplate().setConfirmCallback(new MsgSendConfirmCallBack());

  33. rabbitAdmin.getRabbitTemplate().setReturnCallback(new MsgSendReturnCallback());

  34. return rabbitAdmin;

  35. }

  36.  
  37.  
  38. @RequestMapping("/sendMq")

  39. @ResponseBody

  40. public String send(String name) throws Exception {

  41. String context = "hello "+name+" --" + new Date();

  42. String sendStr;

  43. for(int i=1;i<=100;i++){

  44. sendStr="第["+i+"]个 hello --" + new Date();

  45. logger.debug("HelloSender: " + sendStr);

  46. sendMessage("myqueue",sendStr);

  47. //Thread.sleep(1000);

  48. }

  49. return context;

  50. }

  51.  
  52. /**

  53. * 方式一:动态声明exchange和queue它们的绑定关系 rabbitAdmin

  54. * @param exchangeName

  55. * @param queueName

  56. */

  57. protected void declareBinding(String exchangeName, String queueName) {

  58. if (rabbitAdmin.getQueueProperties(queueName) == null) {

  59. /* queue 队列声明

  60. durable=true,交换机持久化,rabbitmq服务重启交换机依然存在,保证不丢失; durable=false,相反

  61. auto-delete=true:无消费者时,队列自动删除; auto-delete=false:无消费者时,队列不会自动删除

  62. 排他性,exclusive=true:首次申明的connection连接下可见; exclusive=false:所有connection连接下*/

  63. Queue queue = new Queue(queueName, true, false, false, null);

  64. rabbitAdmin.declareQueue(queue);

  65. TopicExchange directExchange = new TopicExchange(exchangeName);

  66. rabbitAdmin.declareExchange(directExchange);//声明exchange

  67. Binding binding = BindingBuilder.bind(queue).to(directExchange).with(queueName); //将queue绑定到exchange

  68. rabbitAdmin.declareBinding(binding); //声明绑定关系

  69. rabbitAdmin.getRabbitTemplate().setMandatory(true);

  70. rabbitAdmin.getRabbitTemplate().setConfirmCallback(new MsgSendConfirmCallBack());//消息确认

  71. rabbitAdmin.getRabbitTemplate().setReturnCallback(new MsgSendReturnCallback());//确认后回调

  72. } else {

  73. rabbitAdmin.getRabbitTemplate().setQueue(queueName);

  74. rabbitAdmin.getRabbitTemplate().setExchange(queueName);

  75. rabbitAdmin.getRabbitTemplate().setRoutingKey(queueName);

  76. }

  77. }

  78.  
  79. /**

  80. * 发送消息

  81. * @param queueName

  82. * @param message

  83. * @throws Exception

  84. */

  85. public void sendMessage(String queueName, String message) throws Exception {

  86. declareBinding(queueName, queueName);

  87. CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());

  88. rabbitAdmin.getRabbitTemplate().convertAndSend(queueName, queueName, message,correlationId);

  89. logger.debug("[rabbitmq-sendMessage]queueName:{} ,uuid:{},msg:{}",queueName,correlationId.getId(),message);

  90. }

  91.  
  92. /**

  93. * 消费者

  94. * @param connectionFactory

  95. * @return

  96. */

  97. @Bean

  98. public SimpleMessageListenerContainer messageContainer(ConnectionFactory connectionFactory) {

  99. SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);

  100. Queue queue = new Queue("myqueue", true, false, false, null);

  101. container.setQueues(queue);

  102. container.setExposeListenerChannel(true);

  103. container.setMaxConcurrentConsumers(1);

  104. container.setConcurrentConsumers(1);

  105. container.setAcknowledgeMode(AcknowledgeMode.MANUAL); //设置确认模式手工确认

  106. container.setMessageListener(new ChannelAwareMessageListener() {

  107. @Override

  108. public void onMessage(Message message, Channel channel) throws Exception {

  109.  
  110. byte[] body = message.getBody();

  111. try {

  112. //业务逻辑

  113. logger.info("消费 receive msg : " + new String(body));

  114. // 消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息

  115. //channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);

  116. channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //手动确认确认消息成功消费

  117. } catch (Exception e) {

  118. logger.info("消费失败: " + new String(body));

  119. // ack返回false,并重新回到队列,api里面解释得很清楚

  120. try {

  121. channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);

  122. } catch (IOException e1) {

  123. e1.printStackTrace();

  124. }

  125. }

  126.  
  127. }

  128. });

  129. return container;

  130. }

  131.  
  132.  
  133. /*

  134.  
  135. //消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息

  136. channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

  137. //ack返回false,并重新回到队列,api里面解释得很清楚

  138. channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);

  139. //拒绝消息

  140. channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);

  141.  
  142. 如果消息没有到exchange,则confirm回调,ack=false

  143. 如果消息到达exchange,则confirm回调,ack=true

  144. exchange到queue成功,则不回调return

  145. exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)

  146. */

  147. }

 

失败后return回调:

 

 
  1. import org.springframework.amqp.core.Message;

  2. import org.springframework.amqp.rabbit.core.RabbitTemplate;

  3. public class MsgSendReturnCallback implements RabbitTemplate.ReturnCallback {

  4. @Override

  5. public void returnedMessage(Message message, int replyCode, String replyText,

  6. String exchange, String routingKey) {

  7. System.out.println("确认后回调return--message:"+new String(message.getBody())+",replyCode:"+replyCode+",replyText:"

  8. +replyText+",exchange:"+exchange+",routingKey:"+routingKey);

  9. }

  10. }

确认后回调: 

 
  1. import org.springframework.amqp.rabbit.core.RabbitTemplate;

  2. import org.springframework.amqp.rabbit.support.CorrelationData;

  3.  
  4. public class MsgSendConfirmCallBack implements RabbitTemplate.ConfirmCallback {

  5. @Override

  6. public void confirm(CorrelationData correlationData, boolean ack, String cause) {

  7. if (ack) {

  8. System.out.println("消息确认成功cause"+cause);

  9. } else {

  10. //处理丢失的消息

  11. System.out.println("消息确认失败:"+correlationData.getId()+"#cause"+cause);

  12. }

  13. }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值