关于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到底是怎么拉取消息了。