RabbitMQ 小记:关于prefetch count的小测试demo

本文通过实验详细阐述了RabbitMQ中prefetchCount的机制。配置prefetchCount为3后,两个消费者启动时各拉取3条消息。之后,每当消费者处理并确认一条消息,RabbitMQ才会继续投递新的消息,实现了消息按需分配而非轮询。实验结果显示,prefetchCount有效地控制了消费者接收消息的频率,避免了消息堆积。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于rabbitmq prefetch count

1-1:起因

对prefetch count一直印象都是模糊的,只知道,代替了原来broker 对consumer的轮询,可以一次拉n条下来,但是当我一条一条通过接口发送message的时候还是轮询,于是有了这篇验证blog。

1-2:实验代码

  • 配置类
package com.example.springbootrabbitmq.configuration;

import com.example.springbootrabbitmq.MyConfirmCallback;
import com.example.springbootrabbitmq.MyReturnCallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author xx
 * @date 2021/10/5 16:24
 */
@Configuration
@Slf4j
public class RabbitmqConfig {
    @Autowired
    private CachingConnectionFactory connectionFactory;
    //自动装配消息监听器所在的容器工厂配置类实例
    @Autowired
    private SimpleRabbitListenerContainerFactoryConfigurer factoryConfigurer;

//    @Bean
//    public MessageConverter jsonMessageConverter() {
//        return new Jackson2JsonMessageConverter();
//    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
//        rabbitTemplate.setConfirmCallback(new MyConfirmCallback());
//        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
//            if (ack) {
                log.info("发送成功");
//            } else {
                correlationData.getReturned().getMessage().getMessageProperties().getMessageId();
                log.info("发送失败");
//            }
//        });
//        rabbitTemplate.setReturnsCallback(new MyReturnCallback());
        return rabbitTemplate;
    }

    /**
     * 针对不同的消费者,可以进行不同的容器配置,来实现多个消费者应用不同的配置。
     */
    @Bean(name = "singleListenerContainer")
    public SimpleRabbitListenerContainerFactory listenerContainer() {
        //定义消息监听器所在的容器工厂
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        //设置容器工厂所用的实例
        factory.setConnectionFactory(connectionFactory);
        //设置消息在传输中的格式,在这里采用JSON的格式进行传输
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
//        //设置并发消费者实例的初始数量。在这里为1个
//        factory.setConcurrentConsumers(1);
//        //设置并发消费者实例的最大数量。在这里为1个
//        factory.setMaxConcurrentConsumers(1);
//        //设置并发消费者实例中每个实例拉取的消息数量-在这里为1个
//        factory.setPrefetchCount(1);
        // 关闭自动应答
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        // 设置不公平分发,更改为每次读取1条消息,在消费者未回执确认之前,不在进行下一条消息的投送,而不是默认的轮询;
        // 也就是说,我处理完了,我再接受下一次的投递,属于消费者端的控制
        // 不设置的话,就是采用轮询的方法去监听队列,你一条我一条
        factory.setPrefetchCount(3);
        return factory;
    }
}
  • 队列交换机配置
    //队列 起名:springDirectQueue
    @Bean
    public Queue springDirectQueue() {
        // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,不然你电脑关机的时候队列就会消失。
        // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
        // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。

        //一般设置一下队列的持久化就好,其余两个就是默认false
        return new Queue("springDirectQueue", true);
    }

    @Bean
    public Queue springDirectQueue2() {
        return new Queue("springDirectQueue2", true);
    }

    //Direct交换机 起名:springDirectExchange
    @Bean
    public DirectExchange TestDirectExchange() {
        return new DirectExchange("springDirectExchange", true, false);
    }

    //用两个队列,将队列和交换机绑定, 并设置用于匹配键:springDirectRouting1, springDirectRouting2,用来实验routingKey的作用
    @Bean
    public Binding bindingDirectQueue1() {
        return BindingBuilder.bind(springDirectQueue()).to(TestDirectExchange()).with("springDirectRouting1");
    }

    @Bean
    public Binding bindingDirectQueue2() {
        return BindingBuilder.bind(springDirectQueue2()).to(TestDirectExchange()).with("springDirectRouting2");
    }
  • Producer.java
package com.example.springbootrabbitmq.controller;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

/**
 * @author xx
 * @date 2021/10/5 16:10
 */
@RestController
@RequestMapping("/directProducer")
@Slf4j
public class DirectProducerController {
    @Resource
    private RabbitTemplate rabbitTemplate;
    
    @GetMapping("/sendMessage")
    public void sendMessage(String msg) {
        Map<String, String> msgMap = new HashMap<>();
        msgMap.put("message", msg);
        String messageJson = JSONObject.toJSONString(msgMap);
        Message message = MessageBuilder
                .withBody(messageJson.getBytes())
                .setContentType(MessageProperties.CONTENT_TYPE_JSON)
                .setContentEncoding("utf-8")
                .setMessageId(UUID.randomUUID() + "")
                .build();
        log.info("生产者发送:" + new String(message.getBody(), StandardCharsets.UTF_8));
        CorrelationData correlationData = new CorrelationData();
        correlationData.setId("1");
        rabbitTemplate.convertAndSend("springDirectExchange", "springDirectRouting1", message, correlationData);
    }
}
  • consumer.java
    /*******************验证同一个队列的消息,默认采用轮询的方法 start ********************/
    @RabbitListener(queues = "springDirectQueue", containerFactory = "singleListenerContainer")
    public void processMessageQueue(Message message, Channel channel) throws Exception {
        String messageString = new String(message.getBody(), StandardCharsets.UTF_8);
        log.info("--------springDirectQueue_consumer1接受:{} --------", messageString);
        int channelNumber = channel.getChannelNumber();
        log.info("--------springDirectQueue_consumer1-channelNumber为:{}", channelNumber);

        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        log.info("--------springDirectQueue_consumer1-deliveryTag:" + deliveryTag);

        TimeUnit.SECONDS.sleep(3);
        channel.basicAck(deliveryTag, false);
//        channel.basicNack(deliveryTag, false, true);
        log.info("--------springDirectQueue_consumer1处理结束--------");
    }

    @RabbitListener(queues = "springDirectQueue", containerFactory = "singleListenerContainer")
    public void processMessageQueue2(Message message, Channel channel) throws Exception {
        String messageString = new String(message.getBody(), StandardCharsets.UTF_8);
        log.info("--------springDirectQueue_consumer2接受:{}--------", messageString);
        int channelNumber = channel.getChannelNumber();
        log.info("--------springDirectQueue_consumer2-channelNumber为:{}", channelNumber);

        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        log.info("--------springDirectQueue_consumer2-deliveryTag:" + deliveryTag);
        TimeUnit.SECONDS.sleep(3);
        channel.basicAck(deliveryTag, false);
        log.info("--------springDirectQueue_consumer2处理结束--------");
    }

1-3:测试流程

首先注释掉两个conusmer,通过接口往队列中发送10条message,并且给自己的message编号,当然可以循环10次,这样最好,我这里手点的。。通过rabbitmq web管理页面,可以看到队列中有10条message,这个时候,再去启动项目,让两个consumer监听队列。通过grep console 插件,我们可以查看两个consumer的日志信息。如下:

  • conusmer1

    2021-10-24 16:42:47.198  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1接受:{"message":"测试directExchange1"} --------
    2021-10-24 16:42:47.199  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1-channelNumber为:12
    2021-10-24 16:42:47.199  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1-deliveryTag:1
    2021-10-24 16:42:50.204  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1处理结束--------
    2021-10-24 16:42:50.205  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1接受:{"message":"测试directExchange2"} --------
    2021-10-24 16:42:50.205  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1-channelNumber为:12
    2021-10-24 16:42:50.205  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1-deliveryTag:2
    2021-10-24 16:42:53.208  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1处理结束--------
    2021-10-24 16:42:53.209  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1接受:{"message":"测试directExchange3"} --------
    2021-10-24 16:42:53.209  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1-channelNumber为:12
    2021-10-24 16:42:53.209  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1-deliveryTag:3
    2021-10-24 16:42:56.212  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1处理结束--------
    2021-10-24 16:42:56.213  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1接受:{"message":"测试directExchange7"} --------
    2021-10-24 16:42:56.214  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1-channelNumber为:12
    2021-10-24 16:42:56.214  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1-deliveryTag:4
    2021-10-24 16:42:59.214  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1处理结束--------
    2021-10-24 16:42:59.215  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1接受:{"message":"测试directExchange10"} --------
    2021-10-24 16:42:59.215  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1-channelNumber为:12
    2021-10-24 16:42:59.216  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1-deliveryTag:5
    2021-10-24 16:43:02.216  INFO 30448 --- [ntContainer#7-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer1处理结束--------
    
  • consumer2

    2021-10-24 16:42:47.198  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2接受:{"message":"测试directExchange4"}--------
    2021-10-24 16:42:47.199  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2-channelNumber为:13
    2021-10-24 16:42:47.199  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2-deliveryTag:1
    2021-10-24 16:42:50.204  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2处理结束--------
    2021-10-24 16:42:50.205  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2接受:{"message":"测试directExchange5"}--------
    2021-10-24 16:42:50.205  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2-channelNumber为:13
    2021-10-24 16:42:50.205  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2-deliveryTag:2
    2021-10-24 16:42:53.208  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2处理结束--------
    2021-10-24 16:42:53.209  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2接受:{"message":"测试directExchange6"}--------
    2021-10-24 16:42:53.209  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2-channelNumber为:13
    2021-10-24 16:42:53.209  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2-deliveryTag:3
    2021-10-24 16:42:56.213  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2处理结束--------
    2021-10-24 16:42:56.213  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2接受:{"message":"测试directExchange8"}--------
    2021-10-24 16:42:56.214  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2-channelNumber为:13
    2021-10-24 16:42:56.214  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2-deliveryTag:4
    2021-10-24 16:42:59.214  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2处理结束--------
    2021-10-24 16:42:59.215  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2接受:{"message":"测试directExchange9"}--------
    2021-10-24 16:42:59.215  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2-channelNumber为:13
    2021-10-24 16:42:59.215  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2-deliveryTag:5
    2021-10-24 16:43:02.216  INFO 30448 --- [ntContainer#8-1] c.e.s.c.DirectConsumerController         : --------springDirectQueue_consumer2处理结束--------
    

1-4:得出结论

consumer1接收到的消息编号是1,2,3,7,10;consumer2接受到的消息编号是4,5,6,8,9。从而可以得出,当监听开始时,consumer1和consumer2同时拉取到了3条消息(和我们配置的一样),但是之后,每个人并不是3条一拉取,broker看每个consumer,谁unack的数目没有达到prefetch count这个值,就会进行投递。这个作者总结得感觉很到位,引用下。

prefetch允许为每个consumer指定最大的unacked messages数目。简单来说就是用来指定一个consumer一次可以从Rabbit中获取多少条message并缓存在client中(RabbitMQ提供的各种语言的client library)。一旦缓冲区满了,Rabbit将会停止投递新的message到该consumer中直到它发出ack。

假设prefetch值设为10,共有两个consumer。意味着每个consumer每次会从queue中预抓取 10 条消息到本地缓存着等待消费。同时该channel的unacked数变为20。而Rabbit投递的顺序是,先为consumer1投递满10个message,再往consumer2投递10个message。如果这时有新message需要投递,先判断channel的unacked数是否等于20,如果是则不会将消息投递到consumer中,message继续呆在queue中。之后其中consumer对一条消息进行ack,unacked此时等于19,Rabbit就判断哪个consumer的unacked少于10,就投递到哪个consumer中。

作者:zcliu
链接:https://www.jianshu.com/p/4d043d3045ca
来源:简书

这样就算搞清楚了prefetch count设定了之后,consumer到底是怎么拉取消息了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

河海哥yyds

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值