浅谈RabbitMQ消费端ACK和限流

消费者 ACK 和 消费端限流

ack指 Acknowledge,确认。 表示消费端收到消息后的确认方式。

有三种确认方式:

• 自动确认:acknowledge="none"

• 手动确认:acknowledge="manual"

• 根据异常情况确认:acknowledge="auto",(这种方式使用麻烦,不作讲解)

其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。

如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。

(1)代码实现

  1. 创建maven工程,消息的消费者工程,项目模块名称:rabbitmq-consumer-spring

  2. 添加依赖

    xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>2.1.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
  3. 在 resources 目录下创建 rabbitmq.properties 配置文件,添加链接RabbitMQ相关信息

    ini 复制代码
    rabbitmq.host=localhost
    rabbitmq.port=5672
    rabbitmq.username=guest
    rabbitmq.password=guest
    rabbitmq.virtual-host=/
  4. 在 resources 目录下创建 spring-rabbitmq-consumer.xml 配置文件,添加以下配置

    xml 复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:rabbit="http://www.springframework.org/schema/rabbit"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                 http://www.springframework.org/schema/beans/spring-beans.xsd
                 http://www.springframework.org/schema/context
                 https://www.springframework.org/schema/context/spring-context.xsd
                 http://www.springframework.org/schema/rabbit
                 http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
        <!--加载配置文件-->
        <context:property-placeholder location="classpath:rabbitmq.properties"/>
    
        <!-- 定义rabbitmq connectionFactory -->
        <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                                   port="${rabbitmq.port}"
                                   username="${rabbitmq.username}"
                                   password="${rabbitmq.password}"
                                   virtual-host="${rabbitmq.virtual-host}"/>
    
    
        <context:component-scan base-package="com.tang.listener" />
    
        <!--定义监听器容器  添加  acknowledge="manual" 手动-->
        <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
            <rabbit:listener ref="ackListener" queue-names="test_queue_confirm"/>
        </rabbit:listener-container>
    
    </beans>
  5. 编写ackListener 监听类实现ChannelAwareMessageListener接口

    java 复制代码
    package com.tang.listener;
    
    import com.rabbitmq.client.Channel;
    import org.springframework.amqp.core.Message;
    import org.springframework.amqp.core.MessageListener;
    import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    
    /**
     * Consumer ACK机制:
     *  1. 设置手动签收。acknowledge="manual"
     *  2. 让监听器类实现ChannelAwareMessageListener接口
     *  3. 如果消息成功处理,则调用channel的 basicAck()签收
     *  4. 如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新发送给consumer
     */
    @Component
    public class AckListener implements ChannelAwareMessageListener {
    
        @Override
        public void onMessage(Message message, Channel channel) throws Exception {
            long deliveryTag = message.getMessageProperties().getDeliveryTag();
    
            try {
                //1.接收转换消息
                System.out.println(new String(message.getBody()));
    
                //2. 处理业务逻辑
                System.out.println("处理业务逻辑...");
                int i = 3/0;//出现错误
                //3. 手动签收
                channel.basicAck(deliveryTag,true);
            } catch (Exception e) {
                //e.printStackTrace();
    
                //4.拒绝签收
                /*
                第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
                 */
                channel.basicNack(deliveryTag,true,true);
                // 了解
                //channel.basicReject(deliveryTag,true);
            }
        }
    }
  6. 编写测试类,启动容器监听MQ队列 com.tang.rabbimq

    less 复制代码
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
    public class ConsumerTest {
    
        @Test
        public void test(){
            while (true){
    
            }
        }
    }

小结

  • 在rabbit:listener-container标签中设置acknowledge属性,设置ack方式 none:自动确认,manual:手动确认
  • 如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,false);方法确认签收消息
  • 如果出现异常,则在catch中调用 basicNack或 basicReject,拒绝消息,让MQ重新发送消息。

如何保证消息的高可靠性传输?

1.持久化

• exchange要持久化

• queue要持久化

• message要持久化

2.生产方确认Confirm

3.消费方确认Ack

4.Broker高可用

消费端限流

如上图所示:如果在A系统中需要维护相关的业务功能,可能需要将A系统的服务停止,那么这个时候消息的生产者还是一直会向MQ中发送待处理的消息,消费者此时服务已经关闭,导致大量的消息都会在MQ中累积。如果当A系统成功启动后,默认情况下消息的消费者会一次性将MQ中累积的大量的消息全部拉取到自己的服务,导致服务在短时间内会处理大量的业务,可能会导致系统服务的崩溃。 所以消费端限流是非常有必要的。

可以通过MQ中的 listener-container 配置属性 perfetch = 1,表示消费端每次从mq拉去一条消息来消费,直到手动确认消费完毕后,才会继续拉去下一条消息。

1)代码实现

  1. 编写 QosListener 监听类,保证当前的监听类消息处理机制是 ACK 为手动方式

    java 复制代码
    @Component
    public class QosListener implements ChannelAwareMessageListener {
    
        @Override
        public void onMessage(Message message, Channel channel) throws Exception {
    
            Thread.sleep(1000);
            //1.获取消息
            System.out.println(new String(message.getBody()));
            //2. 处理业务逻辑
            //3. 签收
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
        }
    }
  2. 在配置文件的 listener-container 配置属性中添加配置

    ini 复制代码
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1" >
        
    <rabbit:listener ref="qosListener" queue-names="test_queue_confirm"/>

    配置说明:

    perfetch = 1,表示消费端每次从mq拉去一条消息来消费,直到手动确认消费完毕后,才会继续拉去下一条消息。

3.生产者,连发10条消息.

csharp 复制代码
    @Test
    public void testSend() {
        for (int i = 0; i < 10; i++) {
            //发送消息
            rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm....");
        }
    }

小结

  • rabbit:listener-container 中配置 prefetch属性设置消费端一次拉取多少消息
  • 消费端的确认模式一定为手动确认。acknowledge="manual"
相关推荐
uhakadotcom几秒前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
Asthenia04121 小时前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz9651 小时前
ovs patch port 对比 veth pair
后端
Asthenia04121 小时前
Java受检异常与非受检异常分析
后端
uhakadotcom2 小时前
快速开始使用 n8n
后端·面试·github
JavaGuide2 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9652 小时前
qemu 网络使用基础
后端
Asthenia04122 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04123 小时前
Spring 启动流程:比喻表达
后端
Asthenia04123 小时前
Spring 启动流程分析-含时序图
后端