Springboot下RabbitMQ的使用及解决消息丢失问题

基础环境

RabbitMQ的安装
Springboot:2.2.5.RELEASE
示例代码:springboot-mq
RabbitMQ:3.8.3
application.yml文件配置

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    cache:
      size: 1
#    listener:
#      direct:
#        # 手动确认
#        acknowledge-mode: auto
#      simple:
#        # 手动确认
#        acknowledge-mode: auto
server:
  servlet:
    context-path: /mq
  port: 8888

消息发送

消息发送前,新建了exchange[echange.key] 和 Queue[queue.name],并绑定
在这里插入图片描述写个Controller测试一下,一般来说发送消息走定时或者实际业务里发起比较常见。


package com.mq.test.controller;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/test")
public class TestController {
   @Autowired
   RabbitTemplate rabbitTemplate;
   @ResponseBody
   @RequestMapping("/mq")
   public String mq(){
       //发送消息
       rabbitTemplate.convertAndSend("exchange.key",null,"data");
       return "SUCCESS";
   }
}

请求:http://localhost:8888/mq/test/mq
在这里插入图片描述产生了消息

消费消息

注册个消费者监听

package com.mq.test.config.mq.listener;

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.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class MQConsumeListeners {
   private static Logger logger = LoggerFactory.getLogger(MQConsumeListeners.class);
   @RabbitListener(queues={"queue.name"})
   public void testConsume(Message message, Channel channel, String message1){
       logger.info("mq consume :"+ message);
   }
   //testConsume 还可以写成如下形式,使用基本数据类型直接接受message的body
   //@RabbitListener(queues={"queue.name"})
   public void testConsume2(String data, Channel channel, String message1){
       logger.info(("接受到的数据:"+data));

   }
}

执行结果:
打印了接收结果
在这里插入图片描述 消息已被消费
在这里插入图片描述

队列、交换器声明及绑定

在Spring里,会根据Bean的类型,自动注册Queue、Exchange及绑定关系

package com.mq.test.config.mq;


//import com.platform.test.config.mq.listener.MQConsumeListener;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.SimpleRoutingConnectionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class MQConfig {
  @Bean
  public Queue testQueue(){
      return new Queue(QueueName.TEST,false);
  }
  @Bean
  public DirectExchange testDirectExchange(){
      return new DirectExchange(ExchangeName.TEST,true,false);
  }
  @Bean
  public Binding testBinding(){
      return BindingBuilder.bind(testQueue()).to(testDirectExchange()).with(RoutingKey.TEST);
  }
  static class QueueName{
      static String TEST = "queue.test";
  }
  static class ExchangeName{
      static String TEST = "exchange.test";
  }

  enum RoutingKey{
      TEST("exchange.test");
      private String key;
      RoutingKey(String key){
          this.key = key;
      }
      @Override
      public String toString(){
          return this.key;
      }
  }
}

在这里插入图片描述发送消息试试TestController.java中新增mq2方法:

   @ResponseBody
  @RequestMapping("/mq2")
  public String mq2(){
      rabbitTemplate.convertAndSend("exchange.test","exchange.test","test data");
      return "SUCCESS";
  }

访问:localhost:8888/mq/test/mq2
在这里插入图片描述## 关于消息丢失问题
消息持久化,解决发送成功但不落盘问题

上面注册队列时durable参数(new Queue()) 参数为false,消息在重启rabbitmq后丢失;修改为true后重新启动项目(记得提前删除已经有的队列)。

在这里插入图片描述重启rabbitmq后消息不丢失

开始ack确认,解决消费失败消息丢失问题
修改yml文件,修改确认方式为手动

spring:
  rabbitmq:
    listener:
      #如果手动声明listener类型,可在config声明时设置
      direct:
        # 手动确认
        acknowledge-mode: manual
      simple:
        # 手动确认
        acknowledge-mode: manual

此时访问:http://localhost:8888/mq/test/mq2,消息会被消费,但是并不会从队列中消失,且全为unacked的状态
在这里插入图片描述
此时需要手动ACK
修改MQConsumeListeners.java增加对queue.test队列的ack确认

@RabbitListener(queues={"queue.test"})
    public void testConsume3(Message message, Channel channel, String message1){
        try {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
            logger.info("ack:"+message.getMessageProperties().getDeliveryTag());

        } catch (IOException e) {
            e.printStackTrace();
        }
        logger.info("mq consume3 :"+ message);
    }

重启结果:
在这里插入图片描述
消息被消费且确认
在这里插入图片描述
无积压消息

通过实现ChannelAwareMessageListener接口实现消息消费

实现ChannelAwareMessageListener接口

package com.mq.test.config.mq.listener;

import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;

@Component
public class MQConsumeListener implements ChannelAwareMessageListener {
    private static Logger logger = LoggerFactory.getLogger(MQConsumeListener.class);


    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        logger.info(message.getMessageProperties().getConsumerQueue());
        logger.info(message.toString());
        channel.basicQos(1);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);// deliveryTag the tag from the received,可以理解为channel的标识
        //channel.basicNack(100L,false,false);
    }
}

声明SimpleMessageListenerContainer,把listener绑定到队列上(MQConfig.java)

@Bean
    public SimpleMessageListenerContainer simpleMessageListenerContainer(CachingConnectionFactory cachingConnectionFactory){

        SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
        Runnable a = simpleMessageListenerContainer::getActiveConsumerCount;
        simpleMessageListenerContainer.setQueueNames("queue.name");
        simpleMessageListenerContainer.setConnectionFactory(cachingConnectionFactory);
        simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        simpleMessageListenerContainer.setMessageListener(mqConsumeListener);
        return simpleMessageListenerContainer;
    }

总结

springboot在使用org.springframework.boot:spring-boot-starter-amqp时利用注解的便利性,使用起来很方便,当然前提是需要对MQ的工作原理有所掌握。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值