Rabbitmq消息丢失-消费者消息丢失(二)

本文介绍了在SpringBoot应用中,如何处理消息消费端的异常情况,如业务逻辑错误或网络中断,以及通过关闭自动ACK并实现手动确认机制来防止消息丢失。还展示了如何配置RabbitMQ的相关配置和控制器、监听器的实现。
摘要由CSDN通过智能技术生成

说明:消费端在处理消息的过程中出现异常,例如:业务逻辑异常,或者消费者被停机,或者网络断开连接等,以上等情况使消息没有得到正确恰当的处理,也会使消息丢失。

分析:分析就是说明中的例如!

解决:ACK确认机制

所谓的ACK就是:首先关闭自动确认【自动ACK】,消费者收到一个消息后,就可以发一个确认【ACK】给MQ,当然什么时候发送确认【ACK】是程序员决定的,也就是说每次在确保处理完这个消息相关的业务后,程序员可以手动发送确认【ACK】,之后把消息从MQ中干掉!这样即使出现了异常也可以有效的消费消息。

工程图:

1.pom.xml

<?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>

  <parent>
    <artifactId>spring-boot-starter-parent</artifactId>  <!-- 被继承的父项目的构件标识符 -->
    <groupId>org.springframework.boot</groupId>  <!-- 被继承的父项目的全球唯一标识符 -->
    <version>2.2.2.RELEASE</version>  <!-- 被继承的父项目的版本 -->
  </parent>

  <groupId>MqLossDemoTwo</groupId>
  <artifactId>MqLossDemoTwo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>MqLossDemoTwo Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <!--spring boot核心-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!--spring boot 测试-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <!--springmvc web-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--开发环境调试-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <optional>true</optional>
    </dependency>
    <!--amqp 支持-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>

    <!--redis-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
      <version>2.1.7.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.78</version>
    </dependency>

    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.6</version>
    </dependency>

    <!--lombok-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.10</version>
    </dependency>
  </dependencies>


  <build>
    <finalName>MqLossDemoTwo</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

2.application.yml

server:
  port: 8080
spring:
  rabbitmq:
    port: 5672
    host: 你的 rabbitmq IP
    username: admin
    password: admin
    virtual-host: /
    listener:
      simple:
        concurrency: 10
        max-concurrency: 10
        prefetch: 1
        auto-startup: true
        default-requeue-rejected: true
        # 设置消费端手动 ack
        acknowledge-mode: manual
        # 是否支持重试
        retry:
          enabled: true

3.RabbitMqQueueConfig

package com.dev.config;

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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 类名称:
 *
 * @author 李庆伟
 * @date 2024年03月04日 14:12
 */
@Configuration
public class RabbitMqQueueConfig {

    //绑定键
    public final static String QUEUE_ONE = "loss_queue";

    public final static String EXCHANGE_ONE = "loss_exchange";



    @Bean
    public Queue directQueue() {
        return new Queue(RabbitMqQueueConfig.QUEUE_ONE);
    }

    //Direct交换机 起名:directExchange
    @Bean
    DirectExchange directExchange() {
        return new DirectExchange(RabbitMqQueueConfig.EXCHANGE_ONE,true,false);
    }

    //绑定  将队列和交换机绑定, 并设置用于匹配键:directRoutingKey
    @Bean
    Binding bindingDirect() {
        return BindingBuilder.bind(directQueue()).to(directExchange()).with("directRoutingKey");
    }


}

4.RabbitController

package com.dev.controller;

import com.alibaba.fastjson.JSONObject;
import com.dev.config.RabbitMqQueueConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;


/**
 * 类名称:消息丢失问题
 *
 * @author lqw
 * @date 2024年02月27日 14:47
 */
@Slf4j
@RestController
@RequestMapping("loss")
public class RabbitController {


    @Autowired
    RabbitTemplate rabbitTemplate;  //使用RabbitTemplate,这提供了接收/发送等等方法


    /**
     * 消息丢失
     * @return
     */
    @GetMapping("/sendMessage")
    public String sendMessage() {
        String id = UUID.randomUUID().toString().replace("-","");
        Map<String,Object> addMap = new HashMap<>();//添加用户信息
        addMap.put("id",id);
        addMap.put("name","张龙");
        Message msg = MessageBuilder.withBody(JSONObject.toJSONString(addMap).getBytes()).setMessageId(id).build();

        rabbitTemplate.convertAndSend(RabbitMqQueueConfig.EXCHANGE_ONE, "directRoutingKey", msg);
        return "ok";
    }





}

5.RabbitMqListener

package com.dev.listener;

import com.alibaba.fastjson.JSON;
import com.rabbitmq.client.Channel;
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.Map;

/**
 * 类名称:
 *
 * @author 李庆伟
 * @date 2024年03月04日 16:54
 */

@Component
public class RabbitMqListener {

    @RabbitListener(queues = "loss_queue")
    @RabbitHandler
    public void process(Message msg, Channel channel) {
        System.out.println("Rabbitmq Direct : " + msg);
        //设置的唯一id,可以用来处理重复消费
        String id = msg.getMessageProperties().getMessageId();

        //消息队列自身设置的唯一标识
        long tag = msg.getMessageProperties().getDeliveryTag();

        //int a = 1/0;

        try {
            //监听到要添加的用户信息
            String dataStr = new String(msg.getBody());
            Map<String,Object> addMap = JSON.parseObject(dataStr,Map.class);


            //先去redis中查询是否已经添加过了该用户
            //如果未添加[重复消费]
            if(true){ //如果未添加
                //添加用户业务
                //....add(addMap);
                //告诉队列该消息已经消费
                channel.basicAck(tag,false);
            } else {//如果已添加
                //告诉队列该消息已经消费
                channel.basicAck(tag,false);
            }

        } catch (Exception e) {
            try {
                //tag:消息序号
                //multiple:消息的标识,是否确认多条,false只确认当前一个消息收到,
                //                               true确认所有consumer获得的消息(成功消费,消息从队列中删除)
                //requeue:是否要退回到队列 true 将消息再次放到mq队列中,false是不把消息放到队列中
                channel.basicNack(tag, true, false);
                //channel.basicNack(tag, true, true); //如果能走到此处,这样会把消息在放到队列中,会在次被监听到,陷入死循环
                //channel.basicNack(tag, false, true); //如果能走到此处,这样会把消息在放到队列中,会在次被监听到,陷入死循环
                //channel.basicNack(tag, false, false); //如果能走到此处,如果是扇形交换机,其他消费者也会再次消费此信息

                //可以把添加用户业务数据保存起来
                //...
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

    }

}

6.App

package com.dev;

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

/**
 * 类名称:
 *
 * @author 李庆伟
 * @date 2024年03月04日 14:11
 */
@SpringBootApplication
public class App {

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

}

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RabbitMQ是一个开源的消息中间件,用于在应用程序之间传递消息。在使用RabbitMQ时,可能会遇到消息丢失的情况。消息丢失可能由以下几个原因引起: 1. 生产者发送消息失败:当生产者发送消息RabbitMQ时,如果网络故障或者RabbitMQ服务不可用,消息可能无法成功发送RabbitMQ服务器,从而导致消息丢失。 2. 消息在传输过程中丢失:在消息从生产者发送消费者的过程中,如果网络故障或者RabbitMQ服务器故障,消息可能会在传输过程中丢失。 3. 消费者未正确处理消息:如果消费者在接收到消息没有正确处理消息,例如没有确认消息的接收,那么RabbitMQ会认为该消息未被成功消费,从而将其重新投递给其他消费者。如果没有其他消费者或者消费者一直无法正确处理该消息,那么该消息可能会被丢弃。 为了避免消息丢失,可以采取以下几个措施: 1. 使用持久化队列和消息:通过将队列和消息标记为持久化,可以确保在RabbitMQ服务器重启后,队列和消息仍然存在。 2. 使用事务或者确认模式:在发送消息时,可以使用事务或者确认模式来确保消息成功发送RabbitMQ服务器。 3. 设置消息的过期时间:可以为消息设置过期时间,当消息在指定时间内未被消费时,RabbitMQ会将其丢弃。 4. 监控和日志记录:及时监控RabbitMQ的运行状态,并记录日志,以便及时发现和解决消息丢失的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值